diff --git a/.gitattributes b/.gitattributes index 229901ecf..dd32d6cac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,3 +8,4 @@ test/browser/media/dash/*.mpd text eol=lf whitespace=-blank-at-eol test/browser/media/dash/*.m4s binary test/browser/media/*.m4a binary refactor/baselines/core-vendor/*.txt text eol=lf +refactor/baselines/thumbnail-vendor/* text eol=lf whitespace=-blank-at-eof diff --git a/docs/compiled/artplayer-tool-thumbnail.js b/docs/compiled/artplayer-tool-thumbnail.js index 25c99402c..4bee91cde 100644 --- a/docs/compiled/artplayer-tool-thumbnail.js +++ b/docs/compiled/artplayer-tool-thumbnail.js @@ -3,5 +3,32 @@ * Github: https://github.com/zhw2590582/ArtPlayer * (c) 2017-2026 Harvey Zhao * Released under the MIT License. + * + * ArtPlayer thumbnail tool bundled third-party notices + * + * tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class + * https://github.com/scottcorgan/tiny-emitter/tree/2.1.0 + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Corgan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this;function o(...i){r.off(e,o),t.apply(n,i)}return o._=t,this.on(e,o,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;rn.pause?.(),()=>n.removeAttribute?.("src"),()=>n.load?.(),()=>n.parentNode?.removeChild(n));i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function a(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function l(e,t){let s=t.epoch;const c=e.video;let l,u,d,h,p=!1,f=!1,m=()=>{};const b=[],y=new Promise((e,t)=>{l=e,u=t});y.catch(()=>{});const g=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function v(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{v()}catch(r){t||(t=r)}if(n)try{e.emit("error",t?.message)}catch(o){t=o}u(t)}}function U(t){if(!g())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!g())return;if(r===t.points.length)return void function(){if(g())try{v(),e.emit("done"),l()}catch(t){let r=t;try{e.emit("error",t?.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let a=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!g()||a||c.seeking||void 0!==c.readyState&&c.readyState<2)){a=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!g())return;t.canvas.toBlob(s=>{if(g()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,g);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(l){w(l)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(g()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,l=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=a(i,0,n.duration),e.option.end=a(s||n.duration,i,n.duration),l(e.option.end>e.option.begin,"End time must be greater than the start time"),l(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,l(e.file&&n,"Please select the video file first"),l(!e.processing,"There is currently a task in progress, please wait a moment..."),l(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),g()||clearTimeout(d)}h={video:c,promise:y,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{g()&&w(new Error(`Unable to load video: media error ${c.error?.code||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!g())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),y;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}return y}const u=new WeakMap;function d(e){let t;for(const[r,o]of e.listeners.splice(0))try{e.input.removeEventListener(r,o)}catch(n){t||(t=n)}if(e.wrapper){try{e.input.parentNode&&e.input.parentNode.removeChild(e.input)}catch(n){t||(t=n)}"relative"===e.wrapper.style.position&&(e.wrapper.style.position=e.position)}if(t)throw t}function h(e,t){try{for(const[n,r]of t)e.listeners.push([n,r]),e.input.addEventListener(n,r)}catch(n){try{d(e)}catch{}throw n}}function p(e){const t=u.get(e);t&&(u.delete(e),d(t))}class f extends e{constructor(e={}){super(),this.processing=!1,this.option={};try{this.setup(Object.assign({},f.DEFAULTS,e)),this.video=f.creatVideo(),n(this).video=this.video,this.duration=0,this.inputChange=this.inputChange.bind(this),this.ondrop=this.ondrop.bind(this),function(e,t){const n=u.get(e);n.callbacks=[["change",e.inputChange],["dragover",t],["drop",e.ondrop]],h(n,n.callbacks)}(this,f.ondragover)}catch(t){s(this,"construction failed");try{p(this)}catch{}try{c(this)}catch{}throw t}}static get DEFAULTS(){return{number:60,width:160,height:90,column:10,begin:0,end:Number.NaN}}static ondragover(e){e.preventDefault()}ondrop(e){if(n(this).closed)return;e.preventDefault();const t=e.dataTransfer.files[0];this.loadVideo(t)}setup(e={}){return n(this).closed||function(e,t){const o=n(e),i=++o.inputEpoch,s=()=>!o.closed&&o.inputEpoch===i,c=Object.assign({},e.option,t),l=c.fileInput;e.errorHandle(l instanceof Element,"The 'fileInput' is not a Element");for(const n of["number","width","column","begin","end"])e.errorHandle("number"===typeof c[n],`The '${n}' is not a number`);if(c.number=a(c.number,10,1e3),c.width=a(c.width,10,1e3),c.column=a(c.column,1,1e3),!s())throw r("input setup superseded");const p=u.get(e);if(p&&(l===p.input||l===p.wrapper))return c.fileInput=p.input,e.option=c,c;const f={input:l,wrapper:null,position:"",listeners:[],callbacks:p?.callbacks||null};try{if("INPUT"===l.tagName&&"file"===l.type||(f.wrapper=l,f.position=l.style.position,f.input=document.createElement("input"),f.input.type="file",Object.assign(f.input.style,{position:"absolute",width:"100%",height:"100%",left:"0",top:"0",right:"0",bottom:"0",opacity:"0"}),l.style.position="relative",l.appendChild(f.input)),!s())throw r("input setup superseded");if(f.callbacks&&h(f,f.callbacks),!s())throw r("input setup superseded")}catch(m){try{d(f)}catch{}throw m}c.fileInput=f.input,u.set(e,f),e.option=c,p&&d(p)}(this,e),this}static creatVideo(){const e=document.createElement("video");e.style.position="absolute",e.style.top="-9999px",e.style.left="-9999px",e.muted=!0,e.controls=!0;try{document.body.appendChild(e)}catch(t){throw e.parentNode&&e.parentNode.removeChild(e),t}return e}inputChange(e){if(n(this).closed)return;const t=this.option.fileInput.files[0];this.loadVideo(t),e.target.value=""}loadVideo(e){!function(e,t){const r=n(e);if(!t||r.closed)return;const s=r.epoch,c=e.video,a=c.canPlayType(t.type);if(e.errorHandle("maybe"===a||"probably"===a,`Playback of this file format is not supported: ${t.type}`),r.closed||r.epoch!==s)return;const l=URL.createObjectURL(t);if(r.sourceUrls.add(l),r.closed||r.epoch!==s)return void o(r.sourceUrls,l);const u=++r.epoch;!r.sourceUrl&&!e.file&&r.job?.adopt(u)||r.job?.cancel("source changed"),r.loading=!0;const d=()=>!r.closed&&r.epoch===u,h=r.sourceListeners,p=[];r.sourceListeners=p;let f=()=>{},m=!1;const b=()=>{if(!d()||m||c.currentSrc&&c.currentSrc!==l)return;m=!0,r.loading=!1;const t=new Error(`Unable to load video: media error ${c.error?.code||0}`);r.job?r.job.fail(t):e.emit("error",t.message)},y=()=>{!d()||c.currentSrc&&c.currentSrc!==l||(r.loading=!1,r.job?.ready())};try{if(i(h.splice(0)),!d())return void o(r.sourceUrls,l);for(const[e,t]of[["error",b],["loadedmetadata",y]])if(f=()=>c.removeEventListener(e,t),p.push(f),c.addEventListener(e,t),!d())return i([f,...p.splice(0)]),void o(r.sourceUrls,l);if(e.videoUrl=l,e.file=t,e.emit("file",t),!d())return void o(r.sourceUrls,l);if(c.src=l,!d())return void o(r.sourceUrls,l);r.sourceUrl=l;for(const e of[...r.sourceUrls])e!==l&&o(r.sourceUrls,e);d()&&e.emit("video",c)}catch(g){d()&&(r.loading=!1,r.job?.cancel("source setup failed"));try{i([f,...p.splice(0)])}catch{}throw o(r.sourceUrls,l),g}}(this,e)}start(){return function(e){const t=n(e);if(t.closed){const e=Promise.reject(r("destroyed"));return e.catch(()=>{}),e}return e.errorHandle(!t.job,"There is currently a task in progress, please wait a moment..."),l(e,t)}(this)}creatScreenshotDate(){return function(e,t){const{number:n,width:r,height:o,column:i,begin:s}=e,c=t/n,a=[s+c];for(;a.length({time:e-c/2,x:t%i*r,y:Math.floor(t/i)*o}))}(this.option,this.duration)}creatCanvas(){return function(e){const{number:t,width:n,height:r,column:o}=e,i=document.createElement("canvas"),s=i.getContext("2d");return i.width=n*o,i.height=Math.ceil(t/o)*r+30,s.fillStyle="black",s.fillRect(0,0,i.width,i.height),s.font="14px Georgia",s.fillStyle="#fff",s.fillText(`From: https://artplayer.org/, Number: ${t}, Width: ${n}, Height: ${r}, Column: ${o}`,10,i.height-11),i}(this.option)}download(){if(n(this).closed)return this;this.errorHandle(this.file&&this.thumbnailUrl,"Download does not seem to be ready, please create preview first"),this.errorHandle(!this.processing,"There is currently a task in progress, please wait a moment...");const e=function(e,t){const n=document.createElement("a"),r=`${function(e){const t=e.split(".");return t.pop(),t.join(".")}(e.name)}.png`;n.download=r,n.href=t;try{document.body.appendChild(n),n.click()}finally{n.parentNode&&n.parentNode.removeChild(n)}return r}(this.file,this.thumbnailUrl);return this.emit("download",e),this}errorHandle(e,t){if(!e)throw this.emit("error",t),new Error(t)}destroy(){s(this)&&i([()=>p(this),()=>c(this),()=>this.emit("destroy")])}}return f}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this,o=t;function i(...t){r.off(e,i),o.apply(n,t)}return i._=t,this.on(e,i,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;rn.pause?.(),()=>n.removeAttribute?.("src"),()=>n.load?.(),()=>n.parentNode?.removeChild(n));i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function a(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function l(e,t){let s=t.epoch;const c=e.video;let l,u,d,h,p=!1,f=!1,m=()=>{};const b=[],y=new Promise((e,t)=>{l=e,u=t});y.catch(()=>{});const g=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function v(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{v()}catch(r){t||(t=r)}if(n)try{e.emit("error",t?.message)}catch(o){t=o}u(t)}}function U(t){if(!g())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!g())return;if(r===t.points.length)return void function(){if(g())try{v(),e.emit("done"),l()}catch(t){let r=t;try{e.emit("error",t?.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let a=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!g()||a||c.seeking||void 0!==c.readyState&&c.readyState<2)){a=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!g())return;t.canvas.toBlob(s=>{if(g()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,g);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(l){w(l)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(g()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,l=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=a(i,0,n.duration),e.option.end=a(s||n.duration,i,n.duration),l(e.option.end>e.option.begin,"End time must be greater than the start time"),l(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,l(e.file&&n,"Please select the video file first"),l(!e.processing,"There is currently a task in progress, please wait a moment..."),l(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),g()||clearTimeout(d)}h={video:c,promise:y,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{g()&&w(new Error(`Unable to load video: media error ${c.error?.code||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!g())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),y;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}return y}const u=new WeakMap;function d(e){let t;for(const[r,o]of e.listeners.splice(0))try{e.input.removeEventListener(r,o)}catch(n){t||(t=n)}if(e.wrapper){try{e.input.parentNode&&e.input.parentNode.removeChild(e.input)}catch(n){t||(t=n)}"relative"===e.wrapper.style.position&&(e.wrapper.style.position=e.position)}if(t)throw t}function h(e,t){try{for(const[n,r]of t)e.listeners.push([n,r]),e.input.addEventListener(n,r)}catch(n){try{d(e)}catch{}throw n}}function p(e){const t=u.get(e);t&&(u.delete(e),d(t))}class f extends e{constructor(e={}){super(),this.processing=!1,this.option={};try{this.setup(Object.assign({},f.DEFAULTS,e)),this.video=f.creatVideo(),n(this).video=this.video,this.duration=0,this.inputChange=this.inputChange.bind(this),this.ondrop=this.ondrop.bind(this),function(e,t){const n=u.get(e);n.callbacks=[["change",e.inputChange],["dragover",t],["drop",e.ondrop]],h(n,n.callbacks)}(this,f.ondragover)}catch(t){s(this,"construction failed");try{p(this)}catch{}try{c(this)}catch{}throw t}}static get DEFAULTS(){return{number:60,width:160,height:90,column:10,begin:0,end:Number.NaN}}static ondragover(e){e.preventDefault()}ondrop(e){if(n(this).closed)return;e.preventDefault();const t=e.dataTransfer.files[0];this.loadVideo(t)}setup(e={}){return n(this).closed||function(e,t){const o=n(e),i=++o.inputEpoch,s=()=>!o.closed&&o.inputEpoch===i,c=Object.assign({},e.option,t),l=c.fileInput;e.errorHandle(l instanceof Element,"The 'fileInput' is not a Element");for(const n of["number","width","column","begin","end"])e.errorHandle("number"===typeof c[n],`The '${n}' is not a number`);if(c.number=a(c.number,10,1e3),c.width=a(c.width,10,1e3),c.column=a(c.column,1,1e3),!s())throw r("input setup superseded");const p=u.get(e);if(p&&(l===p.input||l===p.wrapper))return c.fileInput=p.input,e.option=c,c;const f={input:l,wrapper:null,position:"",listeners:[],callbacks:p?.callbacks||null};try{if("INPUT"===l.tagName&&"file"===l.type||(f.wrapper=l,f.position=l.style.position,f.input=document.createElement("input"),f.input.type="file",Object.assign(f.input.style,{position:"absolute",width:"100%",height:"100%",left:"0",top:"0",right:"0",bottom:"0",opacity:"0"}),l.style.position="relative",l.appendChild(f.input)),!s())throw r("input setup superseded");if(f.callbacks&&h(f,f.callbacks),!s())throw r("input setup superseded")}catch(m){try{d(f)}catch{}throw m}c.fileInput=f.input,u.set(e,f),e.option=c,p&&d(p)}(this,e),this}static creatVideo(){const e=document.createElement("video");e.style.position="absolute",e.style.top="-9999px",e.style.left="-9999px",e.muted=!0,e.controls=!0;try{document.body.appendChild(e)}catch(t){throw e.parentNode&&e.parentNode.removeChild(e),t}return e}inputChange(e){if(n(this).closed)return;const t=this.option.fileInput.files[0];this.loadVideo(t),e.target.value=""}loadVideo(e){!function(e,t){const r=n(e);if(!t||r.closed)return;const s=r.epoch,c=e.video,a=c.canPlayType(t.type);if(e.errorHandle("maybe"===a||"probably"===a,`Playback of this file format is not supported: ${t.type}`),r.closed||r.epoch!==s)return;const l=URL.createObjectURL(t);if(r.sourceUrls.add(l),r.closed||r.epoch!==s)return void o(r.sourceUrls,l);const u=++r.epoch;!r.sourceUrl&&!e.file&&r.job?.adopt(u)||r.job?.cancel("source changed"),r.loading=!0;const d=()=>!r.closed&&r.epoch===u,h=r.sourceListeners,p=[];r.sourceListeners=p;let f=()=>{},m=!1;const b=()=>{if(!d()||m||c.currentSrc&&c.currentSrc!==l)return;m=!0,r.loading=!1;const t=new Error(`Unable to load video: media error ${c.error?.code||0}`);r.job?r.job.fail(t):e.emit("error",t.message)},y=()=>{!d()||c.currentSrc&&c.currentSrc!==l||(r.loading=!1,r.job?.ready())};try{if(i(h.splice(0)),!d())return void o(r.sourceUrls,l);for(const[e,t]of[["error",b],["loadedmetadata",y]])if(f=()=>c.removeEventListener(e,t),p.push(f),c.addEventListener(e,t),!d())return i([f,...p.splice(0)]),void o(r.sourceUrls,l);if(e.videoUrl=l,e.file=t,e.emit("file",t),!d())return void o(r.sourceUrls,l);if(c.src=l,!d())return void o(r.sourceUrls,l);r.sourceUrl=l;for(const e of[...r.sourceUrls])e!==l&&o(r.sourceUrls,e);d()&&e.emit("video",c)}catch(g){d()&&(r.loading=!1,r.job?.cancel("source setup failed"));try{i([f,...p.splice(0)])}catch{}throw o(r.sourceUrls,l),g}}(this,e)}start(){return function(e){const t=n(e);if(t.closed){const e=Promise.reject(r("destroyed"));return e.catch(()=>{}),e}return e.errorHandle(!t.job,"There is currently a task in progress, please wait a moment..."),l(e,t)}(this)}creatScreenshotDate(){return function(e,t){const{number:n,width:r,height:o,column:i,begin:s}=e,c=t/n,a=[s+c];for(;a.length({time:e-c/2,x:t%i*r,y:Math.floor(t/i)*o}))}(this.option,this.duration)}creatCanvas(){return function(e){const{number:t,width:n,height:r,column:o}=e,i=document.createElement("canvas"),s=i.getContext("2d");return i.width=n*o,i.height=Math.ceil(t/o)*r+30,s.fillStyle="black",s.fillRect(0,0,i.width,i.height),s.font="14px Georgia",s.fillStyle="#fff",s.fillText(`From: https://artplayer.org/, Number: ${t}, Width: ${n}, Height: ${r}, Column: ${o}`,10,i.height-11),i}(this.option)}download(){if(n(this).closed)return this;this.errorHandle(this.file&&this.thumbnailUrl,"Download does not seem to be ready, please create preview first"),this.errorHandle(!this.processing,"There is currently a task in progress, please wait a moment...");const e=function(e,t){const n=document.createElement("a"),r=`${function(e){const t=e.split(".");return t.pop(),t.join(".")}(e.name)}.png`;n.download=r,n.href=t;try{document.body.appendChild(n),n.click()}finally{n.parentNode&&n.parentNode.removeChild(n)}return r}(this.file,this.thumbnailUrl);return this.emit("download",e),this}errorHandle(e,t){if(!e)throw this.emit("error",t),new Error(t)}destroy(){s(this)&&i([()=>p(this),()=>c(this),()=>this.emit("destroy")])}}return f}); diff --git a/docs/compiled/artplayer-tool-thumbnail.legacy.js b/docs/compiled/artplayer-tool-thumbnail.legacy.js index 543a7a290..86ca050d8 100644 --- a/docs/compiled/artplayer-tool-thumbnail.legacy.js +++ b/docs/compiled/artplayer-tool-thumbnail.legacy.js @@ -3,5 +3,32 @@ * Github: https://github.com/zhw2590582/ArtPlayer * (c) 2017-2026 Harvey Zhao * Released under the MIT License. + * + * ArtPlayer thumbnail tool bundled third-party notices + * + * tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class + * https://github.com/scottcorgan/tiny-emitter/tree/2.1.0 + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Corgan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this;function o(...i){r.off(e,o),t.apply(n,i)}return o._=t,this.on(e,o,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r{var e;return null==(e=n.pause)?void 0:e.call(n)},()=>{var e;return null==(e=n.removeAttribute)?void 0:e.call(n,"src")},()=>{var e;return null==(e=n.load)?void 0:e.call(n)},()=>{var e;return null==(e=n.parentNode)?void 0:e.removeChild(n)});i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function l(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function a(e,t){let s=t.epoch;const c=e.video;let a,u,d,h,p=!1,f=!1,m=()=>{};const b=[],v=new Promise((e,t)=>{a=e,u=t});v.catch(()=>{});const y=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function g(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{g()}catch(r){t||(t=r)}if(n)try{e.emit("error",null==t?void 0:t.message)}catch(o){t=o}u(t)}}function U(t){if(!y())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!y())return;if(r===t.points.length)return void function(){if(y())try{g(),e.emit("done"),a()}catch(t){let r=t;try{e.emit("error",null==t?void 0:t.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let l=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!y()||l||c.seeking||void 0!==c.readyState&&c.readyState<2)){l=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!y())return;t.canvas.toBlob(s=>{if(y()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,y);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(a){w(a)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(y()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,a=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=l(i,0,n.duration),e.option.end=l(s||n.duration,i,n.duration),a(e.option.end>e.option.begin,"End time must be greater than the start time"),a(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,a(e.file&&n,"Please select the video file first"),a(!e.processing,"There is currently a task in progress, please wait a moment..."),a(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),y()||clearTimeout(d)}h={video:c,promise:v,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{var e;y()&&w(new Error(`Unable to load video: media error ${(null==(e=c.error)?void 0:e.code)||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!y())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),v;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}return v}const u=new WeakMap;function d(e){let t;for(const[r,o]of e.listeners.splice(0))try{e.input.removeEventListener(r,o)}catch(n){t||(t=n)}if(e.wrapper){try{e.input.parentNode&&e.input.parentNode.removeChild(e.input)}catch(n){t||(t=n)}"relative"===e.wrapper.style.position&&(e.wrapper.style.position=e.position)}if(t)throw t}function h(e,t){try{for(const[n,r]of t)e.listeners.push([n,r]),e.input.addEventListener(n,r)}catch(n){try{d(e)}catch(r){}throw n}}function p(e){const t=u.get(e);t&&(u.delete(e),d(t))}class f extends e{constructor(e={}){super(),this.processing=!1,this.option={};try{this.setup(Object.assign({},f.DEFAULTS,e)),this.video=f.creatVideo(),n(this).video=this.video,this.duration=0,this.inputChange=this.inputChange.bind(this),this.ondrop=this.ondrop.bind(this),function(e,t){const n=u.get(e);n.callbacks=[["change",e.inputChange],["dragover",t],["drop",e.ondrop]],h(n,n.callbacks)}(this,f.ondragover)}catch(t){s(this,"construction failed");try{p(this)}catch(r){}try{c(this)}catch(r){}throw t}}static get DEFAULTS(){return{number:60,width:160,height:90,column:10,begin:0,end:Number.NaN}}static ondragover(e){e.preventDefault()}ondrop(e){if(n(this).closed)return;e.preventDefault();const t=e.dataTransfer.files[0];this.loadVideo(t)}setup(e={}){return n(this).closed||function(e,t){const o=n(e),i=++o.inputEpoch,s=()=>!o.closed&&o.inputEpoch===i,c=Object.assign({},e.option,t),a=c.fileInput;e.errorHandle(a instanceof Element,"The 'fileInput' is not a Element");for(const n of["number","width","column","begin","end"])e.errorHandle("number"===typeof c[n],`The '${n}' is not a number`);if(c.number=l(c.number,10,1e3),c.width=l(c.width,10,1e3),c.column=l(c.column,1,1e3),!s())throw r("input setup superseded");const p=u.get(e);if(p&&(a===p.input||a===p.wrapper))return c.fileInput=p.input,e.option=c,c;const f={input:a,wrapper:null,position:"",listeners:[],callbacks:(null==p?void 0:p.callbacks)||null};try{if("INPUT"===a.tagName&&"file"===a.type||(f.wrapper=a,f.position=a.style.position,f.input=document.createElement("input"),f.input.type="file",Object.assign(f.input.style,{position:"absolute",width:"100%",height:"100%",left:"0",top:"0",right:"0",bottom:"0",opacity:"0"}),a.style.position="relative",a.appendChild(f.input)),!s())throw r("input setup superseded");if(f.callbacks&&h(f,f.callbacks),!s())throw r("input setup superseded")}catch(m){try{d(f)}catch(b){}throw m}c.fileInput=f.input,u.set(e,f),e.option=c,p&&d(p)}(this,e),this}static creatVideo(){const e=document.createElement("video");e.style.position="absolute",e.style.top="-9999px",e.style.left="-9999px",e.muted=!0,e.controls=!0;try{document.body.appendChild(e)}catch(t){throw e.parentNode&&e.parentNode.removeChild(e),t}return e}inputChange(e){if(n(this).closed)return;const t=this.option.fileInput.files[0];this.loadVideo(t),e.target.value=""}loadVideo(e){!function(e,t){var r,s,c;const l=n(e);if(!t||l.closed)return;const a=l.epoch,u=e.video,d=u.canPlayType(t.type);if(e.errorHandle("maybe"===d||"probably"===d,`Playback of this file format is not supported: ${t.type}`),l.closed||l.epoch!==a)return;const h=URL.createObjectURL(t);if(l.sourceUrls.add(h),l.closed||l.epoch!==a)return void o(l.sourceUrls,h);const p=++l.epoch;!l.sourceUrl&&!e.file&&(null==(r=l.job)?void 0:r.adopt(p))||null==(s=l.job)||s.cancel("source changed"),l.loading=!0;const f=()=>!l.closed&&l.epoch===p,m=l.sourceListeners,b=[];l.sourceListeners=b;let v=()=>{},y=!1;const g=()=>{var t;if(!f()||y||u.currentSrc&&u.currentSrc!==h)return;y=!0,l.loading=!1;const n=new Error(`Unable to load video: media error ${(null==(t=u.error)?void 0:t.code)||0}`);l.job?l.job.fail(n):e.emit("error",n.message)},w=()=>{var e;!f()||u.currentSrc&&u.currentSrc!==h||(l.loading=!1,null==(e=l.job)||e.ready())};try{if(i(m.splice(0)),!f())return void o(l.sourceUrls,h);for(const[e,t]of[["error",g],["loadedmetadata",w]])if(v=()=>u.removeEventListener(e,t),b.push(v),u.addEventListener(e,t),!f())return i([v,...b.splice(0)]),void o(l.sourceUrls,h);if(e.videoUrl=h,e.file=t,e.emit("file",t),!f())return void o(l.sourceUrls,h);if(u.src=h,!f())return void o(l.sourceUrls,h);l.sourceUrl=h;for(const e of[...l.sourceUrls])e!==h&&o(l.sourceUrls,e);f()&&e.emit("video",u)}catch(U){f()&&(l.loading=!1,null==(c=l.job)||c.cancel("source setup failed"));try{i([v,...b.splice(0)])}catch(E){}throw o(l.sourceUrls,h),U}}(this,e)}start(){return function(e){const t=n(e);if(t.closed){const e=Promise.reject(r("destroyed"));return e.catch(()=>{}),e}return e.errorHandle(!t.job,"There is currently a task in progress, please wait a moment..."),a(e,t)}(this)}creatScreenshotDate(){return function(e,t){const{number:n,width:r,height:o,column:i,begin:s}=e,c=t/n,l=[s+c];for(;l.length({time:e-c/2,x:t%i*r,y:Math.floor(t/i)*o}))}(this.option,this.duration)}creatCanvas(){return function(e){const{number:t,width:n,height:r,column:o}=e,i=document.createElement("canvas"),s=i.getContext("2d");return i.width=n*o,i.height=Math.ceil(t/o)*r+30,s.fillStyle="black",s.fillRect(0,0,i.width,i.height),s.font="14px Georgia",s.fillStyle="#fff",s.fillText(`From: https://artplayer.org/, Number: ${t}, Width: ${n}, Height: ${r}, Column: ${o}`,10,i.height-11),i}(this.option)}download(){if(n(this).closed)return this;this.errorHandle(this.file&&this.thumbnailUrl,"Download does not seem to be ready, please create preview first"),this.errorHandle(!this.processing,"There is currently a task in progress, please wait a moment...");const e=function(e,t){const n=document.createElement("a"),r=`${function(e){const t=e.split(".");return t.pop(),t.join(".")}(e.name)}.png`;n.download=r,n.href=t;try{document.body.appendChild(n),n.click()}finally{n.parentNode&&n.parentNode.removeChild(n)}return r}(this.file,this.thumbnailUrl);return this.emit("download",e),this}errorHandle(e,t){if(!e)throw this.emit("error",t),new Error(t)}destroy(){s(this)&&i([()=>p(this),()=>c(this),()=>this.emit("destroy")])}}return f}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this,o=t;function i(...t){r.off(e,i),o.apply(n,t)}return i._=t,this.on(e,i,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r{var e;return null==(e=n.pause)?void 0:e.call(n)},()=>{var e;return null==(e=n.removeAttribute)?void 0:e.call(n,"src")},()=>{var e;return null==(e=n.load)?void 0:e.call(n)},()=>{var e;return null==(e=n.parentNode)?void 0:e.removeChild(n)});i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function l(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function a(e,t){let s=t.epoch;const c=e.video;let a,u,d,h,p=!1,f=!1,m=()=>{};const b=[],v=new Promise((e,t)=>{a=e,u=t});v.catch(()=>{});const y=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function g(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{g()}catch(r){t||(t=r)}if(n)try{e.emit("error",null==t?void 0:t.message)}catch(o){t=o}u(t)}}function U(t){if(!y())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!y())return;if(r===t.points.length)return void function(){if(y())try{g(),e.emit("done"),a()}catch(t){let r=t;try{e.emit("error",null==t?void 0:t.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let l=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!y()||l||c.seeking||void 0!==c.readyState&&c.readyState<2)){l=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!y())return;t.canvas.toBlob(s=>{if(y()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,y);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(a){w(a)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(y()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,a=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=l(i,0,n.duration),e.option.end=l(s||n.duration,i,n.duration),a(e.option.end>e.option.begin,"End time must be greater than the start time"),a(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,a(e.file&&n,"Please select the video file first"),a(!e.processing,"There is currently a task in progress, please wait a moment..."),a(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),y()||clearTimeout(d)}h={video:c,promise:v,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{var e;y()&&w(new Error(`Unable to load video: media error ${(null==(e=c.error)?void 0:e.code)||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!y())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),v;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}return v}const u=new WeakMap;function d(e){let t;for(const[r,o]of e.listeners.splice(0))try{e.input.removeEventListener(r,o)}catch(n){t||(t=n)}if(e.wrapper){try{e.input.parentNode&&e.input.parentNode.removeChild(e.input)}catch(n){t||(t=n)}"relative"===e.wrapper.style.position&&(e.wrapper.style.position=e.position)}if(t)throw t}function h(e,t){try{for(const[n,r]of t)e.listeners.push([n,r]),e.input.addEventListener(n,r)}catch(n){try{d(e)}catch(r){}throw n}}function p(e){const t=u.get(e);t&&(u.delete(e),d(t))}class f extends e{constructor(e={}){super(),this.processing=!1,this.option={};try{this.setup(Object.assign({},f.DEFAULTS,e)),this.video=f.creatVideo(),n(this).video=this.video,this.duration=0,this.inputChange=this.inputChange.bind(this),this.ondrop=this.ondrop.bind(this),function(e,t){const n=u.get(e);n.callbacks=[["change",e.inputChange],["dragover",t],["drop",e.ondrop]],h(n,n.callbacks)}(this,f.ondragover)}catch(t){s(this,"construction failed");try{p(this)}catch(r){}try{c(this)}catch(r){}throw t}}static get DEFAULTS(){return{number:60,width:160,height:90,column:10,begin:0,end:Number.NaN}}static ondragover(e){e.preventDefault()}ondrop(e){if(n(this).closed)return;e.preventDefault();const t=e.dataTransfer.files[0];this.loadVideo(t)}setup(e={}){return n(this).closed||function(e,t){const o=n(e),i=++o.inputEpoch,s=()=>!o.closed&&o.inputEpoch===i,c=Object.assign({},e.option,t),a=c.fileInput;e.errorHandle(a instanceof Element,"The 'fileInput' is not a Element");for(const n of["number","width","column","begin","end"])e.errorHandle("number"===typeof c[n],`The '${n}' is not a number`);if(c.number=l(c.number,10,1e3),c.width=l(c.width,10,1e3),c.column=l(c.column,1,1e3),!s())throw r("input setup superseded");const p=u.get(e);if(p&&(a===p.input||a===p.wrapper))return c.fileInput=p.input,e.option=c,c;const f={input:a,wrapper:null,position:"",listeners:[],callbacks:(null==p?void 0:p.callbacks)||null};try{if("INPUT"===a.tagName&&"file"===a.type||(f.wrapper=a,f.position=a.style.position,f.input=document.createElement("input"),f.input.type="file",Object.assign(f.input.style,{position:"absolute",width:"100%",height:"100%",left:"0",top:"0",right:"0",bottom:"0",opacity:"0"}),a.style.position="relative",a.appendChild(f.input)),!s())throw r("input setup superseded");if(f.callbacks&&h(f,f.callbacks),!s())throw r("input setup superseded")}catch(m){try{d(f)}catch(b){}throw m}c.fileInput=f.input,u.set(e,f),e.option=c,p&&d(p)}(this,e),this}static creatVideo(){const e=document.createElement("video");e.style.position="absolute",e.style.top="-9999px",e.style.left="-9999px",e.muted=!0,e.controls=!0;try{document.body.appendChild(e)}catch(t){throw e.parentNode&&e.parentNode.removeChild(e),t}return e}inputChange(e){if(n(this).closed)return;const t=this.option.fileInput.files[0];this.loadVideo(t),e.target.value=""}loadVideo(e){!function(e,t){var r,s,c;const l=n(e);if(!t||l.closed)return;const a=l.epoch,u=e.video,d=u.canPlayType(t.type);if(e.errorHandle("maybe"===d||"probably"===d,`Playback of this file format is not supported: ${t.type}`),l.closed||l.epoch!==a)return;const h=URL.createObjectURL(t);if(l.sourceUrls.add(h),l.closed||l.epoch!==a)return void o(l.sourceUrls,h);const p=++l.epoch;!l.sourceUrl&&!e.file&&(null==(r=l.job)?void 0:r.adopt(p))||null==(s=l.job)||s.cancel("source changed"),l.loading=!0;const f=()=>!l.closed&&l.epoch===p,m=l.sourceListeners,b=[];l.sourceListeners=b;let v=()=>{},y=!1;const g=()=>{var t;if(!f()||y||u.currentSrc&&u.currentSrc!==h)return;y=!0,l.loading=!1;const n=new Error(`Unable to load video: media error ${(null==(t=u.error)?void 0:t.code)||0}`);l.job?l.job.fail(n):e.emit("error",n.message)},w=()=>{var e;!f()||u.currentSrc&&u.currentSrc!==h||(l.loading=!1,null==(e=l.job)||e.ready())};try{if(i(m.splice(0)),!f())return void o(l.sourceUrls,h);for(const[e,t]of[["error",g],["loadedmetadata",w]])if(v=()=>u.removeEventListener(e,t),b.push(v),u.addEventListener(e,t),!f())return i([v,...b.splice(0)]),void o(l.sourceUrls,h);if(e.videoUrl=h,e.file=t,e.emit("file",t),!f())return void o(l.sourceUrls,h);if(u.src=h,!f())return void o(l.sourceUrls,h);l.sourceUrl=h;for(const e of[...l.sourceUrls])e!==h&&o(l.sourceUrls,e);f()&&e.emit("video",u)}catch(U){f()&&(l.loading=!1,null==(c=l.job)||c.cancel("source setup failed"));try{i([v,...b.splice(0)])}catch(E){}throw o(l.sourceUrls,h),U}}(this,e)}start(){return function(e){const t=n(e);if(t.closed){const e=Promise.reject(r("destroyed"));return e.catch(()=>{}),e}return e.errorHandle(!t.job,"There is currently a task in progress, please wait a moment..."),a(e,t)}(this)}creatScreenshotDate(){return function(e,t){const{number:n,width:r,height:o,column:i,begin:s}=e,c=t/n,l=[s+c];for(;l.length({time:e-c/2,x:t%i*r,y:Math.floor(t/i)*o}))}(this.option,this.duration)}creatCanvas(){return function(e){const{number:t,width:n,height:r,column:o}=e,i=document.createElement("canvas"),s=i.getContext("2d");return i.width=n*o,i.height=Math.ceil(t/o)*r+30,s.fillStyle="black",s.fillRect(0,0,i.width,i.height),s.font="14px Georgia",s.fillStyle="#fff",s.fillText(`From: https://artplayer.org/, Number: ${t}, Width: ${n}, Height: ${r}, Column: ${o}`,10,i.height-11),i}(this.option)}download(){if(n(this).closed)return this;this.errorHandle(this.file&&this.thumbnailUrl,"Download does not seem to be ready, please create preview first"),this.errorHandle(!this.processing,"There is currently a task in progress, please wait a moment...");const e=function(e,t){const n=document.createElement("a"),r=`${function(e){const t=e.split(".");return t.pop(),t.join(".")}(e.name)}.png`;n.download=r,n.href=t;try{document.body.appendChild(n),n.click()}finally{n.parentNode&&n.parentNode.removeChild(n)}return r}(this.file,this.thumbnailUrl);return this.emit("download",e),this}errorHandle(e,t){if(!e)throw this.emit("error",t),new Error(t)}destroy(){s(this)&&i([()=>p(this),()=>c(this),()=>this.emit("destroy")])}}return f}); diff --git a/docs/compiled/artplayer-tool-thumbnail.mjs b/docs/compiled/artplayer-tool-thumbnail.mjs index e5248b941..7fbe1017c 100644 --- a/docs/compiled/artplayer-tool-thumbnail.mjs +++ b/docs/compiled/artplayer-tool-thumbnail.mjs @@ -3,6 +3,33 @@ * Github: https://github.com/zhw2590582/ArtPlayer * (c) 2017-2026 Harvey Zhao * Released under the MIT License. + * + * ArtPlayer thumbnail tool bundled third-party notices + * + * tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class + * https://github.com/scottcorgan/tiny-emitter/tree/2.1.0 + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Corgan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ class Emitter { on(name, fn, ctx) { @@ -12,15 +39,17 @@ class Emitter { } once(name, fn, ctx) { const self = this; + const callback = fn; function listener(...args) { self.off(name, listener); - fn.apply(ctx, args); + callback.apply(ctx, args); } listener._ = fn; return this.on(name, listener, ctx); } emit(name, ...data) { - const evtArr = ((this.e || (this.e = {}))[name] || []).slice(); + const e = this.e || (this.e = {}); + const evtArr = (e[name] || []).slice(); for (let i = 0; i < evtArr.length; i += 1) { evtArr[i].fn.apply(evtArr[i].ctx, data); } diff --git a/package.json b/package.json index be52f4177..dc02047dc 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports", "test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js", "typecheck": "node scripts/typecheck.mjs", - "test:unit": "node --test 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.test.js test/vast-lifecycle.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:unit": "node --test 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.test.js test/vast-lifecycle.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:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs", "test": "yarn test:node && yarn test:baseline", "test:browser": "playwright test", @@ -83,7 +83,7 @@ "generate:media:hls": "node scripts/generate-hls-fixture.mjs", "test:iframe": "node --test test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe.test.js test/iframe-lifecycle.test.js refactor/scripts/iframe-contract.test.mjs refactor/scripts/iframe-types.test.mjs", "test:iframe-types-package": "node refactor/scripts/iframe-package-types.mjs", - "test:thumbnail": "node --test test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js refactor/scripts/thumbnail-contract.test.mjs" + "test:thumbnail": "node --test 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" }, "browserslist": "last 1 Chrome version", "devDependencies": { diff --git a/packages/artplayer-tool-thumbnail/ARCHITECTURE.md b/packages/artplayer-tool-thumbnail/ARCHITECTURE.md index 48a43b522..1c509a3c1 100644 --- a/packages/artplayer-tool-thumbnail/ARCHITECTURE.md +++ b/packages/artplayer-tool-thumbnail/ARCHITECTURE.md @@ -1,23 +1,25 @@ # Thumbnail tool maintenance map -The runtime responsibilities are separated under PKG-TOOL-THUMB-03. Strict TS and -published declaration work belongs to PKG-TOOL-THUMB-04. Runtime restructuring -does not imply complete migration or release readiness. +The runtime responsibilities were separated under PKG-TOOL-THUMB-03 and are now +strict TypeScript under PKG-TOOL-THUMB-04. Published declarations and installed +entrypoint checks are still in progress; runtime migration is not release readiness. | File | Responsibility | | --- | --- | -| src/index.js | Public class, historical method names, construction and destruction entry | -| src/lifecycle.js | Private state, source/input generations, cancellation and resource release | -| src/source.js | File loading, native metadata/error listeners and source/thumbnail Blob URLs | -| src/extraction.js | Owned metadata wait and serial frame job, callback/error completion and cancellation | -| src/input.js | Option validation/clamps, file-input wrapper creation, listener registration/replacement and release | -| src/sheet.js | Midpoint grid, canvas/footer geometry, temporary download anchor | -| src/emitter.js | Existing on/once/emit/off semantics; provenance review remains in 04 | -| src/utils.js | Existing clamp, filename, sleep and serial-promise helpers | +| src/index.ts | Public class, historical method names, construction and destruction entry | +| src/lifecycle.ts | Private state, source/input generations, cancellation and resource release | +| src/source.ts | File loading, native metadata/error listeners and source/thumbnail Blob URLs | +| src/extraction.ts | Owned metadata wait and serial frame job, callback/error completion and cancellation | +| src/input.ts | Option validation/clamps, file-input wrapper creation, listener registration/replacement and release | +| src/sheet.ts | Midpoint grid, canvas/footer geometry, temporary download anchor | +| src/emitter.ts | Typed local adaptation of tiny-emitter; preserves on/once/emit/off behavior | +| src/utils.ts | Pure clamp and filename helpers; unused sleep/serial helpers removed | +| src/types.ts | Internal option, frame, event tuple, job and lifecycle contracts; no runtime output | -The entry delegates to input/source/extraction/sheet; none imports the entry. +The entry delegates to input/source/extraction/sheet. Helpers import its type only; +the imports are erased and do not create a runtime dependency on the entry. Input/source use lifecycle state, extraction uses lifecycle/source, and sheet uses -only filename calculation. There is no dependency back from helpers to the class. +only filename calculation. types.ts contains no imports or executable state. Input records live in a private WeakMap, so callers replacing `option` cannot lose ownership of generated inputs/listeners. Normal construction preserves the existing instance-field order and bound inputChange/ondrop methods. DEFAULTS, @@ -76,6 +78,7 @@ Use Yarn and the repository scripts: ```sh yarn test:thumbnail +yarn typecheck yarn build artplayer-tool-thumbnail yarn test:browser test/browser/thumbnail-tool.spec.js test/browser/thumbnail-native.spec.js test/browser/thumbnail-input.spec.js ``` @@ -96,7 +99,42 @@ Safari/WebKit evidence in 05. The independent tool example is `test/thumbnail-lifecycle.test.js` covers jobs, source cancellation, native errors, callback failures, reentrancy and private resources. Browser tests hold real PNG callbacks and replace a selected file during encoding; only the latest job may -complete. Continue strict TS and installed declarations in 04; preserve old tests -and the default delay/height boundary. The old sleep/serial helpers in utils.js are -no longer used by extraction and can be removed during that migration. +complete. Continue published declarations and installed consumers in 04; preserve +old tests and the default delay/height boundary. See [task plan](../../refactor/plan.md) and [risk ledger](../../refactor/risks.json). + +## Type and provenance boundaries + +All eight executable source modules and the shared type module are checked with +strict, noUncheckedIndexedAccess, noImplicitOverride, skipLibCheck=false and no +ambient Node/test globals. Public fields use declare so TypeScript does not add +early undefined properties or change constructor property order. File/URL/density +fields retain their absence until the corresponding operation publishes them. +Unknown option fields remain unknown, and custom string/number/symbol events keep +an open payload boundary; built-in events have precise tuples. This source typing +is not yet the missing public npm declaration entrypoint. + +Assertions are limited to existing runtime boundaries: a temporary empty option +object before setup validation; the input/wrapper conversion; event target and +dataTransfer/files supplied by native input/drop events; private records inserted +before lookup; nonempty/dense frame arrays; a successful 2D canvas context; and +the old property read of message on arbitrary thrown values. These assertions +preserve existing runtime validation/errors rather than adding new coercions. +The constructor and destroy rollback paths also intentionally tolerate fields +that have not yet been assigned. DOM listener casts connect known event names to +the matching bound callbacks. Keep new assertions equally local and documented. + +The emitter structurally corresponds to tiny-emitter 2.1.0, which the recovered +3.5.31 manifest declared as ^2.1.0. The exact original copied revision is unknown. +The pinned [reference files](../../refactor/baselines/thumbnail-vendor/sources.json) +and [MIT notice](THIRD_PARTY_NOTICES) retain upstream attribution. The repository +build includes the complete notice in main, legacy and ESM bundle headers. +Final packed contents remain a distribution gate. Do not replace this local +emitter with the core emitter as part of a type-only change: dispatch semantics +and historical prototype-key handling need their own behavior review. + +`test/thumbnail-runtime.test.js` compares upstream, recovered/historical bundles +and the selected candidate for dispatch behavior, and checks candidate public +descriptors and validation. `test/thumbnail-vendor.test.js` verifies fixed source +bytes, complete notices and dist/docs equality. `test/types/thumbnail-runtime.ts` +checks source consumers with positive cases and six rejected invalid uses. diff --git a/packages/artplayer-tool-thumbnail/THIRD_PARTY_NOTICES b/packages/artplayer-tool-thumbnail/THIRD_PARTY_NOTICES new file mode 100644 index 000000000..acca209a6 --- /dev/null +++ b/packages/artplayer-tool-thumbnail/THIRD_PARTY_NOTICES @@ -0,0 +1,26 @@ +ArtPlayer thumbnail tool bundled third-party notices + +tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class +https://github.com/scottcorgan/tiny-emitter/tree/2.1.0 + +The MIT License (MIT) + +Copyright (c) 2017 Scott Corgan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.js b/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.js index 25c99402c..4bee91cde 100644 --- a/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.js +++ b/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.js @@ -3,5 +3,32 @@ * Github: https://github.com/zhw2590582/ArtPlayer * (c) 2017-2026 Harvey Zhao * Released under the MIT License. + * + * ArtPlayer thumbnail tool bundled third-party notices + * + * tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class + * https://github.com/scottcorgan/tiny-emitter/tree/2.1.0 + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Corgan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this;function o(...i){r.off(e,o),t.apply(n,i)}return o._=t,this.on(e,o,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;rn.pause?.(),()=>n.removeAttribute?.("src"),()=>n.load?.(),()=>n.parentNode?.removeChild(n));i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function a(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function l(e,t){let s=t.epoch;const c=e.video;let l,u,d,h,p=!1,f=!1,m=()=>{};const b=[],y=new Promise((e,t)=>{l=e,u=t});y.catch(()=>{});const g=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function v(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{v()}catch(r){t||(t=r)}if(n)try{e.emit("error",t?.message)}catch(o){t=o}u(t)}}function U(t){if(!g())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!g())return;if(r===t.points.length)return void function(){if(g())try{v(),e.emit("done"),l()}catch(t){let r=t;try{e.emit("error",t?.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let a=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!g()||a||c.seeking||void 0!==c.readyState&&c.readyState<2)){a=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!g())return;t.canvas.toBlob(s=>{if(g()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,g);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(l){w(l)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(g()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,l=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=a(i,0,n.duration),e.option.end=a(s||n.duration,i,n.duration),l(e.option.end>e.option.begin,"End time must be greater than the start time"),l(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,l(e.file&&n,"Please select the video file first"),l(!e.processing,"There is currently a task in progress, please wait a moment..."),l(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),g()||clearTimeout(d)}h={video:c,promise:y,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{g()&&w(new Error(`Unable to load video: media error ${c.error?.code||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!g())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),y;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}return y}const u=new WeakMap;function d(e){let t;for(const[r,o]of e.listeners.splice(0))try{e.input.removeEventListener(r,o)}catch(n){t||(t=n)}if(e.wrapper){try{e.input.parentNode&&e.input.parentNode.removeChild(e.input)}catch(n){t||(t=n)}"relative"===e.wrapper.style.position&&(e.wrapper.style.position=e.position)}if(t)throw t}function h(e,t){try{for(const[n,r]of t)e.listeners.push([n,r]),e.input.addEventListener(n,r)}catch(n){try{d(e)}catch{}throw n}}function p(e){const t=u.get(e);t&&(u.delete(e),d(t))}class f extends e{constructor(e={}){super(),this.processing=!1,this.option={};try{this.setup(Object.assign({},f.DEFAULTS,e)),this.video=f.creatVideo(),n(this).video=this.video,this.duration=0,this.inputChange=this.inputChange.bind(this),this.ondrop=this.ondrop.bind(this),function(e,t){const n=u.get(e);n.callbacks=[["change",e.inputChange],["dragover",t],["drop",e.ondrop]],h(n,n.callbacks)}(this,f.ondragover)}catch(t){s(this,"construction failed");try{p(this)}catch{}try{c(this)}catch{}throw t}}static get DEFAULTS(){return{number:60,width:160,height:90,column:10,begin:0,end:Number.NaN}}static ondragover(e){e.preventDefault()}ondrop(e){if(n(this).closed)return;e.preventDefault();const t=e.dataTransfer.files[0];this.loadVideo(t)}setup(e={}){return n(this).closed||function(e,t){const o=n(e),i=++o.inputEpoch,s=()=>!o.closed&&o.inputEpoch===i,c=Object.assign({},e.option,t),l=c.fileInput;e.errorHandle(l instanceof Element,"The 'fileInput' is not a Element");for(const n of["number","width","column","begin","end"])e.errorHandle("number"===typeof c[n],`The '${n}' is not a number`);if(c.number=a(c.number,10,1e3),c.width=a(c.width,10,1e3),c.column=a(c.column,1,1e3),!s())throw r("input setup superseded");const p=u.get(e);if(p&&(l===p.input||l===p.wrapper))return c.fileInput=p.input,e.option=c,c;const f={input:l,wrapper:null,position:"",listeners:[],callbacks:p?.callbacks||null};try{if("INPUT"===l.tagName&&"file"===l.type||(f.wrapper=l,f.position=l.style.position,f.input=document.createElement("input"),f.input.type="file",Object.assign(f.input.style,{position:"absolute",width:"100%",height:"100%",left:"0",top:"0",right:"0",bottom:"0",opacity:"0"}),l.style.position="relative",l.appendChild(f.input)),!s())throw r("input setup superseded");if(f.callbacks&&h(f,f.callbacks),!s())throw r("input setup superseded")}catch(m){try{d(f)}catch{}throw m}c.fileInput=f.input,u.set(e,f),e.option=c,p&&d(p)}(this,e),this}static creatVideo(){const e=document.createElement("video");e.style.position="absolute",e.style.top="-9999px",e.style.left="-9999px",e.muted=!0,e.controls=!0;try{document.body.appendChild(e)}catch(t){throw e.parentNode&&e.parentNode.removeChild(e),t}return e}inputChange(e){if(n(this).closed)return;const t=this.option.fileInput.files[0];this.loadVideo(t),e.target.value=""}loadVideo(e){!function(e,t){const r=n(e);if(!t||r.closed)return;const s=r.epoch,c=e.video,a=c.canPlayType(t.type);if(e.errorHandle("maybe"===a||"probably"===a,`Playback of this file format is not supported: ${t.type}`),r.closed||r.epoch!==s)return;const l=URL.createObjectURL(t);if(r.sourceUrls.add(l),r.closed||r.epoch!==s)return void o(r.sourceUrls,l);const u=++r.epoch;!r.sourceUrl&&!e.file&&r.job?.adopt(u)||r.job?.cancel("source changed"),r.loading=!0;const d=()=>!r.closed&&r.epoch===u,h=r.sourceListeners,p=[];r.sourceListeners=p;let f=()=>{},m=!1;const b=()=>{if(!d()||m||c.currentSrc&&c.currentSrc!==l)return;m=!0,r.loading=!1;const t=new Error(`Unable to load video: media error ${c.error?.code||0}`);r.job?r.job.fail(t):e.emit("error",t.message)},y=()=>{!d()||c.currentSrc&&c.currentSrc!==l||(r.loading=!1,r.job?.ready())};try{if(i(h.splice(0)),!d())return void o(r.sourceUrls,l);for(const[e,t]of[["error",b],["loadedmetadata",y]])if(f=()=>c.removeEventListener(e,t),p.push(f),c.addEventListener(e,t),!d())return i([f,...p.splice(0)]),void o(r.sourceUrls,l);if(e.videoUrl=l,e.file=t,e.emit("file",t),!d())return void o(r.sourceUrls,l);if(c.src=l,!d())return void o(r.sourceUrls,l);r.sourceUrl=l;for(const e of[...r.sourceUrls])e!==l&&o(r.sourceUrls,e);d()&&e.emit("video",c)}catch(g){d()&&(r.loading=!1,r.job?.cancel("source setup failed"));try{i([f,...p.splice(0)])}catch{}throw o(r.sourceUrls,l),g}}(this,e)}start(){return function(e){const t=n(e);if(t.closed){const e=Promise.reject(r("destroyed"));return e.catch(()=>{}),e}return e.errorHandle(!t.job,"There is currently a task in progress, please wait a moment..."),l(e,t)}(this)}creatScreenshotDate(){return function(e,t){const{number:n,width:r,height:o,column:i,begin:s}=e,c=t/n,a=[s+c];for(;a.length({time:e-c/2,x:t%i*r,y:Math.floor(t/i)*o}))}(this.option,this.duration)}creatCanvas(){return function(e){const{number:t,width:n,height:r,column:o}=e,i=document.createElement("canvas"),s=i.getContext("2d");return i.width=n*o,i.height=Math.ceil(t/o)*r+30,s.fillStyle="black",s.fillRect(0,0,i.width,i.height),s.font="14px Georgia",s.fillStyle="#fff",s.fillText(`From: https://artplayer.org/, Number: ${t}, Width: ${n}, Height: ${r}, Column: ${o}`,10,i.height-11),i}(this.option)}download(){if(n(this).closed)return this;this.errorHandle(this.file&&this.thumbnailUrl,"Download does not seem to be ready, please create preview first"),this.errorHandle(!this.processing,"There is currently a task in progress, please wait a moment...");const e=function(e,t){const n=document.createElement("a"),r=`${function(e){const t=e.split(".");return t.pop(),t.join(".")}(e.name)}.png`;n.download=r,n.href=t;try{document.body.appendChild(n),n.click()}finally{n.parentNode&&n.parentNode.removeChild(n)}return r}(this.file,this.thumbnailUrl);return this.emit("download",e),this}errorHandle(e,t){if(!e)throw this.emit("error",t),new Error(t)}destroy(){s(this)&&i([()=>p(this),()=>c(this),()=>this.emit("destroy")])}}return f}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this,o=t;function i(...t){r.off(e,i),o.apply(n,t)}return i._=t,this.on(e,i,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;rn.pause?.(),()=>n.removeAttribute?.("src"),()=>n.load?.(),()=>n.parentNode?.removeChild(n));i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function a(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function l(e,t){let s=t.epoch;const c=e.video;let l,u,d,h,p=!1,f=!1,m=()=>{};const b=[],y=new Promise((e,t)=>{l=e,u=t});y.catch(()=>{});const g=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function v(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{v()}catch(r){t||(t=r)}if(n)try{e.emit("error",t?.message)}catch(o){t=o}u(t)}}function U(t){if(!g())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!g())return;if(r===t.points.length)return void function(){if(g())try{v(),e.emit("done"),l()}catch(t){let r=t;try{e.emit("error",t?.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let a=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!g()||a||c.seeking||void 0!==c.readyState&&c.readyState<2)){a=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!g())return;t.canvas.toBlob(s=>{if(g()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,g);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(l){w(l)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(g()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,l=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=a(i,0,n.duration),e.option.end=a(s||n.duration,i,n.duration),l(e.option.end>e.option.begin,"End time must be greater than the start time"),l(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,l(e.file&&n,"Please select the video file first"),l(!e.processing,"There is currently a task in progress, please wait a moment..."),l(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),g()||clearTimeout(d)}h={video:c,promise:y,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{g()&&w(new Error(`Unable to load video: media error ${c.error?.code||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!g())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),y;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}return y}const u=new WeakMap;function d(e){let t;for(const[r,o]of e.listeners.splice(0))try{e.input.removeEventListener(r,o)}catch(n){t||(t=n)}if(e.wrapper){try{e.input.parentNode&&e.input.parentNode.removeChild(e.input)}catch(n){t||(t=n)}"relative"===e.wrapper.style.position&&(e.wrapper.style.position=e.position)}if(t)throw t}function h(e,t){try{for(const[n,r]of t)e.listeners.push([n,r]),e.input.addEventListener(n,r)}catch(n){try{d(e)}catch{}throw n}}function p(e){const t=u.get(e);t&&(u.delete(e),d(t))}class f extends e{constructor(e={}){super(),this.processing=!1,this.option={};try{this.setup(Object.assign({},f.DEFAULTS,e)),this.video=f.creatVideo(),n(this).video=this.video,this.duration=0,this.inputChange=this.inputChange.bind(this),this.ondrop=this.ondrop.bind(this),function(e,t){const n=u.get(e);n.callbacks=[["change",e.inputChange],["dragover",t],["drop",e.ondrop]],h(n,n.callbacks)}(this,f.ondragover)}catch(t){s(this,"construction failed");try{p(this)}catch{}try{c(this)}catch{}throw t}}static get DEFAULTS(){return{number:60,width:160,height:90,column:10,begin:0,end:Number.NaN}}static ondragover(e){e.preventDefault()}ondrop(e){if(n(this).closed)return;e.preventDefault();const t=e.dataTransfer.files[0];this.loadVideo(t)}setup(e={}){return n(this).closed||function(e,t){const o=n(e),i=++o.inputEpoch,s=()=>!o.closed&&o.inputEpoch===i,c=Object.assign({},e.option,t),l=c.fileInput;e.errorHandle(l instanceof Element,"The 'fileInput' is not a Element");for(const n of["number","width","column","begin","end"])e.errorHandle("number"===typeof c[n],`The '${n}' is not a number`);if(c.number=a(c.number,10,1e3),c.width=a(c.width,10,1e3),c.column=a(c.column,1,1e3),!s())throw r("input setup superseded");const p=u.get(e);if(p&&(l===p.input||l===p.wrapper))return c.fileInput=p.input,e.option=c,c;const f={input:l,wrapper:null,position:"",listeners:[],callbacks:p?.callbacks||null};try{if("INPUT"===l.tagName&&"file"===l.type||(f.wrapper=l,f.position=l.style.position,f.input=document.createElement("input"),f.input.type="file",Object.assign(f.input.style,{position:"absolute",width:"100%",height:"100%",left:"0",top:"0",right:"0",bottom:"0",opacity:"0"}),l.style.position="relative",l.appendChild(f.input)),!s())throw r("input setup superseded");if(f.callbacks&&h(f,f.callbacks),!s())throw r("input setup superseded")}catch(m){try{d(f)}catch{}throw m}c.fileInput=f.input,u.set(e,f),e.option=c,p&&d(p)}(this,e),this}static creatVideo(){const e=document.createElement("video");e.style.position="absolute",e.style.top="-9999px",e.style.left="-9999px",e.muted=!0,e.controls=!0;try{document.body.appendChild(e)}catch(t){throw e.parentNode&&e.parentNode.removeChild(e),t}return e}inputChange(e){if(n(this).closed)return;const t=this.option.fileInput.files[0];this.loadVideo(t),e.target.value=""}loadVideo(e){!function(e,t){const r=n(e);if(!t||r.closed)return;const s=r.epoch,c=e.video,a=c.canPlayType(t.type);if(e.errorHandle("maybe"===a||"probably"===a,`Playback of this file format is not supported: ${t.type}`),r.closed||r.epoch!==s)return;const l=URL.createObjectURL(t);if(r.sourceUrls.add(l),r.closed||r.epoch!==s)return void o(r.sourceUrls,l);const u=++r.epoch;!r.sourceUrl&&!e.file&&r.job?.adopt(u)||r.job?.cancel("source changed"),r.loading=!0;const d=()=>!r.closed&&r.epoch===u,h=r.sourceListeners,p=[];r.sourceListeners=p;let f=()=>{},m=!1;const b=()=>{if(!d()||m||c.currentSrc&&c.currentSrc!==l)return;m=!0,r.loading=!1;const t=new Error(`Unable to load video: media error ${c.error?.code||0}`);r.job?r.job.fail(t):e.emit("error",t.message)},y=()=>{!d()||c.currentSrc&&c.currentSrc!==l||(r.loading=!1,r.job?.ready())};try{if(i(h.splice(0)),!d())return void o(r.sourceUrls,l);for(const[e,t]of[["error",b],["loadedmetadata",y]])if(f=()=>c.removeEventListener(e,t),p.push(f),c.addEventListener(e,t),!d())return i([f,...p.splice(0)]),void o(r.sourceUrls,l);if(e.videoUrl=l,e.file=t,e.emit("file",t),!d())return void o(r.sourceUrls,l);if(c.src=l,!d())return void o(r.sourceUrls,l);r.sourceUrl=l;for(const e of[...r.sourceUrls])e!==l&&o(r.sourceUrls,e);d()&&e.emit("video",c)}catch(g){d()&&(r.loading=!1,r.job?.cancel("source setup failed"));try{i([f,...p.splice(0)])}catch{}throw o(r.sourceUrls,l),g}}(this,e)}start(){return function(e){const t=n(e);if(t.closed){const e=Promise.reject(r("destroyed"));return e.catch(()=>{}),e}return e.errorHandle(!t.job,"There is currently a task in progress, please wait a moment..."),l(e,t)}(this)}creatScreenshotDate(){return function(e,t){const{number:n,width:r,height:o,column:i,begin:s}=e,c=t/n,a=[s+c];for(;a.length({time:e-c/2,x:t%i*r,y:Math.floor(t/i)*o}))}(this.option,this.duration)}creatCanvas(){return function(e){const{number:t,width:n,height:r,column:o}=e,i=document.createElement("canvas"),s=i.getContext("2d");return i.width=n*o,i.height=Math.ceil(t/o)*r+30,s.fillStyle="black",s.fillRect(0,0,i.width,i.height),s.font="14px Georgia",s.fillStyle="#fff",s.fillText(`From: https://artplayer.org/, Number: ${t}, Width: ${n}, Height: ${r}, Column: ${o}`,10,i.height-11),i}(this.option)}download(){if(n(this).closed)return this;this.errorHandle(this.file&&this.thumbnailUrl,"Download does not seem to be ready, please create preview first"),this.errorHandle(!this.processing,"There is currently a task in progress, please wait a moment...");const e=function(e,t){const n=document.createElement("a"),r=`${function(e){const t=e.split(".");return t.pop(),t.join(".")}(e.name)}.png`;n.download=r,n.href=t;try{document.body.appendChild(n),n.click()}finally{n.parentNode&&n.parentNode.removeChild(n)}return r}(this.file,this.thumbnailUrl);return this.emit("download",e),this}errorHandle(e,t){if(!e)throw this.emit("error",t),new Error(t)}destroy(){s(this)&&i([()=>p(this),()=>c(this),()=>this.emit("destroy")])}}return f}); diff --git a/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.legacy.js b/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.legacy.js index 543a7a290..86ca050d8 100644 --- a/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.legacy.js +++ b/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.legacy.js @@ -3,5 +3,32 @@ * Github: https://github.com/zhw2590582/ArtPlayer * (c) 2017-2026 Harvey Zhao * Released under the MIT License. + * + * ArtPlayer thumbnail tool bundled third-party notices + * + * tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class + * https://github.com/scottcorgan/tiny-emitter/tree/2.1.0 + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Corgan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this;function o(...i){r.off(e,o),t.apply(n,i)}return o._=t,this.on(e,o,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r{var e;return null==(e=n.pause)?void 0:e.call(n)},()=>{var e;return null==(e=n.removeAttribute)?void 0:e.call(n,"src")},()=>{var e;return null==(e=n.load)?void 0:e.call(n)},()=>{var e;return null==(e=n.parentNode)?void 0:e.removeChild(n)});i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function l(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function a(e,t){let s=t.epoch;const c=e.video;let a,u,d,h,p=!1,f=!1,m=()=>{};const b=[],v=new Promise((e,t)=>{a=e,u=t});v.catch(()=>{});const y=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function g(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{g()}catch(r){t||(t=r)}if(n)try{e.emit("error",null==t?void 0:t.message)}catch(o){t=o}u(t)}}function U(t){if(!y())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!y())return;if(r===t.points.length)return void function(){if(y())try{g(),e.emit("done"),a()}catch(t){let r=t;try{e.emit("error",null==t?void 0:t.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let l=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!y()||l||c.seeking||void 0!==c.readyState&&c.readyState<2)){l=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!y())return;t.canvas.toBlob(s=>{if(y()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,y);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(a){w(a)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(y()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,a=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=l(i,0,n.duration),e.option.end=l(s||n.duration,i,n.duration),a(e.option.end>e.option.begin,"End time must be greater than the start time"),a(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,a(e.file&&n,"Please select the video file first"),a(!e.processing,"There is currently a task in progress, please wait a moment..."),a(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),y()||clearTimeout(d)}h={video:c,promise:v,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{var e;y()&&w(new Error(`Unable to load video: media error ${(null==(e=c.error)?void 0:e.code)||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!y())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),v;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}return v}const u=new WeakMap;function d(e){let t;for(const[r,o]of e.listeners.splice(0))try{e.input.removeEventListener(r,o)}catch(n){t||(t=n)}if(e.wrapper){try{e.input.parentNode&&e.input.parentNode.removeChild(e.input)}catch(n){t||(t=n)}"relative"===e.wrapper.style.position&&(e.wrapper.style.position=e.position)}if(t)throw t}function h(e,t){try{for(const[n,r]of t)e.listeners.push([n,r]),e.input.addEventListener(n,r)}catch(n){try{d(e)}catch(r){}throw n}}function p(e){const t=u.get(e);t&&(u.delete(e),d(t))}class f extends e{constructor(e={}){super(),this.processing=!1,this.option={};try{this.setup(Object.assign({},f.DEFAULTS,e)),this.video=f.creatVideo(),n(this).video=this.video,this.duration=0,this.inputChange=this.inputChange.bind(this),this.ondrop=this.ondrop.bind(this),function(e,t){const n=u.get(e);n.callbacks=[["change",e.inputChange],["dragover",t],["drop",e.ondrop]],h(n,n.callbacks)}(this,f.ondragover)}catch(t){s(this,"construction failed");try{p(this)}catch(r){}try{c(this)}catch(r){}throw t}}static get DEFAULTS(){return{number:60,width:160,height:90,column:10,begin:0,end:Number.NaN}}static ondragover(e){e.preventDefault()}ondrop(e){if(n(this).closed)return;e.preventDefault();const t=e.dataTransfer.files[0];this.loadVideo(t)}setup(e={}){return n(this).closed||function(e,t){const o=n(e),i=++o.inputEpoch,s=()=>!o.closed&&o.inputEpoch===i,c=Object.assign({},e.option,t),a=c.fileInput;e.errorHandle(a instanceof Element,"The 'fileInput' is not a Element");for(const n of["number","width","column","begin","end"])e.errorHandle("number"===typeof c[n],`The '${n}' is not a number`);if(c.number=l(c.number,10,1e3),c.width=l(c.width,10,1e3),c.column=l(c.column,1,1e3),!s())throw r("input setup superseded");const p=u.get(e);if(p&&(a===p.input||a===p.wrapper))return c.fileInput=p.input,e.option=c,c;const f={input:a,wrapper:null,position:"",listeners:[],callbacks:(null==p?void 0:p.callbacks)||null};try{if("INPUT"===a.tagName&&"file"===a.type||(f.wrapper=a,f.position=a.style.position,f.input=document.createElement("input"),f.input.type="file",Object.assign(f.input.style,{position:"absolute",width:"100%",height:"100%",left:"0",top:"0",right:"0",bottom:"0",opacity:"0"}),a.style.position="relative",a.appendChild(f.input)),!s())throw r("input setup superseded");if(f.callbacks&&h(f,f.callbacks),!s())throw r("input setup superseded")}catch(m){try{d(f)}catch(b){}throw m}c.fileInput=f.input,u.set(e,f),e.option=c,p&&d(p)}(this,e),this}static creatVideo(){const e=document.createElement("video");e.style.position="absolute",e.style.top="-9999px",e.style.left="-9999px",e.muted=!0,e.controls=!0;try{document.body.appendChild(e)}catch(t){throw e.parentNode&&e.parentNode.removeChild(e),t}return e}inputChange(e){if(n(this).closed)return;const t=this.option.fileInput.files[0];this.loadVideo(t),e.target.value=""}loadVideo(e){!function(e,t){var r,s,c;const l=n(e);if(!t||l.closed)return;const a=l.epoch,u=e.video,d=u.canPlayType(t.type);if(e.errorHandle("maybe"===d||"probably"===d,`Playback of this file format is not supported: ${t.type}`),l.closed||l.epoch!==a)return;const h=URL.createObjectURL(t);if(l.sourceUrls.add(h),l.closed||l.epoch!==a)return void o(l.sourceUrls,h);const p=++l.epoch;!l.sourceUrl&&!e.file&&(null==(r=l.job)?void 0:r.adopt(p))||null==(s=l.job)||s.cancel("source changed"),l.loading=!0;const f=()=>!l.closed&&l.epoch===p,m=l.sourceListeners,b=[];l.sourceListeners=b;let v=()=>{},y=!1;const g=()=>{var t;if(!f()||y||u.currentSrc&&u.currentSrc!==h)return;y=!0,l.loading=!1;const n=new Error(`Unable to load video: media error ${(null==(t=u.error)?void 0:t.code)||0}`);l.job?l.job.fail(n):e.emit("error",n.message)},w=()=>{var e;!f()||u.currentSrc&&u.currentSrc!==h||(l.loading=!1,null==(e=l.job)||e.ready())};try{if(i(m.splice(0)),!f())return void o(l.sourceUrls,h);for(const[e,t]of[["error",g],["loadedmetadata",w]])if(v=()=>u.removeEventListener(e,t),b.push(v),u.addEventListener(e,t),!f())return i([v,...b.splice(0)]),void o(l.sourceUrls,h);if(e.videoUrl=h,e.file=t,e.emit("file",t),!f())return void o(l.sourceUrls,h);if(u.src=h,!f())return void o(l.sourceUrls,h);l.sourceUrl=h;for(const e of[...l.sourceUrls])e!==h&&o(l.sourceUrls,e);f()&&e.emit("video",u)}catch(U){f()&&(l.loading=!1,null==(c=l.job)||c.cancel("source setup failed"));try{i([v,...b.splice(0)])}catch(E){}throw o(l.sourceUrls,h),U}}(this,e)}start(){return function(e){const t=n(e);if(t.closed){const e=Promise.reject(r("destroyed"));return e.catch(()=>{}),e}return e.errorHandle(!t.job,"There is currently a task in progress, please wait a moment..."),a(e,t)}(this)}creatScreenshotDate(){return function(e,t){const{number:n,width:r,height:o,column:i,begin:s}=e,c=t/n,l=[s+c];for(;l.length({time:e-c/2,x:t%i*r,y:Math.floor(t/i)*o}))}(this.option,this.duration)}creatCanvas(){return function(e){const{number:t,width:n,height:r,column:o}=e,i=document.createElement("canvas"),s=i.getContext("2d");return i.width=n*o,i.height=Math.ceil(t/o)*r+30,s.fillStyle="black",s.fillRect(0,0,i.width,i.height),s.font="14px Georgia",s.fillStyle="#fff",s.fillText(`From: https://artplayer.org/, Number: ${t}, Width: ${n}, Height: ${r}, Column: ${o}`,10,i.height-11),i}(this.option)}download(){if(n(this).closed)return this;this.errorHandle(this.file&&this.thumbnailUrl,"Download does not seem to be ready, please create preview first"),this.errorHandle(!this.processing,"There is currently a task in progress, please wait a moment...");const e=function(e,t){const n=document.createElement("a"),r=`${function(e){const t=e.split(".");return t.pop(),t.join(".")}(e.name)}.png`;n.download=r,n.href=t;try{document.body.appendChild(n),n.click()}finally{n.parentNode&&n.parentNode.removeChild(n)}return r}(this.file,this.thumbnailUrl);return this.emit("download",e),this}errorHandle(e,t){if(!e)throw this.emit("error",t),new Error(t)}destroy(){s(this)&&i([()=>p(this),()=>c(this),()=>this.emit("destroy")])}}return f}); +!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.ArtplayerToolThumbnail=t(),define(function(){return e.ArtplayerToolThumbnail})):(e="undefined"!==typeof globalThis?globalThis:e||self).ArtplayerToolThumbnail=t()}(this,function(){"use strict";class e{on(e,t,n){const r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this}once(e,t,n){const r=this,o=t;function i(...t){r.off(e,i),o.apply(n,t)}return i._=t,this.on(e,i,n)}emit(e,...t){const n=((this.e||(this.e={}))[e]||[]).slice();for(let r=0;r{var e;return null==(e=n.pause)?void 0:e.call(n)},()=>{var e;return null==(e=n.removeAttribute)?void 0:e.call(n,"src")},()=>{var e;return null==(e=n.load)?void 0:e.call(n)},()=>{var e;return null==(e=n.parentNode)?void 0:e.removeChild(n)});i([...t.sourceListeners.splice(0),...c,...[...r].map(e=>()=>o(t.sourceUrls,e)),...[...s].map(e=>()=>o(t.thumbnailUrls,e))])}function l(e,t,n){return Math.max(Math.min(e,Math.max(t,n)),Math.min(t,n))}function a(e,t){let s=t.epoch;const c=e.video;let a,u,d,h,p=!1,f=!1,m=()=>{};const b=[],v=new Promise((e,t)=>{a=e,u=t});v.catch(()=>{});const y=()=>!p&&!t.closed&&t.epoch===s&&t.job===h;function g(){return!p&&(p=!0,t.job===h&&(t.job=null,e.processing=!1),i([()=>clearTimeout(d),m,...b.splice(0)]),!0)}function w(t,n=!0){if(!p){try{g()}catch(r){t||(t=r)}if(n)try{e.emit("error",null==t?void 0:t.message)}catch(o){t=o}u(t)}}function U(t){if(!y())return;f=!0,e.processing=!0;let r=0;const i=()=>{if(!y())return;if(r===t.points.length)return void function(){if(y())try{g(),e.emit("done"),a()}catch(t){let r=t;try{e.emit("error",null==t?void 0:t.message)}catch(n){r=n}u(r)}}();const s=t.points[r];let l=!1,d=!1;const h=c.oncanplay,p=()=>{if(!(!y()||l||c.seeking||void 0!==c.readyState&&c.readyState<2)){l=!0;try{if(m(),t.context.drawImage(c,s.x,s.y,t.width,t.height),!y())return;t.canvas.toBlob(s=>{if(y()&&!d){d=!0;try{if(!s)throw new Error("Unable to create thumbnail image");const c=function(e,t,r){const i=n(e),s=URL.createObjectURL(t);if(i.thumbnailUrls.add(s),!r())return o(i.thumbnailUrls,s),null;const c=new Set([...i.thumbnailUrls,e.thumbnailUrl]);i.thumbnailUrl=s,e.thumbnailUrl=s;for(const n of c)n&&n!==s&&o(i.thumbnailUrls,n);return r()?s:null}(e,s,y);if(!c)return;e.emit("update",c,(r+1)/t.number),r++,Promise.resolve().then(i)}catch(c){w(c)}}})}catch(a){w(a)}}};m=()=>{c.oncanplay===p&&(c.oncanplay=h),c.removeEventListener("seeked",p)};try{c.oncanplay=p,c.addEventListener("seeked",p),c.currentTime=s.time,c.readyState>=2&&!c.seeking&&Promise.resolve().then(p)}catch(f){w(f)}};Promise.resolve().then(i)}function E(n=!1){if(y()&&!f)if(clearTimeout(d),!t.loading&&c.duration){f=!0;try{const t=function(e,t){const{video:n}=t,{width:r,number:o,begin:i,end:s}=e.option,c=n.videoHeight/n.videoWidth*r,a=(n,r)=>{try{e.errorHandle(n,r)}catch(o){throw t.reported=o,t.wasReported=!0,o}};e.option.height=c,e.option.begin=l(i,0,n.duration),e.option.end=l(s||n.duration,i,n.duration),a(e.option.end>e.option.begin,"End time must be greater than the start time"),a(Number.isFinite(n.duration),"Video duration must be finite"),e.duration=e.option.end-e.option.begin,e.density=o/e.duration,a(e.file&&n,"Please select the video file first"),a(!e.processing,"There is currently a task in progress, please wait a moment..."),a(e.density<=1,`The preview density cannot be greater than 1, but got ${e.density}`);const u=e.creatScreenshotDate(),d=e.creatCanvas(),h=d.getContext("2d");return e.emit("canvas",d),{width:r,height:c,number:o,points:u,canvas:d,context:h}}(e,h);U(t)}catch(r){if(w(r,!n&&!(h.wasReported&&r===h.reported)),n)throw r}}else d=setTimeout(()=>E(),1e3),y()||clearTimeout(d)}h={video:c,promise:v,ready:E,fail:w,cancel(e){w(r(e),!1)},adopt:t=>!p&&!f&&c===e.video&&(s=t,!0)},t.job=h;try{for(const[e,t]of[["error",()=>{var e;y()&&w(new Error(`Unable to load video: media error ${(null==(e=c.error)?void 0:e.code)||0}`))}],["loadedmetadata",()=>E()],["durationchange",()=>E()]])if(b.push(()=>c.removeEventListener(e,t)),c.addEventListener(e,t),!y())return i([()=>c.removeEventListener(e,t),...b.splice(0)]),v;c.error?w(new Error(`Unable to load video: media error ${c.error.code}`)):E(!0)}catch(j){throw w(j,!1),j}return v}const u=new WeakMap;function d(e){let t;for(const[r,o]of e.listeners.splice(0))try{e.input.removeEventListener(r,o)}catch(n){t||(t=n)}if(e.wrapper){try{e.input.parentNode&&e.input.parentNode.removeChild(e.input)}catch(n){t||(t=n)}"relative"===e.wrapper.style.position&&(e.wrapper.style.position=e.position)}if(t)throw t}function h(e,t){try{for(const[n,r]of t)e.listeners.push([n,r]),e.input.addEventListener(n,r)}catch(n){try{d(e)}catch(r){}throw n}}function p(e){const t=u.get(e);t&&(u.delete(e),d(t))}class f extends e{constructor(e={}){super(),this.processing=!1,this.option={};try{this.setup(Object.assign({},f.DEFAULTS,e)),this.video=f.creatVideo(),n(this).video=this.video,this.duration=0,this.inputChange=this.inputChange.bind(this),this.ondrop=this.ondrop.bind(this),function(e,t){const n=u.get(e);n.callbacks=[["change",e.inputChange],["dragover",t],["drop",e.ondrop]],h(n,n.callbacks)}(this,f.ondragover)}catch(t){s(this,"construction failed");try{p(this)}catch(r){}try{c(this)}catch(r){}throw t}}static get DEFAULTS(){return{number:60,width:160,height:90,column:10,begin:0,end:Number.NaN}}static ondragover(e){e.preventDefault()}ondrop(e){if(n(this).closed)return;e.preventDefault();const t=e.dataTransfer.files[0];this.loadVideo(t)}setup(e={}){return n(this).closed||function(e,t){const o=n(e),i=++o.inputEpoch,s=()=>!o.closed&&o.inputEpoch===i,c=Object.assign({},e.option,t),a=c.fileInput;e.errorHandle(a instanceof Element,"The 'fileInput' is not a Element");for(const n of["number","width","column","begin","end"])e.errorHandle("number"===typeof c[n],`The '${n}' is not a number`);if(c.number=l(c.number,10,1e3),c.width=l(c.width,10,1e3),c.column=l(c.column,1,1e3),!s())throw r("input setup superseded");const p=u.get(e);if(p&&(a===p.input||a===p.wrapper))return c.fileInput=p.input,e.option=c,c;const f={input:a,wrapper:null,position:"",listeners:[],callbacks:(null==p?void 0:p.callbacks)||null};try{if("INPUT"===a.tagName&&"file"===a.type||(f.wrapper=a,f.position=a.style.position,f.input=document.createElement("input"),f.input.type="file",Object.assign(f.input.style,{position:"absolute",width:"100%",height:"100%",left:"0",top:"0",right:"0",bottom:"0",opacity:"0"}),a.style.position="relative",a.appendChild(f.input)),!s())throw r("input setup superseded");if(f.callbacks&&h(f,f.callbacks),!s())throw r("input setup superseded")}catch(m){try{d(f)}catch(b){}throw m}c.fileInput=f.input,u.set(e,f),e.option=c,p&&d(p)}(this,e),this}static creatVideo(){const e=document.createElement("video");e.style.position="absolute",e.style.top="-9999px",e.style.left="-9999px",e.muted=!0,e.controls=!0;try{document.body.appendChild(e)}catch(t){throw e.parentNode&&e.parentNode.removeChild(e),t}return e}inputChange(e){if(n(this).closed)return;const t=this.option.fileInput.files[0];this.loadVideo(t),e.target.value=""}loadVideo(e){!function(e,t){var r,s,c;const l=n(e);if(!t||l.closed)return;const a=l.epoch,u=e.video,d=u.canPlayType(t.type);if(e.errorHandle("maybe"===d||"probably"===d,`Playback of this file format is not supported: ${t.type}`),l.closed||l.epoch!==a)return;const h=URL.createObjectURL(t);if(l.sourceUrls.add(h),l.closed||l.epoch!==a)return void o(l.sourceUrls,h);const p=++l.epoch;!l.sourceUrl&&!e.file&&(null==(r=l.job)?void 0:r.adopt(p))||null==(s=l.job)||s.cancel("source changed"),l.loading=!0;const f=()=>!l.closed&&l.epoch===p,m=l.sourceListeners,b=[];l.sourceListeners=b;let v=()=>{},y=!1;const g=()=>{var t;if(!f()||y||u.currentSrc&&u.currentSrc!==h)return;y=!0,l.loading=!1;const n=new Error(`Unable to load video: media error ${(null==(t=u.error)?void 0:t.code)||0}`);l.job?l.job.fail(n):e.emit("error",n.message)},w=()=>{var e;!f()||u.currentSrc&&u.currentSrc!==h||(l.loading=!1,null==(e=l.job)||e.ready())};try{if(i(m.splice(0)),!f())return void o(l.sourceUrls,h);for(const[e,t]of[["error",g],["loadedmetadata",w]])if(v=()=>u.removeEventListener(e,t),b.push(v),u.addEventListener(e,t),!f())return i([v,...b.splice(0)]),void o(l.sourceUrls,h);if(e.videoUrl=h,e.file=t,e.emit("file",t),!f())return void o(l.sourceUrls,h);if(u.src=h,!f())return void o(l.sourceUrls,h);l.sourceUrl=h;for(const e of[...l.sourceUrls])e!==h&&o(l.sourceUrls,e);f()&&e.emit("video",u)}catch(U){f()&&(l.loading=!1,null==(c=l.job)||c.cancel("source setup failed"));try{i([v,...b.splice(0)])}catch(E){}throw o(l.sourceUrls,h),U}}(this,e)}start(){return function(e){const t=n(e);if(t.closed){const e=Promise.reject(r("destroyed"));return e.catch(()=>{}),e}return e.errorHandle(!t.job,"There is currently a task in progress, please wait a moment..."),a(e,t)}(this)}creatScreenshotDate(){return function(e,t){const{number:n,width:r,height:o,column:i,begin:s}=e,c=t/n,l=[s+c];for(;l.length({time:e-c/2,x:t%i*r,y:Math.floor(t/i)*o}))}(this.option,this.duration)}creatCanvas(){return function(e){const{number:t,width:n,height:r,column:o}=e,i=document.createElement("canvas"),s=i.getContext("2d");return i.width=n*o,i.height=Math.ceil(t/o)*r+30,s.fillStyle="black",s.fillRect(0,0,i.width,i.height),s.font="14px Georgia",s.fillStyle="#fff",s.fillText(`From: https://artplayer.org/, Number: ${t}, Width: ${n}, Height: ${r}, Column: ${o}`,10,i.height-11),i}(this.option)}download(){if(n(this).closed)return this;this.errorHandle(this.file&&this.thumbnailUrl,"Download does not seem to be ready, please create preview first"),this.errorHandle(!this.processing,"There is currently a task in progress, please wait a moment...");const e=function(e,t){const n=document.createElement("a"),r=`${function(e){const t=e.split(".");return t.pop(),t.join(".")}(e.name)}.png`;n.download=r,n.href=t;try{document.body.appendChild(n),n.click()}finally{n.parentNode&&n.parentNode.removeChild(n)}return r}(this.file,this.thumbnailUrl);return this.emit("download",e),this}errorHandle(e,t){if(!e)throw this.emit("error",t),new Error(t)}destroy(){s(this)&&i([()=>p(this),()=>c(this),()=>this.emit("destroy")])}}return f}); diff --git a/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.mjs b/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.mjs index e5248b941..7fbe1017c 100644 --- a/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.mjs +++ b/packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.mjs @@ -3,6 +3,33 @@ * Github: https://github.com/zhw2590582/ArtPlayer * (c) 2017-2026 Harvey Zhao * Released under the MIT License. + * + * ArtPlayer thumbnail tool bundled third-party notices + * + * tiny-emitter - reference 2.1.0, locally adapted to a TypeScript class + * https://github.com/scottcorgan/tiny-emitter/tree/2.1.0 + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Corgan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ class Emitter { on(name, fn, ctx) { @@ -12,15 +39,17 @@ class Emitter { } once(name, fn, ctx) { const self = this; + const callback = fn; function listener(...args) { self.off(name, listener); - fn.apply(ctx, args); + callback.apply(ctx, args); } listener._ = fn; return this.on(name, listener, ctx); } emit(name, ...data) { - const evtArr = ((this.e || (this.e = {}))[name] || []).slice(); + const e = this.e || (this.e = {}); + const evtArr = (e[name] || []).slice(); for (let i = 0; i < evtArr.length; i += 1) { evtArr[i].fn.apply(evtArr[i].ctx, data); } diff --git a/packages/artplayer-tool-thumbnail/src/emitter.js b/packages/artplayer-tool-thumbnail/src/emitter.js deleted file mode 100644 index 988662583..000000000 --- a/packages/artplayer-tool-thumbnail/src/emitter.js +++ /dev/null @@ -1,44 +0,0 @@ -export default class Emitter { - on(name, fn, ctx) { - const e = this.e || (this.e = {}); - (e[name] || (e[name] = [])).push({ fn, ctx }) - return this - } - - once(name, fn, ctx) { - const self = this - function listener(...args) { - self.off(name, listener) - fn.apply(ctx, args) - } - listener._ = fn - return this.on(name, listener, ctx) - } - - emit(name, ...data) { - const evtArr = ((this.e || (this.e = {}))[name] || []).slice() - for (let i = 0; i < evtArr.length; i += 1) { - evtArr[i].fn.apply(evtArr[i].ctx, data) - } - return this - } - - off(name, callback) { - const e = this.e || (this.e = {}) - const evts = e[name] - const liveEvents = [] - if (evts && callback) { - for (let i = 0, len = evts.length; i < len; i += 1) { - if (evts[i].fn !== callback && evts[i].fn._ !== callback) - liveEvents.push(evts[i]) - } - } - if (liveEvents.length) { - e[name] = liveEvents - } - else { - delete e[name] - } - return this - } -} diff --git a/packages/artplayer-tool-thumbnail/src/emitter.ts b/packages/artplayer-tool-thumbnail/src/emitter.ts new file mode 100644 index 000000000..fb8c7cfef --- /dev/null +++ b/packages/artplayer-tool-thumbnail/src/emitter.ts @@ -0,0 +1,58 @@ +// Locally adapted from tiny-emitter; upstream reference 2.1.0. See THIRD_PARTY_NOTICES. +type EventMap = { [Name in keyof Events]: readonly unknown[] } +type Listener = ((...args: Args) => unknown) & { _?: (...args: Args) => unknown } +interface Registration { + fn: Listener + ctx: unknown +} +type Registry> = { [Name in keyof Events]?: Registration<[...Events[Name]]>[] } + +export default class Emitter = Record> { + declare e?: Registry + + on(name: Name, fn: (this: Context, ...args: [...Events[Name]]) => unknown, ctx?: Context): this { + const e: Registry = this.e || (this.e = {}); + (e[name] || (e[name] = [])).push({ fn, ctx }) + return this + } + + once(name: Name, fn: (this: Context, ...args: [...Events[Name]]) => unknown, ctx?: Context): this { + // eslint-disable-next-line ts/no-this-alias -- Preserve the owning emitter independently of callback ctx. + const self = this + const callback: Listener<[...Events[Name]]> = fn + function listener(...args: [...Events[Name]]) { + self.off(name, listener) + callback.apply(ctx, args) + } + listener._ = fn + return this.on(name, listener, ctx) + } + + emit(name: Name, ...data: [...Events[Name]]): this { + const e: Registry = this.e || (this.e = {}) + const evtArr = (e[name] || []).slice() + for (let i = 0; i < evtArr.length; i += 1) { + evtArr[i]!.fn.apply(evtArr[i]!.ctx, data) + } + return this + } + + off(name: Name, callback?: Listener<[...Events[Name]]>): this { + const e: Registry = this.e || (this.e = {}) + const evts = e[name] + const liveEvents: Registration<[...Events[Name]]>[] = [] + if (evts && callback) { + for (let i = 0, len = evts.length; i < len; i += 1) { + if (evts[i]!.fn !== callback && evts[i]!.fn._ !== callback) + liveEvents.push(evts[i]!) + } + } + if (liveEvents.length) { + e[name] = liveEvents + } + else { + delete e[name] + } + return this + } +} diff --git a/packages/artplayer-tool-thumbnail/src/extraction.js b/packages/artplayer-tool-thumbnail/src/extraction.ts similarity index 85% rename from packages/artplayer-tool-thumbnail/src/extraction.js rename to packages/artplayer-tool-thumbnail/src/extraction.ts index 8f3d2ddb4..4d8c09c86 100644 --- a/packages/artplayer-tool-thumbnail/src/extraction.js +++ b/packages/artplayer-tool-thumbnail/src/extraction.ts @@ -1,12 +1,14 @@ +import type ArtplayerToolThumbnail from './index' +import type { Cleanup, ExtractionJob, LifecycleState } from './types' import { cancellation, cleanupAll, stateFor } from './lifecycle' import { replaceThumbnail } from './source' import { clamp } from './utils' -function prepare(tool, job) { +function prepare(tool: ArtplayerToolThumbnail, job: ExtractionJob) { const { video } = job const { width, number, begin, end } = tool.option const height = (video.videoHeight / video.videoWidth) * width - const guard = (condition, message) => { + const guard = (condition: unknown, message: string) => { try { tool.errorHandle(condition, message) } @@ -28,23 +30,23 @@ function prepare(tool, job) { guard(tool.density <= 1, `The preview density cannot be greater than 1, but got ${tool.density}`) const points = tool.creatScreenshotDate() const canvas = tool.creatCanvas() - const context = canvas.getContext('2d') + const context = canvas.getContext('2d')! tool.emit('canvas', canvas) return { width, height, number, points, canvas, context } } -function createJob(tool, state) { +function createJob(tool: ArtplayerToolThumbnail, state: LifecycleState) { let epoch = state.epoch const video = tool.video - let resolve - let reject - let timer + let resolve!: () => void + let reject!: (error: unknown) => void + let timer: ReturnType | undefined let settled = false let running = false - let job + let job: ExtractionJob let frameCleanup = () => {} - const listeners = [] - const promise = new Promise((yes, no) => { + const listeners: Cleanup[] = [] + const promise = new Promise((yes, no) => { resolve = yes reject = no }) @@ -62,7 +64,7 @@ function createJob(tool, state) { cleanupAll([() => clearTimeout(timer), frameCleanup, ...listeners.splice(0)]) return true } - function fail(error, report = true) { + function fail(error: unknown, report = true) { if (settled) return try { @@ -73,7 +75,7 @@ function createJob(tool, state) { } if (report) { try { - tool.emit('error', error?.message) + tool.emit('error', (error as { message?: string } | null | undefined)?.message) } catch (listenerError) { error = listenerError @@ -92,7 +94,7 @@ function createJob(tool, state) { catch (error) { let failure = error try { - tool.emit('error', error?.message) + tool.emit('error', (error as { message?: string } | null | undefined)?.message) } catch (listenerError) { failure = listenerError @@ -100,7 +102,7 @@ function createJob(tool, state) { reject(failure) } } - function extract(plan) { + function extract(plan: ReturnType) { if (!live()) return running = true @@ -113,7 +115,7 @@ function createJob(tool, state) { complete() return } - const point = plan.points[index] + const point = plan.points[index]! let drawing = false let encoded = false const previous = video.oncanplay @@ -211,7 +213,7 @@ function createJob(tool, state) { }], ['loadedmetadata', () => ready()], ['durationchange', () => ready()], - ]) { + ] as const) { listeners.push(() => video.removeEventListener(name, callback)) video.addEventListener(name, callback) if (!live()) { @@ -231,7 +233,7 @@ function createJob(tool, state) { return promise } -export function startExtraction(tool) { +export function startExtraction(tool: ArtplayerToolThumbnail): Promise { const state = stateFor(tool) if (state.closed) { const promise = Promise.reject(cancellation('destroyed')) diff --git a/packages/artplayer-tool-thumbnail/src/index.js b/packages/artplayer-tool-thumbnail/src/index.ts similarity index 72% rename from packages/artplayer-tool-thumbnail/src/index.js rename to packages/artplayer-tool-thumbnail/src/index.ts index 09b0a8bb4..156814c12 100644 --- a/packages/artplayer-tool-thumbnail/src/index.js +++ b/packages/artplayer-tool-thumbnail/src/index.ts @@ -1,3 +1,4 @@ +import type { InputOptions, RuntimeOptions, ThumbnailEvents } from './types' import Emitter from './emitter' import { startExtraction } from './extraction' import { connectInput, releaseInput, setupInput } from './input' @@ -5,11 +6,21 @@ import { cleanupAll, closeState, releaseMedia, stateFor } from './lifecycle' import { createSheet, downloadSheet, screenshotPoints } from './sheet' import { loadSource } from './source' -export default class ArtplayerToolThumbnail extends Emitter { - constructor(option = {}) { +export default class ArtplayerToolThumbnail extends Emitter { + declare processing: boolean + declare option: RuntimeOptions + declare video: HTMLVideoElement + declare duration: number + declare density: number | undefined + declare file: File | undefined + declare videoUrl: string | undefined + declare thumbnailUrl: string | undefined + + constructor(option: InputOptions = {}) { super() this.processing = false - this.option = {} + // setup validates the temporary empty options before construction can succeed. + this.option = {} as RuntimeOptions try { this.setup(Object.assign({}, ArtplayerToolThumbnail.DEFAULTS, option)) this.video = ArtplayerToolThumbnail.creatVideo() @@ -44,19 +55,19 @@ export default class ArtplayerToolThumbnail extends Emitter { } } - static ondragover(event) { + static ondragover(event: DragEvent) { event.preventDefault() } - ondrop(event) { + ondrop(event: DragEvent) { if (stateFor(this).closed) return event.preventDefault() - const file = event.dataTransfer.files[0] + const file = event.dataTransfer!.files[0] this.loadVideo(file) } - setup(option = {}) { + setup(option: InputOptions = {}) { if (!stateFor(this).closed) setupInput(this, option) return this @@ -80,15 +91,15 @@ export default class ArtplayerToolThumbnail extends Emitter { return video } - inputChange(event) { + inputChange(event: Event) { if (stateFor(this).closed) return - const file = this.option.fileInput.files[0] + const file = this.option.fileInput.files![0] this.loadVideo(file) - event.target.value = '' + ;(event.target as HTMLInputElement).value = '' } - loadVideo(file) { + loadVideo(file?: File | null) { loadSource(this, file) } @@ -112,12 +123,12 @@ export default class ArtplayerToolThumbnail extends Emitter { 'Download does not seem to be ready, please create preview first', ) this.errorHandle(!this.processing, 'There is currently a task in progress, please wait a moment...') - const name = downloadSheet(this.file, this.thumbnailUrl) + const name = downloadSheet(this.file!, this.thumbnailUrl!) this.emit('download', name) return this } - errorHandle(condition, msg) { + errorHandle(condition: unknown, msg: string) { if (!condition) { this.emit('error', msg) throw new Error(msg) diff --git a/packages/artplayer-tool-thumbnail/src/input.js b/packages/artplayer-tool-thumbnail/src/input.ts similarity index 68% rename from packages/artplayer-tool-thumbnail/src/input.js rename to packages/artplayer-tool-thumbnail/src/input.ts index 2c6ac66de..adf7b707b 100644 --- a/packages/artplayer-tool-thumbnail/src/input.js +++ b/packages/artplayer-tool-thumbnail/src/input.ts @@ -1,10 +1,21 @@ +import type ArtplayerToolThumbnail from './index' +import type { InputOptions } from './types' import { cancellation, stateFor } from './lifecycle' import { clamp } from './utils' -const inputs = new WeakMap() +type Registration = readonly [name: string, callback: EventListener] +interface InputRecord { + input: HTMLInputElement + wrapper: HTMLElement | null + position: string + listeners: Registration[] + callbacks: Registration[] | null +} -function release(record) { - let failure +const inputs = new WeakMap() + +function release(record: InputRecord) { + let failure: unknown for (const [name, callback] of record.listeners.splice(0)) { try { record.input.removeEventListener(name, callback) @@ -28,7 +39,7 @@ function release(record) { throw failure } -function connect(record, callbacks) { +function connect(record: InputRecord, callbacks: Registration[]) { try { for (const [name, callback] of callbacks) { record.listeners.push([name, callback]) @@ -44,12 +55,12 @@ function connect(record, callbacks) { } } -export function setupInput(tool, patch) { +export function setupInput(tool: ArtplayerToolThumbnail, patch: InputOptions) { const state = stateFor(tool) const epoch = ++state.inputEpoch const current = () => !state.closed && state.inputEpoch === epoch const option = Object.assign({}, tool.option, patch) - const target = option.fileInput + const target = option.fileInput! tool.errorHandle(target instanceof Element, 'The \'fileInput\' is not a Element') for (const name of ['number', 'width', 'column', 'begin', 'end']) tool.errorHandle(typeof option[name] === 'number', `The '${name}' is not a number`) @@ -65,9 +76,10 @@ export function setupInput(tool, patch) { tool.option = option return option } - const record = { input: target, wrapper: null, position: '', listeners: [], callbacks: previous?.callbacks || null } + // A wrapper is replaced by its owned file input before the record is committed. + const record: InputRecord = { input: target as HTMLInputElement, wrapper: null, position: '', listeners: [], callbacks: previous?.callbacks || null } try { - if (!(target.tagName === 'INPUT' && target.type === 'file')) { + if (!(target.tagName === 'INPUT' && (target as HTMLInputElement).type === 'file')) { record.wrapper = target record.position = target.style.position record.input = document.createElement('input') @@ -98,13 +110,14 @@ export function setupInput(tool, patch) { return option } -export function connectInput(tool, dragover) { - const record = inputs.get(tool) - record.callbacks = [['change', tool.inputChange], ['dragover', dragover], ['drop', tool.ondrop]] +export function connectInput(tool: ArtplayerToolThumbnail, dragover: (event: DragEvent) => void) { + const record = inputs.get(tool)! + // These callbacks are registered only for their matching DOM event names. + record.callbacks = [['change', tool.inputChange], ['dragover', dragover as EventListener], ['drop', tool.ondrop as EventListener]] connect(record, record.callbacks) } -export function releaseInput(tool) { +export function releaseInput(tool: ArtplayerToolThumbnail) { const record = inputs.get(tool) if (!record) return diff --git a/packages/artplayer-tool-thumbnail/src/lifecycle.js b/packages/artplayer-tool-thumbnail/src/lifecycle.ts similarity index 69% rename from packages/artplayer-tool-thumbnail/src/lifecycle.js rename to packages/artplayer-tool-thumbnail/src/lifecycle.ts index 40c648e91..a3d39e2da 100644 --- a/packages/artplayer-tool-thumbnail/src/lifecycle.js +++ b/packages/artplayer-tool-thumbnail/src/lifecycle.ts @@ -1,6 +1,9 @@ -const states = new WeakMap() +import type ArtplayerToolThumbnail from './index' +import type { Cleanup, LifecycleState } from './types' -export function stateFor(tool) { +const states = new WeakMap() + +export function stateFor(tool: ArtplayerToolThumbnail): LifecycleState { if (!states.has(tool)) { states.set(tool, { closed: false, @@ -16,24 +19,24 @@ export function stateFor(tool) { loading: false, }) } - return states.get(tool) + return states.get(tool)! } -export function cancellation(reason) { +export function cancellation(reason: string) { const error = new Error(`Thumbnail task cancelled: ${reason}`) error.name = 'AbortError' return error } -export function revoke(urls, url) { +export function revoke(urls: Set, url: string | undefined) { if (!url) return urls.delete(url) URL.revokeObjectURL(url) } -export function cleanupAll(callbacks) { - let failure +export function cleanupAll(callbacks: Cleanup[]) { + let failure: unknown for (const callback of callbacks) { try { callback() @@ -46,7 +49,7 @@ export function cleanupAll(callbacks) { throw failure } -export function closeState(tool, reason = 'destroyed') { +export function closeState(tool: ArtplayerToolThumbnail, reason = 'destroyed') { const state = stateFor(tool) if (state.closed) return false @@ -56,11 +59,11 @@ export function closeState(tool, reason = 'destroyed') { return true } -export function releaseMedia(tool) { +export function releaseMedia(tool: ArtplayerToolThumbnail) { const state = stateFor(tool) const sources = new Set([...state.sourceUrls, tool.videoUrl]) const thumbnails = new Set([...state.thumbnailUrls, tool.thumbnailUrl]) - const media = [] + const media: Cleanup[] = [] for (const video of new Set([state.video, tool.video])) { if (video) { media.push( diff --git a/packages/artplayer-tool-thumbnail/src/sheet.js b/packages/artplayer-tool-thumbnail/src/sheet.ts similarity index 77% rename from packages/artplayer-tool-thumbnail/src/sheet.js rename to packages/artplayer-tool-thumbnail/src/sheet.ts index 5ffcd2ad5..283a9d43e 100644 --- a/packages/artplayer-tool-thumbnail/src/sheet.js +++ b/packages/artplayer-tool-thumbnail/src/sheet.ts @@ -1,11 +1,12 @@ +import type { ScreenshotPoint, SheetOptions } from './types' import { getFileName } from './utils' -export function screenshotPoints(option, duration) { +export function screenshotPoints(option: SheetOptions, duration: number): ScreenshotPoint[] { const { number, width, height, column, begin } = option const timeGap = duration / number const timePoints = [begin + timeGap] while (timePoints.length < number) { - const last = timePoints[timePoints.length - 1] + const last = timePoints[timePoints.length - 1]! timePoints.push(last + timeGap) } return timePoints.map((item, index) => ({ @@ -15,10 +16,10 @@ export function screenshotPoints(option, duration) { })) } -export function createSheet(option) { +export function createSheet(option: SheetOptions) { const { number, width, height, column } = option const canvas = document.createElement('canvas') - const context = canvas.getContext('2d') + const context = canvas.getContext('2d')! canvas.width = width * column canvas.height = Math.ceil(number / column) * height + 30 context.fillStyle = 'black' @@ -33,7 +34,7 @@ export function createSheet(option) { return canvas } -export function downloadSheet(file, url) { +export function downloadSheet(file: File, url: string) { const link = document.createElement('a') const name = `${getFileName(file.name)}.png` link.download = name diff --git a/packages/artplayer-tool-thumbnail/src/source.js b/packages/artplayer-tool-thumbnail/src/source.ts similarity index 90% rename from packages/artplayer-tool-thumbnail/src/source.js rename to packages/artplayer-tool-thumbnail/src/source.ts index ff7f820ef..16773212d 100644 --- a/packages/artplayer-tool-thumbnail/src/source.js +++ b/packages/artplayer-tool-thumbnail/src/source.ts @@ -1,6 +1,8 @@ +import type ArtplayerToolThumbnail from './index' +import type { Cleanup } from './types' import { cleanupAll, revoke, stateFor } from './lifecycle' -export function loadSource(tool, file) { +export function loadSource(tool: ArtplayerToolThumbnail, file?: File | null) { const state = stateFor(tool) if (!file || state.closed) return @@ -23,7 +25,7 @@ export function loadSource(tool, file) { state.loading = true const current = () => !state.closed && state.epoch === epoch const previousListeners = state.sourceListeners - const listeners = [] + const listeners: Cleanup[] = [] state.sourceListeners = listeners let pendingCleanup = () => {} let reported = false @@ -50,7 +52,7 @@ export function loadSource(tool, file) { revoke(state.sourceUrls, url) return } - for (const [name, callback] of [['error', error], ['loadedmetadata', ready]]) { + for (const [name, callback] of [['error', error], ['loadedmetadata', ready]] as const) { pendingCleanup = () => video.removeEventListener(name, callback) listeners.push(pendingCleanup) video.addEventListener(name, callback) @@ -94,7 +96,7 @@ export function loadSource(tool, file) { } } -export function replaceThumbnail(tool, blob, live) { +export function replaceThumbnail(tool: ArtplayerToolThumbnail, blob: Blob, live: () => boolean) { const state = stateFor(tool) const url = URL.createObjectURL(blob) state.thumbnailUrls.add(url) diff --git a/packages/artplayer-tool-thumbnail/src/types.ts b/packages/artplayer-tool-thumbnail/src/types.ts new file mode 100644 index 000000000..750c86b7f --- /dev/null +++ b/packages/artplayer-tool-thumbnail/src/types.ts @@ -0,0 +1,62 @@ +export interface SheetOptions { + number: number + width: number + height: number + column: number + begin: number + end: number +} + +export interface InputOptions extends Partial { + fileInput?: Element + [name: string]: unknown +} + +export interface RuntimeOptions extends SheetOptions { + fileInput: HTMLInputElement + [name: string]: unknown +} + +export interface ScreenshotPoint { + time: number + x: number + y: number +} + +export interface ThumbnailEvents extends Record { + file: [file: File] + video: [video: HTMLVideoElement] + canvas: [canvas: HTMLCanvasElement] + update: [url: string, progress: number] + done: [] + download: [name: string] + error: [message: string | undefined] + destroy: [] +} + +export type Cleanup = () => void + +export interface ExtractionJob { + video: HTMLVideoElement + promise: Promise + ready: (synchronous?: boolean) => void + fail: (error: unknown, report?: boolean) => void + cancel: (reason: string) => void + adopt: (epoch: number) => boolean + reported?: unknown + wasReported?: boolean +} + +export interface LifecycleState { + closed: boolean + epoch: number + inputEpoch: number + job: ExtractionJob | null + video: HTMLVideoElement | null + sourceUrl: string | null + thumbnailUrl: string | null + sourceUrls: Set + thumbnailUrls: Set + sourceListeners: Cleanup[] + loading: boolean +} diff --git a/packages/artplayer-tool-thumbnail/src/utils.js b/packages/artplayer-tool-thumbnail/src/utils.js deleted file mode 100644 index 3f8bcece8..000000000 --- a/packages/artplayer-tool-thumbnail/src/utils.js +++ /dev/null @@ -1,17 +0,0 @@ -export function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)) -} - -export function runPromisesInSeries(ps) { - return ps.reduce((p, next) => p.then(next), Promise.resolve()) -} - -export function getFileName(name) { - const nameArray = name.split('.') - nameArray.pop() - return nameArray.join('.') -} - -export function clamp(num, a, b) { - return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b)) -} diff --git a/packages/artplayer-tool-thumbnail/src/utils.ts b/packages/artplayer-tool-thumbnail/src/utils.ts new file mode 100644 index 000000000..9b3216159 --- /dev/null +++ b/packages/artplayer-tool-thumbnail/src/utils.ts @@ -0,0 +1,9 @@ +export function getFileName(name: string) { + const nameArray = name.split('.') + nameArray.pop() + return nameArray.join('.') +} + +export function clamp(num: number, a: number, b: number) { + return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b)) +} diff --git a/packages/artplayer-tool-thumbnail/tsconfig.json b/packages/artplayer-tool-thumbnail/tsconfig.json new file mode 100644 index 000000000..42c2804b8 --- /dev/null +++ b/packages/artplayer-tool-thumbnail/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "allowJs": false }, + "include": ["src/**/*.ts"] +} diff --git a/refactor/baselines/thumbnail-runtime-types-validation.json b/refactor/baselines/thumbnail-runtime-types-validation.json new file mode 100644 index 000000000..ba3265849 --- /dev/null +++ b/refactor/baselines/thumbnail-runtime-types-validation.json @@ -0,0 +1,7446 @@ +{ + "schemaVersion": 1, + "task": "PKG-TOOL-THUMB-04", + "status": "doing", + "verifiedAt": "2026-09-12T20:43:53.175Z", + "startedFrom": "efd9f42fb984302dd061d5cd9f96bd4de74fcccf", + "nodeVersion": "v24.21.0", + "migration": { + "executableModules": 8, + "typeOnlyModules": 1, + "oldJsRemaining": 0, + "strict": true, + "publicNpmDeclarationsCompleted": false, + "sourceConsumerNegativeUses": 6, + "emittedEsmDiff": "Only complete upstream notice and two equivalent local emitter aliases; rest of prior 03 runtime unchanged." + }, + "node": { + "perVariant": 110, + "candidateInputAndLifecycle": 40, + "newFacadeEmitter": 6, + "newProvenance": 2, + "historical": 62, + "variants": [ + "source", + "main", + "legacy" + ], + "failed": 0 + }, + "ci": { + "total": 1654, + "unit": 1449, + "engineering": 14, + "baseline": 191, + "productionTs": 339, + "imports": 3, + "builtEsmImport": true, + "lintWarnings": [ + "Existing unused ts/no-namespace disable in docs/assets/ts/artplayer.d.ts. No lint errors." + ], + "runtimeWarnings": [ + "Yarn Classic DEP0169 url.parse warning under Node 24.21.0." + ] + }, + "browser": { + "total": 171, + "retries": 0, + "skips": 0, + "runs": [ + { + "variant": "source", + "counts": { + "actual-tool-scenario": 34, + "candidate-native-input": 3, + "blob-unavailable-control": 17, + "native-media-control": 3 + }, + "cases": [ + { + "title": "thumbnail-input.spec.js / Thumbnail input ownership replaces real DOM listeners and generated inputs", + "project": "chromium", + "kind": "candidate-native-input", + "status": "passed", + "durationMs": 288, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-input-ownership", + "data": { + "implementation": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "scope": "Native file input/DOM/listeners only; does not claim media decoding.", + "switched": { + "value": "", + "events": [ + "thumbnail-pattern.mp4" + ], + "firstPosition": "sticky", + "disconnected": true + }, + "result": { + "events": [ + "thumbnail-pattern.mp4", + "thumbnail-pattern.mp4" + ], + "sameError": true, + "notifications": 1, + "inputConnected": false, + "videoConnected": false, + "secondPosition": "absolute", + "inputs": 0 + } + } + } + ] + }, + { + "title": "thumbnail-input.spec.js / Thumbnail input ownership replaces real DOM listeners and generated inputs", + "project": "firefox", + "kind": "candidate-native-input", + "status": "passed", + "durationMs": 1766, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-input-ownership", + "data": { + "implementation": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "scope": "Native file input/DOM/listeners only; does not claim media decoding.", + "switched": { + "value": "", + "events": [ + "thumbnail-pattern.mp4" + ], + "firstPosition": "sticky", + "disconnected": true + }, + "result": { + "events": [ + "thumbnail-pattern.mp4", + "thumbnail-pattern.mp4" + ], + "sameError": true, + "notifications": 1, + "inputConnected": false, + "videoConnected": false, + "secondPosition": "absolute", + "inputs": 0 + } + } + } + ] + }, + { + "title": "thumbnail-input.spec.js / Thumbnail input ownership replaces real DOM listeners and generated inputs", + "project": "webkit", + "kind": "candidate-native-input", + "status": "passed", + "durationMs": 798, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-input-ownership", + "data": { + "implementation": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "scope": "Native file input/DOM/listeners only; does not claim media decoding.", + "switched": { + "value": "", + "events": [ + "thumbnail-pattern.mp4" + ], + "firstPosition": "sticky", + "disconnected": true + }, + "result": { + "events": [ + "thumbnail-pattern.mp4", + "thumbnail-pattern.mp4" + ], + "sameError": true, + "notifications": 1, + "inputConnected": false, + "videoConnected": false, + "secondPosition": "absolute", + "inputs": 0 + } + } + } + ] + }, + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "chromium", + "kind": "native-media-control", + "status": "passed", + "durationMs": 276, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.046009, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.046009, + "width": 640 + } + ] + } + ] + }, + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "firefox", + "kind": "native-media-control", + "status": "passed", + "durationMs": 1959, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.045986, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.045986, + "width": 640 + } + ] + } + ] + }, + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "webkit", + "kind": "native-media-control", + "status": "passed", + "durationMs": 2041, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 3, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 2, + "error": 0, + "duration": 90.094, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + } + ] + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 8164, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 150, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 30, + "mime": "image/png", + "bytes": 26427, + "nonBlack": 2400, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/2192597d-ca96-4328-82fe-fe9f2c731a13", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 212, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 248, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/9ccdb395-0871-4616-a910-fde198848728" + }, + "result": { + "value": "C:\\fakepath\\thumbnail-pattern.mp4", + "created": [ + "blob:http://127.0.0.1:8084/9ccdb395-0871-4616-a910-fde198848728", + "blob:http://127.0.0.1:8084/c4d4d19e-6396-4970-9f46-9c7ce108b321" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/c4d4d19e-6396-4970-9f46-9c7ce108b321" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 619, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/39f993fe-3a83-4604-b9ea-164d29965c22", + "blob:http://127.0.0.1:8084/a68af9b5-dddd-4f7a-9f2e-589735d6f022" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/39f993fe-3a83-4604-b9ea-164d29965c22" + ], + "pendingState": "pending", + "events": [ + "file", + "canvas", + "video", + "destroy", + "update" + ], + "connected": false, + "bytes": 7540 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 8469, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 150, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 30, + "mime": "image/png", + "bytes": 18224, + "nonBlack": 2400, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/6bd17e7c-2504-4806-8091-fa4a727ebe3f", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 466, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 582, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/f74f098d-b12e-4a30-b40f-95ff1e7ef752" + }, + "result": { + "value": "C:\\fakepath\\thumbnail-pattern.mp4", + "created": [ + "blob:http://127.0.0.1:8084/f74f098d-b12e-4a30-b40f-95ff1e7ef752", + "blob:http://127.0.0.1:8084/e842974b-4791-4b93-af32-7e5805dc9e4e" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/e842974b-4791-4b93-af32-7e5805dc9e4e" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1862, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/1bb035b1-6d1b-46d0-94a9-fed3f7e0fb22", + "blob:http://127.0.0.1:8084/add4cb93-db97-4418-9c03-5267a5a3cd3e" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/1bb035b1-6d1b-46d0-94a9-fed3f7e0fb22" + ], + "pendingState": "pending", + "events": [ + "file", + "canvas", + "video", + "destroy", + "update" + ], + "connected": false, + "bytes": 4980 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1489, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1380, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1344, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1344, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 494, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 44055, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/1de5fc19-fb3f-46ba-809a-02e8f40a2b65", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 204, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 278, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/8161e658-3a11-45fc-8f66-dc7bf41cf8dd" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/8161e658-3a11-45fc-8f66-dc7bf41cf8dd", + "blob:http://127.0.0.1:8084/46b0ffa6-f059-4254-8922-bdaab1e252da" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/46b0ffa6-f059-4254-8922-bdaab1e252da" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 355, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/7f1f14ab-d3f9-4d33-b906-72899f8aa70e", + "blob:http://127.0.0.1:8084/580ca125-4d87-42c8-b0b5-ab589c4fbc43" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/7f1f14ab-d3f9-4d33-b906-72899f8aa70e" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 9120 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1611, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 38895, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/a857732d-37a1-4a2f-8c8d-917c86eb6e92", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 468, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 633, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/6007069d-a25f-4664-90d9-35641643c743" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/6007069d-a25f-4664-90d9-35641643c743", + "blob:http://127.0.0.1:8084/754d201c-95e7-408a-b0a7-82e843999749" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/754d201c-95e7-408a-b0a7-82e843999749" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 745, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/fedc2cfa-dc8a-42bd-8260-826cfa8b414f", + "blob:http://127.0.0.1:8084/2f0f3445-ae9d-4797-86b2-c6dd249c020c" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/fedc2cfa-dc8a-42bd-8260-826cfa8b414f" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 6496 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1396, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1390, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1510, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1263, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 515, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 44055, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/2508eade-0eb7-4e9d-96d2-c68250df6607", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 183, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 228, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/d8da6ec9-87ed-405c-ad41-7e25e24fafcb" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/d8da6ec9-87ed-405c-ad41-7e25e24fafcb", + "blob:http://127.0.0.1:8084/b784432c-d6da-4732-90b2-a42b1e81513d" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/b784432c-d6da-4732-90b2-a42b1e81513d" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 339, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/18d6d85b-df34-44db-9bd2-4bfb7b1cee71", + "blob:http://127.0.0.1:8084/12cef8e4-fdab-4d14-b56c-6ecb05b2f22c" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/18d6d85b-df34-44db-9bd2-4bfb7b1cee71" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 9120 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1831, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 38560, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/d29149e0-856a-44a3-89c8-d1818c5ed3fb", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 433, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 493, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/2d792552-840e-47dc-81ab-9670fcc10755" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/2d792552-840e-47dc-81ab-9670fcc10755", + "blob:http://127.0.0.1:8084/489547ae-ac4d-4549-9326-bec08a5866f6" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/489547ae-ac4d-4549-9326-bec08a5866f6" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 689, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/5b604987-0a06-4a79-8978-9e3317b9543e", + "blob:http://127.0.0.1:8084/98f714e1-ff99-47a0-bd5a-bd0bda1eb0ca" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/5b604987-0a06-4a79-8978-9e3317b9543e" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 6496 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1302, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1376, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1292, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1299, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 521, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 44055, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 0, + "retiredSrc": null, + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / DOM drop dispatch uses the registered handler while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 187, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 226, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 1, + "first": "blob:http://127.0.0.1:8084/9305dcfd-a781-4a16-ac36-4e245d35a46e" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/9305dcfd-a781-4a16-ac36-4e245d35a46e", + "blob:http://127.0.0.1:8084/85985333-23ab-48a9-8e54-5305bae958e4" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/9305dcfd-a781-4a16-ac36-4e245d35a46e", + "blob:http://127.0.0.1:8084/85985333-23ab-48a9-8e54-5305bae958e4" + ] + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / native PNG callback delivered after destroy is ignored after cancellation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 349, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/a50493a9-834a-4653-bf96-84f8b793a74b" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/a50493a9-834a-4653-bf96-84f8b793a74b" + ], + "pendingState": "rejected", + "events": [ + "file", + "video", + "canvas", + "destroy" + ], + "connected": false, + "bytes": 9120 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / source replacement cancels held native encoding and completes only the latest job", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 505, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-native-source-cancellation", + "data": { + "oldState": "AbortError", + "firstEncodedBytes": 9120, + "source": "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "updateSources": [ + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8" + ], + "created": [ + "blob:http://127.0.0.1:8084/31c11db4-6406-41ac-b5ed-37149eb1de7d", + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "blob:http://127.0.0.1:8084/5b9d15db-0922-469b-8f3d-7865f16503c0", + "blob:http://127.0.0.1:8084/2f782ba4-24d9-4018-b687-beabbcbca763", + "blob:http://127.0.0.1:8084/9651b9c4-0122-4055-88ca-882a40a1c9dc", + "blob:http://127.0.0.1:8084/699a45f9-5951-498e-b8fc-48a94d8391c0", + "blob:http://127.0.0.1:8084/da55e898-ac6e-4dca-829b-4aa1c61c3f4f", + "blob:http://127.0.0.1:8084/a73c5f4e-99e2-45f9-b1e1-267a4e1cfa6d", + "blob:http://127.0.0.1:8084/27b638f2-8eb2-4a51-89f7-09cfe01fea7b", + "blob:http://127.0.0.1:8084/dbfcac68-ffee-4ba3-91a0-b352d469ed6c", + "blob:http://127.0.0.1:8084/8d3df93e-d829-4b21-8239-84bfab64ed9d", + "blob:http://127.0.0.1:8084/b8d14d7e-5b01-4dc6-957c-df38e128ec53" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/31c11db4-6406-41ac-b5ed-37149eb1de7d", + "blob:http://127.0.0.1:8084/5b9d15db-0922-469b-8f3d-7865f16503c0", + "blob:http://127.0.0.1:8084/2f782ba4-24d9-4018-b687-beabbcbca763", + "blob:http://127.0.0.1:8084/9651b9c4-0122-4055-88ca-882a40a1c9dc", + "blob:http://127.0.0.1:8084/699a45f9-5951-498e-b8fc-48a94d8391c0", + "blob:http://127.0.0.1:8084/da55e898-ac6e-4dca-829b-4aa1c61c3f4f", + "blob:http://127.0.0.1:8084/a73c5f4e-99e2-45f9-b1e1-267a4e1cfa6d", + "blob:http://127.0.0.1:8084/27b638f2-8eb2-4a51-89f7-09cfe01fea7b", + "blob:http://127.0.0.1:8084/dbfcac68-ffee-4ba3-91a0-b352d469ed6c", + "blob:http://127.0.0.1:8084/8d3df93e-d829-4b21-8239-84bfab64ed9d", + "blob:http://127.0.0.1:8084/e8964d63-6df4-4c02-ba71-90d0075ebfe8", + "blob:http://127.0.0.1:8084/b8d14d7e-5b01-4dc6-957c-df38e128ec53" + ], + "events": [ + "file", + "video", + "canvas", + "file", + "video", + "canvas", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "done", + "destroy" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1621, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 38895, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 0, + "retiredSrc": null, + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / DOM drop dispatch uses the registered handler while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 481, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 597, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 1, + "first": "blob:http://127.0.0.1:8084/02aabd84-1bf2-4483-a23a-c11da0863ff1" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/02aabd84-1bf2-4483-a23a-c11da0863ff1", + "blob:http://127.0.0.1:8084/01f23143-bfc6-4a23-ba11-be99ac92af03" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/02aabd84-1bf2-4483-a23a-c11da0863ff1", + "blob:http://127.0.0.1:8084/01f23143-bfc6-4a23-ba11-be99ac92af03" + ] + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / native PNG callback delivered after destroy is ignored after cancellation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 733, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/cbfaeab4-7a9a-4bdc-aa8e-7096ca8822a5" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/cbfaeab4-7a9a-4bdc-aa8e-7096ca8822a5" + ], + "pendingState": "rejected", + "events": [ + "file", + "video", + "canvas", + "destroy" + ], + "connected": false, + "bytes": 6496 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / source replacement cancels held native encoding and completes only the latest job", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1628, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-native-source-cancellation", + "data": { + "oldState": "AbortError", + "firstEncodedBytes": 6496, + "source": "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "updateSources": [ + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764" + ], + "created": [ + "blob:http://127.0.0.1:8084/5c9ee64a-7363-4125-8438-dd86634d4b98", + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "blob:http://127.0.0.1:8084/0e2f3275-e7de-42bb-adbd-e6f3a87e1423", + "blob:http://127.0.0.1:8084/a5304e0b-3db4-4295-8470-b04d23759479", + "blob:http://127.0.0.1:8084/3c7638a1-40a7-4a83-9fa3-a82ac553eea2", + "blob:http://127.0.0.1:8084/a3e0d349-dbf1-44fd-905c-2c5485323743", + "blob:http://127.0.0.1:8084/8735db69-a38d-462c-9b34-9470e7615d9d", + "blob:http://127.0.0.1:8084/bfd3ea35-dc05-419f-aaab-573c01c0b993", + "blob:http://127.0.0.1:8084/f39424f4-65d7-4d97-99db-7094aed8ce54", + "blob:http://127.0.0.1:8084/cb630a3b-4a59-4aff-83ac-80ddf6322a1e", + "blob:http://127.0.0.1:8084/d5608d2e-d227-4972-92fa-c75ef37ad732", + "blob:http://127.0.0.1:8084/53b75104-00ff-4687-a33d-3a3b7b1715f3" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/5c9ee64a-7363-4125-8438-dd86634d4b98", + "blob:http://127.0.0.1:8084/0e2f3275-e7de-42bb-adbd-e6f3a87e1423", + "blob:http://127.0.0.1:8084/a5304e0b-3db4-4295-8470-b04d23759479", + "blob:http://127.0.0.1:8084/3c7638a1-40a7-4a83-9fa3-a82ac553eea2", + "blob:http://127.0.0.1:8084/a3e0d349-dbf1-44fd-905c-2c5485323743", + "blob:http://127.0.0.1:8084/8735db69-a38d-462c-9b34-9470e7615d9d", + "blob:http://127.0.0.1:8084/bfd3ea35-dc05-419f-aaab-573c01c0b993", + "blob:http://127.0.0.1:8084/f39424f4-65d7-4d97-99db-7094aed8ce54", + "blob:http://127.0.0.1:8084/cb630a3b-4a59-4aff-83ac-80ddf6322a1e", + "blob:http://127.0.0.1:8084/d5608d2e-d227-4972-92fa-c75ef37ad732", + "blob:http://127.0.0.1:8084/6772176f-551a-4f5a-a3b4-04d293b35764", + "blob:http://127.0.0.1:8084/53b75104-00ff-4687-a33d-3a3b7b1715f3" + ], + "events": [ + "file", + "video", + "canvas", + "file", + "video", + "canvas", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "done", + "destroy" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1325, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / DOM drop dispatch uses the registered handler while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1315, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1302, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / native PNG callback delivered after destroy is ignored after cancellation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1446, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-source / source replacement cancels held native encoding and completes only the latest job", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1282, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-source", + "sha256": "27a78a64342d575260f332e19be15018282e7ced2ec81fa73f37e58237f72db0", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + } + ] + }, + { + "variant": "main", + "counts": { + "actual-tool-scenario": 34, + "candidate-native-input": 3, + "blob-unavailable-control": 17, + "native-media-control": 3 + }, + "cases": [ + { + "title": "thumbnail-input.spec.js / Thumbnail input ownership replaces real DOM listeners and generated inputs", + "project": "chromium", + "kind": "candidate-native-input", + "status": "passed", + "durationMs": 279, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-input-ownership", + "data": { + "implementation": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "scope": "Native file input/DOM/listeners only; does not claim media decoding.", + "switched": { + "value": "", + "events": [ + "thumbnail-pattern.mp4" + ], + "firstPosition": "sticky", + "disconnected": true + }, + "result": { + "events": [ + "thumbnail-pattern.mp4", + "thumbnail-pattern.mp4" + ], + "sameError": true, + "notifications": 1, + "inputConnected": false, + "videoConnected": false, + "secondPosition": "absolute", + "inputs": 0 + } + } + } + ] + }, + { + "title": "thumbnail-input.spec.js / Thumbnail input ownership replaces real DOM listeners and generated inputs", + "project": "firefox", + "kind": "candidate-native-input", + "status": "passed", + "durationMs": 1745, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-input-ownership", + "data": { + "implementation": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "scope": "Native file input/DOM/listeners only; does not claim media decoding.", + "switched": { + "value": "", + "events": [ + "thumbnail-pattern.mp4" + ], + "firstPosition": "sticky", + "disconnected": true + }, + "result": { + "events": [ + "thumbnail-pattern.mp4", + "thumbnail-pattern.mp4" + ], + "sameError": true, + "notifications": 1, + "inputConnected": false, + "videoConnected": false, + "secondPosition": "absolute", + "inputs": 0 + } + } + } + ] + }, + { + "title": "thumbnail-input.spec.js / Thumbnail input ownership replaces real DOM listeners and generated inputs", + "project": "webkit", + "kind": "candidate-native-input", + "status": "passed", + "durationMs": 891, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-input-ownership", + "data": { + "implementation": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "scope": "Native file input/DOM/listeners only; does not claim media decoding.", + "switched": { + "value": "", + "events": [ + "thumbnail-pattern.mp4" + ], + "firstPosition": "sticky", + "disconnected": true + }, + "result": { + "events": [ + "thumbnail-pattern.mp4", + "thumbnail-pattern.mp4" + ], + "sameError": true, + "notifications": 1, + "inputConnected": false, + "videoConnected": false, + "secondPosition": "absolute", + "inputs": 0 + } + } + } + ] + }, + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "chromium", + "kind": "native-media-control", + "status": "passed", + "durationMs": 265, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.046009, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.046009, + "width": 640 + } + ] + } + ] + }, + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "firefox", + "kind": "native-media-control", + "status": "passed", + "durationMs": 1900, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.045986, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.045986, + "width": 640 + } + ] + } + ] + }, + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "webkit", + "kind": "native-media-control", + "status": "passed", + "durationMs": 2288, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 3, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 3, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 2, + "error": 0, + "duration": 90.094, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + } + ] + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 8080, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 150, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 30, + "mime": "image/png", + "bytes": 26427, + "nonBlack": 2400, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/51a8fd44-d4ff-440d-ac9b-94ef00f45cd9", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 216, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 252, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/a822e3d6-2e21-46a0-8599-b719bad52472" + }, + "result": { + "value": "C:\\fakepath\\thumbnail-pattern.mp4", + "created": [ + "blob:http://127.0.0.1:8084/a822e3d6-2e21-46a0-8599-b719bad52472", + "blob:http://127.0.0.1:8084/c123e6da-8384-49cd-9761-be50c38f2c78" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/c123e6da-8384-49cd-9761-be50c38f2c78" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 626, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/87a7fc0e-dcc7-42a3-8a5f-b5b242217e5d", + "blob:http://127.0.0.1:8084/0b0879e8-93c2-405b-b276-19e4d2cfc0cf" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/87a7fc0e-dcc7-42a3-8a5f-b5b242217e5d" + ], + "pendingState": "pending", + "events": [ + "file", + "canvas", + "video", + "destroy", + "update" + ], + "connected": false, + "bytes": 7540 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 8442, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 150, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 30, + "mime": "image/png", + "bytes": 18224, + "nonBlack": 2400, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/a7961d86-08f6-44b3-bdfe-52d10cade95b", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 479, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 585, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/7ba3cdcb-44b3-4f5b-ae59-4283d9e45ddd" + }, + "result": { + "value": "C:\\fakepath\\thumbnail-pattern.mp4", + "created": [ + "blob:http://127.0.0.1:8084/7ba3cdcb-44b3-4f5b-ae59-4283d9e45ddd", + "blob:http://127.0.0.1:8084/4ed6f68a-b38d-4f20-8e99-a3b29f1cd56d" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/4ed6f68a-b38d-4f20-8e99-a3b29f1cd56d" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1924, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/146fca50-9c92-48a1-ad1e-f72d78298ebf", + "blob:http://127.0.0.1:8084/70ad168e-284b-45cf-b0a9-2bb87d1e0c2c" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/146fca50-9c92-48a1-ad1e-f72d78298ebf" + ], + "pendingState": "pending", + "events": [ + "file", + "canvas", + "video", + "destroy", + "update" + ], + "connected": false, + "bytes": 4980 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1425, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1423, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1359, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1426, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 505, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 44055, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/ae230aad-98df-46a8-bd8b-7455cde7e9a1", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 191, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 225, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/4f26f18d-5e79-4826-be7d-920ffe4e0e36" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/4f26f18d-5e79-4826-be7d-920ffe4e0e36", + "blob:http://127.0.0.1:8084/9d3027a6-a344-434f-b7ea-afb44d6a8a1c" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/9d3027a6-a344-434f-b7ea-afb44d6a8a1c" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 340, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/00e1f1d5-7a95-40cf-b818-54a1aa3e8bc8", + "blob:http://127.0.0.1:8084/73de65f2-51ea-4d58-8ceb-b50bf3e39920" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/00e1f1d5-7a95-40cf-b818-54a1aa3e8bc8" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 9120 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1517, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 38895, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/2a0f1f12-1df1-4a52-a9a2-0e2ee7d7040b", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 461, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 595, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/3a5c34f2-6d82-4ab1-9cee-efb1d89ccb57" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/3a5c34f2-6d82-4ab1-9cee-efb1d89ccb57", + "blob:http://127.0.0.1:8084/306f5128-a1a8-4300-bd7e-f13685bb38e3" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/306f5128-a1a8-4300-bd7e-f13685bb38e3" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 663, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/1ff73fe7-6b8a-4be9-b044-498453dcfd71", + "blob:http://127.0.0.1:8084/a3348016-6bf4-4b0e-a045-6e91582d42e8" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/1ff73fe7-6b8a-4be9-b044-498453dcfd71" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 6496 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1350, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1532, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1393, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1320, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 539, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 44055, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/835d174a-bd71-49b4-b6d1-55a90da9ab11", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 206, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 256, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/f2875d85-621e-44a3-bc31-2b5298bde097" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/f2875d85-621e-44a3-bc31-2b5298bde097", + "blob:http://127.0.0.1:8084/8ae82476-ece0-4ffc-8ba0-d4feca9feb37" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/8ae82476-ece0-4ffc-8ba0-d4feca9feb37" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 367, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/82889b7f-2014-4661-a652-1132da47949a", + "blob:http://127.0.0.1:8084/3516e788-c134-474a-abab-9fd18efff915" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/82889b7f-2014-4661-a652-1132da47949a" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 9120 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1802, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 38895, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/dba5e719-4d2d-49a8-8216-f1480a386daf", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 546, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 543, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/b810bd2a-ab2e-4204-b156-82df52b53af6" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/b810bd2a-ab2e-4204-b156-82df52b53af6", + "blob:http://127.0.0.1:8084/1d2f7129-dcc7-4a97-9f01-7ee1979c4874" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/1d2f7129-dcc7-4a97-9f01-7ee1979c4874" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 618, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/88173a82-b6af-4b8f-9771-0247cee26d8e", + "blob:http://127.0.0.1:8084/85a9c89f-d35d-41b8-882e-19ede3f8f836" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/88173a82-b6af-4b8f-9771-0247cee26d8e" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 6496 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1443, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1388, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1476, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1500, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 590, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 44055, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 0, + "retiredSrc": null, + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / DOM drop dispatch uses the registered handler while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 207, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 235, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 1, + "first": "blob:http://127.0.0.1:8084/ac01af04-7cab-4005-8879-e02f77ff0805" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/ac01af04-7cab-4005-8879-e02f77ff0805", + "blob:http://127.0.0.1:8084/8fe8e3f5-eebb-4079-b9ca-94ddbe93e157" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/ac01af04-7cab-4005-8879-e02f77ff0805", + "blob:http://127.0.0.1:8084/8fe8e3f5-eebb-4079-b9ca-94ddbe93e157" + ] + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native PNG callback delivered after destroy is ignored after cancellation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 369, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/45992ac0-957e-4639-8f52-aab8740e6bf2" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/45992ac0-957e-4639-8f52-aab8740e6bf2" + ], + "pendingState": "rejected", + "events": [ + "file", + "video", + "canvas", + "destroy" + ], + "connected": false, + "bytes": 9120 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / source replacement cancels held native encoding and completes only the latest job", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 470, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-native-source-cancellation", + "data": { + "oldState": "AbortError", + "firstEncodedBytes": 9120, + "source": "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "updateSources": [ + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8" + ], + "created": [ + "blob:http://127.0.0.1:8084/3883e9dc-0330-4c65-963d-24cee0df9556", + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "blob:http://127.0.0.1:8084/c17fcf3c-712c-4cd7-a39c-036f5ae5cf28", + "blob:http://127.0.0.1:8084/375f453a-f508-4a02-9d1a-54d554488df1", + "blob:http://127.0.0.1:8084/48f35239-3d4d-4d4c-a77a-9aed6c76cd60", + "blob:http://127.0.0.1:8084/921f2268-5413-4ac3-9214-7a94f8d9961c", + "blob:http://127.0.0.1:8084/09ed4e04-2c3a-41ea-8f3c-f638f31bbcb1", + "blob:http://127.0.0.1:8084/77968e43-da49-4597-ac56-df4b01bdae27", + "blob:http://127.0.0.1:8084/322e5d2a-09fd-4c09-a852-0f5bf7aa687c", + "blob:http://127.0.0.1:8084/287c1ca5-9711-40dc-a7d7-7e55c9c394e3", + "blob:http://127.0.0.1:8084/cbfa059d-becf-437a-a5dc-8482b0b1eb25", + "blob:http://127.0.0.1:8084/4159e3cf-917b-4f1d-a550-1f6f83254e3c" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/3883e9dc-0330-4c65-963d-24cee0df9556", + "blob:http://127.0.0.1:8084/c17fcf3c-712c-4cd7-a39c-036f5ae5cf28", + "blob:http://127.0.0.1:8084/375f453a-f508-4a02-9d1a-54d554488df1", + "blob:http://127.0.0.1:8084/48f35239-3d4d-4d4c-a77a-9aed6c76cd60", + "blob:http://127.0.0.1:8084/921f2268-5413-4ac3-9214-7a94f8d9961c", + "blob:http://127.0.0.1:8084/09ed4e04-2c3a-41ea-8f3c-f638f31bbcb1", + "blob:http://127.0.0.1:8084/77968e43-da49-4597-ac56-df4b01bdae27", + "blob:http://127.0.0.1:8084/322e5d2a-09fd-4c09-a852-0f5bf7aa687c", + "blob:http://127.0.0.1:8084/287c1ca5-9711-40dc-a7d7-7e55c9c394e3", + "blob:http://127.0.0.1:8084/cbfa059d-becf-437a-a5dc-8482b0b1eb25", + "blob:http://127.0.0.1:8084/42c71af8-8ab8-4c4a-94fd-c8913881bba8", + "blob:http://127.0.0.1:8084/4159e3cf-917b-4f1d-a550-1f6f83254e3c" + ], + "events": [ + "file", + "video", + "canvas", + "file", + "video", + "canvas", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "done", + "destroy" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1670, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 38560, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 0, + "retiredSrc": null, + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / DOM drop dispatch uses the registered handler while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 466, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 640, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 1, + "first": "blob:http://127.0.0.1:8084/deee25e7-b2be-44d9-b490-13bbbaa8c278" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/deee25e7-b2be-44d9-b490-13bbbaa8c278", + "blob:http://127.0.0.1:8084/e25d3898-55a8-4149-ab05-5973031b550b" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/deee25e7-b2be-44d9-b490-13bbbaa8c278", + "blob:http://127.0.0.1:8084/e25d3898-55a8-4149-ab05-5973031b550b" + ] + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native PNG callback delivered after destroy is ignored after cancellation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 711, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/78f3115e-69d5-42d4-8633-e371da1a988d" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/78f3115e-69d5-42d4-8633-e371da1a988d" + ], + "pendingState": "rejected", + "events": [ + "file", + "video", + "canvas", + "destroy" + ], + "connected": false, + "bytes": 6496 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / source replacement cancels held native encoding and completes only the latest job", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 2112, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-native-source-cancellation", + "data": { + "oldState": "AbortError", + "firstEncodedBytes": 6496, + "source": "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "updateSources": [ + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727" + ], + "created": [ + "blob:http://127.0.0.1:8084/51dd17e2-672b-47b6-93a8-7cc8a3c01d20", + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "blob:http://127.0.0.1:8084/f5ab9590-0b45-48ba-af9f-b3983a029949", + "blob:http://127.0.0.1:8084/39e25957-2ff9-4638-b924-ab82883c63f1", + "blob:http://127.0.0.1:8084/0efdac4e-4df9-4b1f-9e4a-1ce4cedea08b", + "blob:http://127.0.0.1:8084/b297ffc3-33f9-4037-a16f-ce5040384e3b", + "blob:http://127.0.0.1:8084/115456f8-c22b-4086-abb7-82ffb03fc7f5", + "blob:http://127.0.0.1:8084/5c5d9a9e-be13-42d9-99b3-276787c5db94", + "blob:http://127.0.0.1:8084/eb6fd9b4-02fc-4401-bc5c-0f00afdb6861", + "blob:http://127.0.0.1:8084/19f499b7-bd41-494f-af76-f5748e459830", + "blob:http://127.0.0.1:8084/68a96ba6-0c3d-44c9-9f78-ea6934d4d798", + "blob:http://127.0.0.1:8084/a341e09c-e795-4a43-b98c-394296cf2dac" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/51dd17e2-672b-47b6-93a8-7cc8a3c01d20", + "blob:http://127.0.0.1:8084/f5ab9590-0b45-48ba-af9f-b3983a029949", + "blob:http://127.0.0.1:8084/39e25957-2ff9-4638-b924-ab82883c63f1", + "blob:http://127.0.0.1:8084/0efdac4e-4df9-4b1f-9e4a-1ce4cedea08b", + "blob:http://127.0.0.1:8084/b297ffc3-33f9-4037-a16f-ce5040384e3b", + "blob:http://127.0.0.1:8084/115456f8-c22b-4086-abb7-82ffb03fc7f5", + "blob:http://127.0.0.1:8084/5c5d9a9e-be13-42d9-99b3-276787c5db94", + "blob:http://127.0.0.1:8084/eb6fd9b4-02fc-4401-bc5c-0f00afdb6861", + "blob:http://127.0.0.1:8084/19f499b7-bd41-494f-af76-f5748e459830", + "blob:http://127.0.0.1:8084/68a96ba6-0c3d-44c9-9f78-ea6934d4d798", + "blob:http://127.0.0.1:8084/a97389c7-44de-44d6-8651-451ff16df727", + "blob:http://127.0.0.1:8084/a341e09c-e795-4a43-b98c-394296cf2dac" + ], + "events": [ + "file", + "video", + "canvas", + "file", + "video", + "canvas", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "done", + "destroy" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1501, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / DOM drop dispatch uses the registered handler while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1441, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1865, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native PNG callback delivered after destroy is ignored after cancellation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1725, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / source replacement cancels held native encoding and completes only the latest job", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1734, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + } + ] + }, + { + "variant": "legacy", + "counts": { + "actual-tool-scenario": 34, + "candidate-native-input": 3, + "blob-unavailable-control": 17, + "native-media-control": 3 + }, + "cases": [ + { + "title": "thumbnail-input.spec.js / Thumbnail input ownership replaces real DOM listeners and generated inputs", + "project": "chromium", + "kind": "candidate-native-input", + "status": "passed", + "durationMs": 303, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-input-ownership", + "data": { + "implementation": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "scope": "Native file input/DOM/listeners only; does not claim media decoding.", + "switched": { + "value": "", + "events": [ + "thumbnail-pattern.mp4" + ], + "firstPosition": "sticky", + "disconnected": true + }, + "result": { + "events": [ + "thumbnail-pattern.mp4", + "thumbnail-pattern.mp4" + ], + "sameError": true, + "notifications": 1, + "inputConnected": false, + "videoConnected": false, + "secondPosition": "absolute", + "inputs": 0 + } + } + } + ] + }, + { + "title": "thumbnail-input.spec.js / Thumbnail input ownership replaces real DOM listeners and generated inputs", + "project": "firefox", + "kind": "candidate-native-input", + "status": "passed", + "durationMs": 1949, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-input-ownership", + "data": { + "implementation": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "scope": "Native file input/DOM/listeners only; does not claim media decoding.", + "switched": { + "value": "", + "events": [ + "thumbnail-pattern.mp4" + ], + "firstPosition": "sticky", + "disconnected": true + }, + "result": { + "events": [ + "thumbnail-pattern.mp4", + "thumbnail-pattern.mp4" + ], + "sameError": true, + "notifications": 1, + "inputConnected": false, + "videoConnected": false, + "secondPosition": "absolute", + "inputs": 0 + } + } + } + ] + }, + { + "title": "thumbnail-input.spec.js / Thumbnail input ownership replaces real DOM listeners and generated inputs", + "project": "webkit", + "kind": "candidate-native-input", + "status": "passed", + "durationMs": 937, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-input-ownership", + "data": { + "implementation": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "scope": "Native file input/DOM/listeners only; does not claim media decoding.", + "switched": { + "value": "", + "events": [ + "thumbnail-pattern.mp4" + ], + "firstPosition": "sticky", + "disconnected": true + }, + "result": { + "events": [ + "thumbnail-pattern.mp4", + "thumbnail-pattern.mp4" + ], + "sameError": true, + "notifications": 1, + "inputConnected": false, + "videoConnected": false, + "secondPosition": "absolute", + "inputs": 0 + } + } + } + ] + }, + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "chromium", + "kind": "native-media-control", + "status": "passed", + "durationMs": 273, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.046009, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.046009, + "width": 640 + } + ] + } + ] + }, + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "firefox", + "kind": "native-media-control", + "status": "passed", + "durationMs": 1901, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 3, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.045986, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 90.045986, + "width": 640 + } + ] + } + ] + }, + { + "title": "thumbnail-native.spec.js / Thumbnail native HTTP and Blob media controls", + "project": "webkit", + "kind": "native-media-control", + "status": "passed", + "durationMs": 2422, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-native-media-controls", + "data": [ + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 4, + "error": 0, + "duration": 8, + "width": 320 + }, + { + "url": "/test/pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 3, + "error": 0, + "duration": 24, + "width": 320 + }, + { + "url": "/test/thumbnail-pattern.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "http", + "outcome": "loaded", + "readyState": 2, + "error": 0, + "duration": 90.094, + "width": 640 + }, + { + "url": "/assets/sample/video.mp4", + "type": "video/mp4", + "transport": "blob", + "outcome": "error", + "readyState": 0, + "error": 4, + "duration": null, + "width": 0 + } + ] + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 8123, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 150, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 30, + "mime": "image/png", + "bytes": 26427, + "nonBlack": 2400, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/5e995b60-7fcf-4b70-93de-4a532c7d78e7", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 210, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 292, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/e2e29b49-7650-47dc-b77e-a3b7c931cb5b" + }, + "result": { + "value": "C:\\fakepath\\thumbnail-pattern.mp4", + "created": [ + "blob:http://127.0.0.1:8084/e2e29b49-7650-47dc-b77e-a3b7c931cb5b", + "blob:http://127.0.0.1:8084/45fb592e-a28d-461e-9e7e-2bae41849388" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/45fb592e-a28d-461e-9e7e-2bae41849388" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 636, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/b9bc86ae-a6a5-4912-9ebf-48db2a5e7fc4", + "blob:http://127.0.0.1:8084/6d97076b-c19c-4964-8319-8eeb54f18259" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/b9bc86ae-a6a5-4912-9ebf-48db2a5e7fc4" + ], + "pendingState": "pending", + "events": [ + "file", + "canvas", + "video", + "destroy", + "update" + ], + "connected": false, + "bytes": 7540 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 8508, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 150, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 30, + "mime": "image/png", + "bytes": 18224, + "nonBlack": 2400, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/44cdd1f9-10f4-4405-bfef-89eb1d14e494", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 511, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1841, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/9b3100b5-9221-4aee-8116-d95c27b2b574" + }, + "result": { + "value": "C:\\fakepath\\thumbnail-pattern.mp4", + "created": [ + "blob:http://127.0.0.1:8084/9b3100b5-9221-4aee-8116-d95c27b2b574", + "blob:http://127.0.0.1:8084/ad7e0468-dfcf-4e00-b8ec-7430167a3a6d" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/ad7e0468-dfcf-4e00-b8ec-7430167a3a6d" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 950, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/1c01d435-9cdb-4688-a0b4-1badfa61dfb7", + "blob:http://127.0.0.1:8084/2043d052-1488-4a01-806a-bc052bce05a9" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/1c01d435-9cdb-4688-a0b4-1badfa61dfb7" + ], + "pendingState": "pending", + "events": [ + "file", + "canvas", + "video", + "destroy", + "update" + ], + "connected": false, + "bytes": 4980 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1399, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1396, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1495, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool cdn-3.5.31 / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1312, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "cdn-3.5.31", + "sha256": "27fdc506c9b228c40df39f8508b3a42c145571c06c0b9cc60327a007a2064c87", + "recoveredIndividualFile": true, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 503, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 44055, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/4947f941-b047-477f-bc64-085b71c3e738", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 209, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 257, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/4d9b25a8-7e9c-47fc-976e-1682b10e62cc" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/4d9b25a8-7e9c-47fc-976e-1682b10e62cc", + "blob:http://127.0.0.1:8084/8b9649e1-7164-45cd-95ac-2370a21c8383" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/8b9649e1-7164-45cd-95ac-2370a21c8383" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 349, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/6bac0b37-b4ee-41ab-a2f7-9144d6551142", + "blob:http://127.0.0.1:8084/bfcc2d87-5149-476f-8417-0af2f0ef67a8" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/6bac0b37-b4ee-41ab-a2f7-9144d6551142" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 9120 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1748, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 38895, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/a11c3037-ba11-44df-acb3-856fca497601", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 533, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 668, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/10da2172-5e73-4743-a285-c9692be9a579" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/10da2172-5e73-4743-a285-c9692be9a579", + "blob:http://127.0.0.1:8084/1e34b095-56ac-42a3-a776-f21a1bfc040b" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/1e34b095-56ac-42a3-a776-f21a1bfc040b" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 876, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/a1de1016-a7de-41ff-a6dc-26afc67d7a1d", + "blob:http://127.0.0.1:8084/6bda66c3-f514-448c-a292-b40d7c2616ec" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/a1de1016-a7de-41ff-a6dc-26afc67d7a1d" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 6496 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1341, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1444, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1368, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1456, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 612, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 44055, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/7c26c332-5f6e-4482-9135-29a194a47087", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 200, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 247, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/1d40c262-6930-4d82-983c-97360544bf99" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/1d40c262-6930-4d82-983c-97360544bf99", + "blob:http://127.0.0.1:8084/65293dd5-02ef-4fde-9e8a-829fe3b54de3" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/65293dd5-02ef-4fde-9e8a-829fe3b54de3" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 362, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/3f3f8d12-5a40-4058-a740-5a986a88874f", + "blob:http://127.0.0.1:8084/d47a2d31-21e9-4a64-8997-e76d3b363625" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/3f3f8d12-5a40-4058-a740-5a986a88874f" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 9120 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1789, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 38895, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 4, + "retiredSrc": "blob:http://127.0.0.1:8084/c27d1199-b3f5-4f23-ad8a-cef613f6174a", + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 478, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 719, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 0, + "first": "blob:http://127.0.0.1:8084/117a0424-b92b-49f9-99b1-d7f240ce7d11" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/117a0424-b92b-49f9-99b1-d7f240ce7d11", + "blob:http://127.0.0.1:8084/111505c2-c7bd-4518-8fba-46b1a7cb9e4e" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/111505c2-c7bd-4518-8fba-46b1a7cb9e4e" + ], + "repeated": "NotFoundError" + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 726, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/16a7958a-2dff-454c-a687-6e8448bd288a", + "blob:http://127.0.0.1:8084/bab4c2f3-ab9b-44b7-9527-fba22a35a70e" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/16a7958a-2dff-454c-a687-6e8448bd288a" + ], + "pendingState": "pending", + "events": [ + "file", + "video", + "canvas", + "destroy", + "update" + ], + "connected": false, + "bytes": 6496 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1430, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / DOM drop dispatch exposes missing listener while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1416, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1387, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool workspace.legacy.js / native PNG callback delivered after destroy still emits update and creates a URL", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1384, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "workspace.legacy.js", + "sha256": "f77419cc62eeaa45c0413f4b04d169f78178fd539443cbf7b1e5db4ad4c95c98", + "recoveredIndividualFile": false, + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 0, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 556, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 44055, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 0, + "retiredSrc": null, + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / DOM drop dispatch uses the registered handler while callable ondrop works", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 208, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 242, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 1, + "first": "blob:http://127.0.0.1:8084/ad2cff2d-9cb7-46c9-8d32-71b668f80c20" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/ad2cff2d-9cb7-46c9-8d32-71b668f80c20", + "blob:http://127.0.0.1:8084/f10f808d-6929-4a13-9184-9616e7362ed0" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/ad2cff2d-9cb7-46c9-8d32-71b668f80c20", + "blob:http://127.0.0.1:8084/f10f808d-6929-4a13-9184-9616e7362ed0" + ] + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native PNG callback delivered after destroy is ignored after cancellation", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 357, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/d4be05ae-1791-4c65-b01e-8f44356aa7ea" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/d4be05ae-1791-4c65-b01e-8f44356aa7ea" + ], + "pendingState": "rejected", + "events": [ + "file", + "video", + "canvas", + "destroy" + ], + "connected": false, + "bytes": 9120 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / source replacement cancels held native encoding and completes only the latest job", + "project": "chromium", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 525, + "browser": "153.0.8010.12", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-native-source-cancellation", + "data": { + "oldState": "AbortError", + "firstEncodedBytes": 9120, + "source": "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "updateSources": [ + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498" + ], + "created": [ + "blob:http://127.0.0.1:8084/680d8e54-06ec-43b9-9def-a469d5f96535", + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "blob:http://127.0.0.1:8084/0bff75f6-7c62-4a5d-afba-d82322f76696", + "blob:http://127.0.0.1:8084/39b0a00d-2e01-4fd9-92fb-56d4abb80ae5", + "blob:http://127.0.0.1:8084/e3ca9c13-1a91-4916-811e-1706a21a1a1d", + "blob:http://127.0.0.1:8084/a879f898-011e-4ae8-aa19-b85253bb7902", + "blob:http://127.0.0.1:8084/f6e3c418-1144-44a6-b563-a4e323bf3130", + "blob:http://127.0.0.1:8084/57855c8b-29ed-4ffa-ada3-6e3025488f19", + "blob:http://127.0.0.1:8084/8c7c1ae9-7d71-4cb2-b28b-9721b21eb650", + "blob:http://127.0.0.1:8084/3aaa8a00-220c-4702-adb8-f4128b200ebb", + "blob:http://127.0.0.1:8084/89dcd49b-bbe4-4128-9816-d930440e25ad", + "blob:http://127.0.0.1:8084/16d2cccd-9e13-4fb3-ab5a-ca30869f222d" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/680d8e54-06ec-43b9-9def-a469d5f96535", + "blob:http://127.0.0.1:8084/0bff75f6-7c62-4a5d-afba-d82322f76696", + "blob:http://127.0.0.1:8084/39b0a00d-2e01-4fd9-92fb-56d4abb80ae5", + "blob:http://127.0.0.1:8084/e3ca9c13-1a91-4916-811e-1706a21a1a1d", + "blob:http://127.0.0.1:8084/a879f898-011e-4ae8-aa19-b85253bb7902", + "blob:http://127.0.0.1:8084/f6e3c418-1144-44a6-b563-a4e323bf3130", + "blob:http://127.0.0.1:8084/57855c8b-29ed-4ffa-ada3-6e3025488f19", + "blob:http://127.0.0.1:8084/8c7c1ae9-7d71-4cb2-b28b-9721b21eb650", + "blob:http://127.0.0.1:8084/3aaa8a00-220c-4702-adb8-f4128b200ebb", + "blob:http://127.0.0.1:8084/89dcd49b-bbe4-4128-9816-d930440e25ad", + "blob:http://127.0.0.1:8084/0d2861a9-1c90-4f9d-92f5-0afd643eb498", + "blob:http://127.0.0.1:8084/16d2cccd-9e13-4fb3-ab5a-ca30869f222d" + ], + "events": [ + "file", + "video", + "canvas", + "file", + "video", + "canvas", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "done", + "destroy" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 2715, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-extraction", + "data": { + "width": 240, + "height": 210, + "sourceWidth": 320, + "sourceHeight": 180, + "cellHeight": 45, + "mime": "image/png", + "bytes": 38895, + "nonBlack": 3600, + "createdCount": 21, + "revokedCount": 21, + "doneCount": 2, + "updates": [ + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.7, + 0.8, + 0.9, + 1 + ], + "retiredReadyState": 0, + "retiredSrc": null, + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / DOM drop dispatch uses the registered handler while callable ondrop works", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 514, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 703, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-media-failure", + "data": { + "failed": { + "code": 4, + "toolErrors": 1, + "first": "blob:http://127.0.0.1:8084/e237f480-a2b8-4be7-8420-8246ee0d1de6" + }, + "result": { + "value": "", + "created": [ + "blob:http://127.0.0.1:8084/e237f480-a2b8-4be7-8420-8246ee0d1de6", + "blob:http://127.0.0.1:8084/b78723cd-ade2-44ce-93d7-4234743bb8fc" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/e237f480-a2b8-4be7-8420-8246ee0d1de6", + "blob:http://127.0.0.1:8084/b78723cd-ade2-44ce-93d7-4234743bb8fc" + ] + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native PNG callback delivered after destroy is ignored after cancellation", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 795, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-late-native-blob", + "data": { + "count": 1, + "created": [ + "blob:http://127.0.0.1:8084/5c84b474-91da-4f87-ac86-1d31497ce804" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/5c84b474-91da-4f87-ac86-1d31497ce804" + ], + "pendingState": "rejected", + "events": [ + "file", + "video", + "canvas", + "destroy" + ], + "connected": false, + "bytes": 6496 + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / source replacement cancels held native encoding and completes only the latest job", + "project": "firefox", + "kind": "actual-tool-scenario", + "status": "passed", + "durationMs": 1378, + "browser": "155.0", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-native-source-cancellation", + "data": { + "oldState": "AbortError", + "firstEncodedBytes": 6496, + "source": "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "updateSources": [ + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504" + ], + "created": [ + "blob:http://127.0.0.1:8084/dc6aa7e0-7bb3-4d18-99d0-dafa064bea0d", + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "blob:http://127.0.0.1:8084/9b0fd8ab-d164-4045-b2e4-a8ff35f9ca03", + "blob:http://127.0.0.1:8084/a8c12064-c88b-4f05-b4a1-cd64369a5ab2", + "blob:http://127.0.0.1:8084/1a438d49-5d96-401f-a12c-4f5e1571778e", + "blob:http://127.0.0.1:8084/1e461667-cad4-4ffb-900f-b1481b16786d", + "blob:http://127.0.0.1:8084/70562b90-9d27-41e9-9aff-fd8c8df90366", + "blob:http://127.0.0.1:8084/e00b552c-1726-46ee-8b56-998f7dc5fa26", + "blob:http://127.0.0.1:8084/8114064d-874d-4162-95c0-0d4cda4b84a1", + "blob:http://127.0.0.1:8084/75b59d91-7579-46f0-9bf0-86fe4e8eb2eb", + "blob:http://127.0.0.1:8084/5a6e681d-9d16-4055-8863-6585c420efb9", + "blob:http://127.0.0.1:8084/db554ca4-d96a-4c6a-aa11-aa1ae7943eab" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/dc6aa7e0-7bb3-4d18-99d0-dafa064bea0d", + "blob:http://127.0.0.1:8084/9b0fd8ab-d164-4045-b2e4-a8ff35f9ca03", + "blob:http://127.0.0.1:8084/a8c12064-c88b-4f05-b4a1-cd64369a5ab2", + "blob:http://127.0.0.1:8084/1a438d49-5d96-401f-a12c-4f5e1571778e", + "blob:http://127.0.0.1:8084/1e461667-cad4-4ffb-900f-b1481b16786d", + "blob:http://127.0.0.1:8084/70562b90-9d27-41e9-9aff-fd8c8df90366", + "blob:http://127.0.0.1:8084/e00b552c-1726-46ee-8b56-998f7dc5fa26", + "blob:http://127.0.0.1:8084/8114064d-874d-4162-95c0-0d4cda4b84a1", + "blob:http://127.0.0.1:8084/75b59d91-7579-46f0-9bf0-86fe4e8eb2eb", + "blob:http://127.0.0.1:8084/5a6e681d-9d16-4055-8863-6585c420efb9", + "blob:http://127.0.0.1:8084/3618881e-c7dc-41c1-98b3-1fe65f9a2504", + "blob:http://127.0.0.1:8084/db554ca4-d96a-4c6a-aa11-aa1ae7943eab" + ], + "events": [ + "file", + "video", + "canvas", + "file", + "video", + "canvas", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "update", + "done", + "destroy" + ], + "connected": false + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native file selection, seek extraction, PNG pixels and repeat generation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1428, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / DOM drop dispatch uses the registered handler while callable ondrop works", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1379, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / corrupt media, replacement URLs and repeated destroy reproduce baseline gaps", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1439, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / native PNG callback delivered after destroy is ignored after cancellation", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1445, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + }, + { + "title": "thumbnail-tool.spec.js / Thumbnail tool candidate-artifact / source replacement cancels held native encoding and completes only the latest job", + "project": "webkit", + "kind": "blob-unavailable-control", + "status": "passed", + "durationMs": 1885, + "browser": "26.6", + "platform": "win32", + "measurements": [ + { + "name": "thumbnail-runtime", + "data": { + "name": "candidate-artifact", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6", + "originalNpmArchive": false, + "media": "test\\browser\\media\\thumbnail-pattern.mp4" + } + }, + { + "name": "thumbnail-blob-unavailable", + "data": { + "native": { + "http": "loaded", + "error": 4, + "blob": "error" + }, + "tool": { + "errors": 1, + "processing": false, + "duration": null + } + } + } + ] + } + ] + } + ] + }, + "inputs": [ + { + "file": "packages/artplayer-tool-thumbnail/src/emitter.ts", + "sha256": "2844fa8d279d35d676f61eef4f8a3e173146548c64b8e49f2dabfc25aec56668" + }, + { + "file": "packages/artplayer-tool-thumbnail/src/extraction.ts", + "sha256": "8ef1c824de78b217a0cca80cc18a8c6e1d11bfd1fc460e34b2bee876a8493f34" + }, + { + "file": "packages/artplayer-tool-thumbnail/src/index.ts", + "sha256": "24713364030cfca8201bb564794b612047a517cb9b233889b71bd8b9e14c49a9" + }, + { + "file": "packages/artplayer-tool-thumbnail/src/input.ts", + "sha256": "cafbef270a2c4edbbf2ac3814a9fccf95d805b7158ffd25a42ebc211bf1834ce" + }, + { + "file": "packages/artplayer-tool-thumbnail/src/lifecycle.ts", + "sha256": "3c30d5ad20d89abe80a39abb95270e340c47534e11065047ab191bd5fbecec4a" + }, + { + "file": "packages/artplayer-tool-thumbnail/src/sheet.ts", + "sha256": "af423e53fbd6aec2c54ae74410cf84f99eb8c125605b064aaf6fa1f0bb8f0d45" + }, + { + "file": "packages/artplayer-tool-thumbnail/src/source.ts", + "sha256": "e10348712f5edbdfe1012a527f7c6af83f44f9a7e1dee69a6353c4c095458e54" + }, + { + "file": "packages/artplayer-tool-thumbnail/src/types.ts", + "sha256": "e9a20522b899d6ff48954133518ba1b146685e15d59f89fbf4b5b665a695144c" + }, + { + "file": "packages/artplayer-tool-thumbnail/src/utils.ts", + "sha256": "90d2e1195f7de6c4a5930172baf23b95c2de1cc729375b0d29e30fe43e4dfd13" + }, + { + "file": "packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.js", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b" + }, + { + "file": "docs/compiled/artplayer-tool-thumbnail.js", + "sha256": "714bac5f2065557e005a91226511cc7a7356c4a9edb354adc21fffab495fd96b" + }, + { + "file": "packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.legacy.js", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6" + }, + { + "file": "docs/compiled/artplayer-tool-thumbnail.legacy.js", + "sha256": "563b28e23ccff86cbb9f954b45882b48e90eb31e0827c84eee071577b5e04ec6" + }, + { + "file": "packages/artplayer-tool-thumbnail/dist/artplayer-tool-thumbnail.mjs", + "sha256": "bd23f573a91932b27d05fcd87197d0b0b188e250247c727edb0b48a17bd820ad" + }, + { + "file": "docs/compiled/artplayer-tool-thumbnail.mjs", + "sha256": "bd23f573a91932b27d05fcd87197d0b0b188e250247c727edb0b48a17bd820ad" + }, + { + "file": ".gitattributes", + "sha256": "8f6858f5df7bd86643ae67a62b8381274d01d411ee2b76fcb3bc3484a17ece10" + }, + { + "file": "packages/artplayer-tool-thumbnail/tsconfig.json", + "sha256": "8e0ff87adb1950e96729c262006180a5a0db3fefef69a8195fe1cc40ff47e6d9" + }, + { + "file": "packages/artplayer-tool-thumbnail/THIRD_PARTY_NOTICES", + "sha256": "7bea0832808c3d37b5a79f2ed6c65ef2ebdf7dbcbba124c469ebb90e2ec7c013" + }, + { + "file": "packages/artplayer-tool-thumbnail/ARCHITECTURE.md", + "sha256": "c468987ee4d1b6fcc0333c80b14f93f71cbcf048f136fda7d548710db2191936" + }, + { + "file": "test/thumbnail-runtime.test.js", + "sha256": "4e08e133fd91a273fda298c2a334560ab81585e72f2a85d5249e008449de7451" + }, + { + "file": "test/thumbnail-vendor.test.js", + "sha256": "b9dc2ea786aa89f334d0f06efc708bcee817c8449ef2f4af84c5ff525e59ed18" + }, + { + "file": "test/types/thumbnail-runtime.ts", + "sha256": "8ebc8094aaa01a1ed3cb36663c218a96be9f704d1c4ba02bb8b4003350c2d12c" + }, + { + "file": "test/thumbnail-input.test.js", + "sha256": "08da28330bcd4ef83bddcea3aa10a95ad9ef075bdf3bb7daee734c0807f4b51a" + }, + { + "file": "test/thumbnail-lifecycle.test.js", + "sha256": "decc5f06f343412815f06d30fff5263580ff106eb266eda7c73fb3eec3dc4403" + }, + { + "file": "test/helpers/thumbnail.js", + "sha256": "e547f3fe4581df6298ed448f1f672fe6e1fb501c3fbe60cc9be805a027fdab56" + }, + { + "file": "test/browser/thumbnail-tool.spec.js", + "sha256": "04f322adf9f505f8705253f9225cc92f3bfa8bb1ba9495926a23811b39277089" + }, + { + "file": "test/browser/thumbnail-native.spec.js", + "sha256": "50d2724c4903c2281cf7a5fa8577e4a8174742ac30b48cd3c679eaecc2d27f3e" + }, + { + "file": "test/browser/thumbnail-input.spec.js", + "sha256": "df82785958860b550552507dde59a334d13d916985c0ac85673e210eeaaf3a38" + }, + { + "file": "refactor/baselines/thumbnail-vendor/sources.json", + "sha256": "c40758bc55d6ae4c060c7b73dd836a1e360fcee534877388634001cc592a1c21" + }, + { + "file": "refactor/baselines/thumbnail-vendor/tiny-emitter-2.1.0.txt", + "sha256": "3e3679233f20177d63d253e7c2645024fef51731d05df15621c62a48ba98e0d3" + }, + { + "file": "refactor/baselines/thumbnail-vendor/tiny-emitter-license.txt", + "sha256": "aada4c7da6a5be46beadf9d4f40d7dba9e222e57180d46289b2bb9112e65daae" + }, + { + "file": "refactor/baselines/thumbnail-vendor/tiny-emitter-package.json", + "sha256": "f5328afd1e742ba9b107e316e5fc4c74ec81201bafad8eb8e6dc456ef6d628de" + } + ], + "logs": [ + { + "file": "refactor/.cache/thumbnail04-runtime-build.log", + "sha256": "a725adeb0a0ad31c5232fe0bad6f2d47995ae2ecf28691f25af1090b53a55628" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-source.log", + "sha256": "5fc9c5e546b1ef83a1dcf85058011a55a310c37151d99e0257b7630204c48db2" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-main.log", + "sha256": "560383c7916fc38e79699383ba7e4e7aed7e2f97eb2c1b0f9dce0bcc0296c545" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-legacy.log", + "sha256": "b6af02dd68dcaa0e644fa42a076a8da9a17a43db32a2d29d6236ee9ce7029d6e" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-ci.log", + "sha256": "98134dbe6327f632c43ebb0a81076d32785bc07a35465ed6ae42912e71dd6815" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-imports.log", + "sha256": "f0409b8cfa633b9c53ccc798d633467891d8f8f8f789126ff98686e455d96df0" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-esm.log", + "sha256": "26fdac04ab2b2090bac639ede776fb3329f943164911687b1d12a21aeaaca17b" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-browser-source.log", + "sha256": "dccd305230d30e0f048504ac377aca573a0da55cf1109ea3c3fcc55b7f8e6e0d" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-browser-source.json", + "sha256": "3cf55c2b612b6ea35f0169d2dc0b4c2e4226e77ec50bc64d5b65fb6ade3ba3e2" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-browser-main.log", + "sha256": "d9df5ad7700d61d5d5313aecaed928bebc576d644433981e0e7e427d5192ab42" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-browser-main.json", + "sha256": "c07299cd3ac2aeec1d891c01f244cf13ad6deb4462584eb5a7d919e62b9ea69a" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-browser-legacy.log", + "sha256": "be1409eaa8e238e9317cd5f1b2024671022a9c80ddfeb5a993125170a4791d40" + }, + { + "file": "refactor/.cache/thumbnail04-runtime-browser-legacy.json", + "sha256": "ef19481d67a357d4ba745585dc50ad1710fa81b1e8253689edb0450d113ecb23" + } + ], + "limitations": [ + "Public declarations, legacy TS and installed CommonJS/ESM entrypoints still pending in 04.", + "3.5.31 versus 4.4.0 delay/height default policy remains open.", + "Each browser run includes 17 Windows WebKit Blob-unavailable controls, not successful extraction.", + "Exact original emitter copy revision and complete original npm archive unknown; final packed notice verification pending.", + "No current thumbnail coverage percentage, hosted CI, physical Safari, version bump, push, tag or publication." + ] +} diff --git a/refactor/baselines/thumbnail-vendor/sources.json b/refactor/baselines/thumbnail-vendor/sources.json new file mode 100644 index 000000000..847c6afa1 --- /dev/null +++ b/refactor/baselines/thumbnail-vendor/sources.json @@ -0,0 +1,22 @@ +{ + "reviewedAt": "2026-09-12T20:36:43.265Z", + "referenceVersion": "2.1.0", + "sources": [ + { + "file": "refactor/baselines/thumbnail-vendor/tiny-emitter-2.1.0.txt", + "url": "https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/index.js", + "sha256": "3e3679233f20177d63d253e7c2645024fef51731d05df15621c62a48ba98e0d3" + }, + { + "file": "refactor/baselines/thumbnail-vendor/tiny-emitter-license.txt", + "url": "https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/LICENSE", + "sha256": "aada4c7da6a5be46beadf9d4f40d7dba9e222e57180d46289b2bb9112e65daae" + }, + { + "file": "refactor/baselines/thumbnail-vendor/tiny-emitter-package.json", + "url": "https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/package.json", + "sha256": "f5328afd1e742ba9b107e316e5fc4c74ec81201bafad8eb8e6dc456ef6d628de" + } + ], + "limitation": "Pinned upstream comparison reference. Structural correspondence and the recovered 3.5.31 dependency establish attribution, not the exact original copy revision or complete npm archive." +} diff --git a/refactor/baselines/thumbnail-vendor/tiny-emitter-2.1.0.txt b/refactor/baselines/thumbnail-vendor/tiny-emitter-2.1.0.txt new file mode 100644 index 000000000..7ca4a606e --- /dev/null +++ b/refactor/baselines/thumbnail-vendor/tiny-emitter-2.1.0.txt @@ -0,0 +1,67 @@ +function E () { + // Keep this empty so it's easier to inherit from + // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3) +} + +E.prototype = { + on: function (name, callback, ctx) { + var e = this.e || (this.e = {}); + + (e[name] || (e[name] = [])).push({ + fn: callback, + ctx: ctx + }); + + return this; + }, + + once: function (name, callback, ctx) { + var self = this; + function listener () { + self.off(name, listener); + callback.apply(ctx, arguments); + }; + + listener._ = callback + return this.on(name, listener, ctx); + }, + + emit: function (name) { + var data = [].slice.call(arguments, 1); + var evtArr = ((this.e || (this.e = {}))[name] || []).slice(); + var i = 0; + var len = evtArr.length; + + for (i; i < len; i++) { + evtArr[i].fn.apply(evtArr[i].ctx, data); + } + + return this; + }, + + off: function (name, callback) { + var e = this.e || (this.e = {}); + var evts = e[name]; + var liveEvents = []; + + if (evts && callback) { + for (var i = 0, len = evts.length; i < len; i++) { + if (evts[i].fn !== callback && evts[i].fn._ !== callback) + liveEvents.push(evts[i]); + } + } + + // Remove event from queue to prevent memory leak + // Suggested by https://github.com/lazd + // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910 + + (liveEvents.length) + ? e[name] = liveEvents + : delete e[name]; + + return this; + } +}; + +module.exports = E; +module.exports.TinyEmitter = E; diff --git a/refactor/baselines/thumbnail-vendor/tiny-emitter-license.txt b/refactor/baselines/thumbnail-vendor/tiny-emitter-license.txt new file mode 100644 index 000000000..24f102466 --- /dev/null +++ b/refactor/baselines/thumbnail-vendor/tiny-emitter-license.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2017 Scott Corgan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/refactor/baselines/thumbnail-vendor/tiny-emitter-package.json b/refactor/baselines/thumbnail-vendor/tiny-emitter-package.json new file mode 100644 index 000000000..5da633581 --- /dev/null +++ b/refactor/baselines/thumbnail-vendor/tiny-emitter-package.json @@ -0,0 +1,53 @@ +{ + "name": "tiny-emitter", + "version": "2.1.0", + "description": "A tiny (less than 1k) event emitter library", + "main": "index.js", + "scripts": { + "test-node": "tape test/index.js | tap-format-spec", + "test": "testling | tap-format-spec", + "bundle": "node_modules/.bin/browserify index.js > dist/tinyemitter.js -s TinyEmitter && echo 'Bundled'", + "minify": "node_modules/.bin/uglifyjs dist/tinyemitter.js -o dist/tinyemitter.min.js -m && echo 'Minified'", + "build": "npm test && npm run bundle && npm run minify", + "size": "node_modules/.bin/uglifyjs index.js -o minified.js -m && ls -l && rm minified.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/scottcorgan/tiny-emitter.git" + }, + "keywords": [ + "event", + "emitter", + "pubsub", + "tiny", + "events", + "bind" + ], + "author": "Scott Corgan", + "license": "MIT", + "bugs": { + "url": "https://github.com/scottcorgan/tiny-emitter/issues" + }, + "devDependencies": { + "@tap-format/spec": "0.2.0", + "browserify": "11.2.0", + "tape": "4.2.1", + "testling": "1.7.1", + "uglify-js": "2.5.0" + }, + "testling": { + "files": [ + "test/index.js" + ], + "browsers": [ + "iexplore/10.0", + "iexplore/9.0", + "firefox/16..latest", + "chrome/22..latest", + "safari/5.1..latest", + "ipad/6.0..latest", + "iphone/6.0..latest", + "android-browser/4.2..latest" + ] + } +} diff --git a/refactor/changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md b/refactor/changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md new file mode 100644 index 000000000..5dfe3b82f --- /dev/null +++ b/refactor/changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md @@ -0,0 +1,70 @@ +# PKG-TOOL-THUMB-04 runtime TypeScript checkpoint + +Status: doing. This checkpoint migrates executable source and records emitter +attribution. It does not complete public declarations, installed entrypoints or +the task's compatibility policy gate. + +## Source and compatibility + +Starts at efd9f42fb984302dd061d5cd9f96bd4de74fcccf. Eight runtime JS modules become +strict TS, with a ninth type-only module for configuration, events, frame points, +job controls and lifecycle state. The input and source WeakMaps remain private; +helpers import the facade only as an erased type. Existing runtime validation is +retained. There are no ts-ignore, ts-nocheck or broad any escape hatches. Local +assertions and initialization invariants are explained in the package architecture +map. Unused sleep and serial-promise helpers are removed; neither was reachable +from the public bundle or used by the owned extraction job. + +declare fields preserve the old own-property order and lazy optional fields. +Normal class exports, historical creat* spellings, synchronous validation, +Promise completion, cancellation and the workspace 4.4.0 defaults remain. +Source options accept Element wrappers and unknown extension fields. Known event +tuples are checked and unknown string/number/symbol events remain possible. +The source types are internal until the package's declaration entry is supplied. + +## Emitter origin + +Pinned upstream comparison: tiny-emitter 2.1.0 index.js, package.json and LICENSE, +fetched from its fixed Git tag and recorded with SHA-256. Its four methods and +listener registry structure correspond to the local class; the recovered 3.5.31 +manifest also declared tiny-emitter ^2.1.0. This establishes the appropriate +attribution, not an exact original copied revision or a recovered complete npm +archive. Changes from that comparison reference are the existing class/ES module +conversion, rest arguments, local names and loop/style forms, plus this migration's +erased generic types and equivalent typed local aliases. + +The complete upstream MIT notice is retained in package THIRD_PARTY_NOTICES and +the existing normal build banner includes it in all three standalone artifacts. +No new runtime dependency is introduced. VENDOR-09 records final packed-content +verification still outstanding; THUMB-PROVENANCE-01 retains the historical archive +limit. Source attribution must not be replaced with the repository author's name. + +## Validation + +The validation JSON for this checkpoint records final commands, file hashes and +browser outcomes. Six new runtime tests check public descriptors/defaults/unknown +options/validation and compare upstream, recovered 3.5.31, frozen workspace main +and legacy, and the selected candidate for emitter snapshot/removal/ctx/symbol/ +exception behavior. Two provenance tests verify fixed upstream bytes and full +notice retention in package and dist/docs copies. Existing lifecycle/input tests +remain unchanged. Source type consumers include six negative uses. + +Browser runs retain native media controls. Windows WebKit Blob decoding remains +an unavailable-capability control, not successful thumbnail extraction. No new +coverage percentage, physical Safari, remote GitHub CI or npm result is claimed. + +## Continue / rollback + +Continue 04 with the missing public declaration, legacy compiler and isolated +installed CommonJS/ESM consumers. Resolve or explicitly request a decision for +THUMB-COMPAT-01 (recovered 3.5.31 delay/fixed height versus workspace 4.4.0 defaults) +before claiming compatibility closure. Review custom emitter typing and dispatch +boundaries alongside those consumers. BASE-DIST-01 still covers the missing +promised .esm.js and types entries; task 06 owns final distribution acceptance. +05/06 retain supported WebKit/Safari file extraction and core integration/demo +verification. No manifest entry, version, lockfile or package manager changes in +this checkpoint. No push, tag or publication. + +To roll back, revert this checkpoint commit and rebuild the tool using the normal +Yarn package build. Preserve upstream provenance/notice records for any retained +copy of the emitter. Do not revert the separate 03 resource fixes accidentally. diff --git a/refactor/plan.md b/refactor/plan.md index eeec3d427..90d7eb066 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。 -状态:todo 100 / doing 10 / blocked 0 / done 112 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 99 / doing 11 / blocked 0 / done 112 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -358,7 +358,7 @@ | PKG-TOOL-THUMB-01 | artplayer-tool-thumbnail
核对包契约与历史用法 | BASE-05 | 构造/defaults/事件/方法/历史拼写、.esm.js 入口和缺失 types | 源码/声明/README/demo/可恢复发布内容差异已登记;公开形状和版本范围冻结;区分旧 npm 的 main/CSS 与当前工作区无 main、.esm.js/类型缺失,不把工作区状态冒称旧 npm 发布事实 | H | done | | PKG-TOOL-THUMB-02 | artplayer-tool-thumbnail
建立特有行为与错误测试 | PKG-TOOL-THUMB-01, ENG-03, ENG-05 | 文件输入/拖放、抽帧网格、begin/end、进度/失败/重复任务 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done | | PKG-TOOL-THUMB-03 | artplayer-tool-thumbnail
整理内部职责与资源 | PKG-TOOL-THUMB-02, CORE-01 | 输入、抽帧队列、网格导出、URL/监听清理分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done | -| PKG-TOOL-THUMB-04 | artplayer-tool-thumbnail
迁移自有源码和公开类型 | PKG-TOOL-THUMB-03, ENG-04, ENG-06, CORE-07 | 补齐真实 API 声明,自有 emitter/utils TS 化 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;为 BASE-DIST-01 提供兼容入口/声明及消费者验证 | H | todo | +| PKG-TOOL-THUMB-04 | artplayer-tool-thumbnail
迁移自有源码和公开类型 | PKG-TOOL-THUMB-03, ENG-04, ENG-06, CORE-07 | 补齐真实 API 声明,自有 emitter/utils TS 化 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;为 BASE-DIST-01 提供兼容入口/声明及消费者验证 | H | doing | | PKG-TOOL-THUMB-05 | artplayer-tool-thumbnail
验证新旧核心和组合 | PKG-TOOL-THUMB-04, CORE-22 | 工具独立浏览器使用,生成缩略图在核心中显示 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | | PKG-TOOL-THUMB-06 | artplayer-tool-thumbnail
验证分发并同步文档 | PKG-TOOL-THUMB-05, ENG-07 | tool.thumbnail.js、历史 ESM 兼容文件、types 路径和 tarball | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo | @@ -538,4 +538,5 @@ - PKG-TOOL-THUMB-01: [记录](baselines/thumbnail-release.json) [记录](baselines/thumbnail-contract.md) [记录](changes/2026-09-13-PKG-TOOL-THUMB-01-contract.md) [记录](baselines/thumbnail-contract-validation.json) - PKG-TOOL-THUMB-02: [记录](baselines/thumbnail-behavior-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md) - PKG-TOOL-THUMB-03: [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-input-checkpoint.md) [记录](baselines/thumbnail-input-checkpoint.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-lifecycle.md) [记录](baselines/thumbnail-lifecycle-validation.json) +- PKG-TOOL-THUMB-04: [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md) [记录](baselines/thumbnail-runtime-types-validation.json) - PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md) diff --git a/refactor/progress.md b/refactor/progress.md index d06a88569..e34950bb1 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,19 @@ # 进度与证据 +## PKG-TOOL-THUMB-04 运行时 TS 与 emitter 来源检查点(doing) + +8个运行时模块已迁为严格TS,新增类型模块明确option/事件/帧/job/资源状态;declare保留 +旧实例字段顺序和按需出现的字段。移除未使用的sleep/serial助手。生成ESM除许可和两个 +等价emitter局部变量外与03运行时相同,默认值和同步/异步行为保持。固定tiny-emitter +2.1.0上游比较文件及完整MIT通知,登记VENDOR-09;不声称确定原始复制revision。 +新增6项门面/emitter兼容与2项来源/通知检查,源码/main/legacy各110项通过。 +三份浏览器各57通过(34真实工具、3输入、17Blob不可用、3原生媒体对照)。 +完整CI1654通过,339生产TS和3导入通过;保留既有编辑器声明lint警告及Yarn警告记录。 +公开声明/旧编译器/实际安装入口和默认历史差异仍待04继续,05/06保留真实设备/分发门槛。 +见[变更](changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md)和 +[验证](baselines/thumbnail-runtime-types-validation.json)。222项:112 done、11 doing、99 todo。 + + ## PKG-TOOL-THUMB-03 完成:抽帧任务与媒体资源所有权 增加lifecycle/source/extraction模块,保留公开类形状与工作区默认行为。元数据等待、 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index 367750b7d..08b08d3f3 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -181,3 +181,4 @@ | THUMB-LIFE-01 | open / 已复现 | Thumbnail drop binding and repeated destroy fail; pending work and replacement resource ownership need repair | PKG-TOOL-THUMB-02, PKG-TOOL-THUMB-03, PKG-TOOL-THUMB-05 | | THUMB-PROVENANCE-01 | open / 源码/产物事实 | Thumbnail local emitter attribution and historical distribution completeness remain unverified | PKG-TOOL-THUMB-04, PKG-TOOL-THUMB-06 | | THUMB-MEDIA-01 | open / 已复现 | Windows WebKit native Blob URLs cannot load tested MP4 media despite successful HTTP controls | PKG-TOOL-THUMB-05, PKG-TOOL-THUMB-06 | +| VENDOR-09 | open / 源码/产物事实 | Thumbnail tiny-emitter 改写来源与最终分发许可 | PKG-TOOL-THUMB-04, PKG-TOOL-THUMB-06 | diff --git a/refactor/risks.json b/refactor/risks.json index 7db012a8f..10fe472bc 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -3934,6 +3934,25 @@ ], "compatibleResolution": "Preserve independent HTTP/Blob capability controls; obtain actual supported WebKit/Safari file extraction evidence before release acceptance. Do not count the 12 unavailable controls as successful thumbnail generation or silently replace local files with HTTP media.", "closureCriteria": "Actual generated PNG/extraction and lifecycle proof in supported Safari/WebKit environments, or explicit supported-environment decision with migration documentation; Windows limitation stays identifiable." + }, + { + "id": "VENDOR-09", + "title": "Thumbnail tiny-emitter 改写来源与最终分发许可", + "confirmation": "source-observed", + "status": "open", + "owners": [ + "PKG-TOOL-THUMB-04", + "PKG-TOOL-THUMB-06" + ], + "evidence": [ + "refactor/baselines/thumbnail-vendor/sources.json", + "packages/artplayer-tool-thumbnail/src/emitter.ts", + "packages/artplayer-tool-thumbnail/THIRD_PARTY_NOTICES", + "test/thumbnail-vendor.test.js" + ], + "compatibleResolution": "Preserve original attribution, typed local dispatch compatibility and complete MIT notice in source/package/standalone bundles.", + "closureCriteria": "Pinned upstream reference and local adaptation reviewed; all bundle and actual packed distribution notices verified. Do not claim the exact original copied revision or a recovered complete historical archive.", + "workspaceState": "TS adaptation and pinned 2.1.0 source/license references recorded; normal build includes package notices. Final installed/packed distribution evidence remains with 04/06." } ] } diff --git a/refactor/tasks.json b/refactor/tasks.json index 51458a007..3196990ef 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -3809,11 +3809,14 @@ "ENG-06", "CORE-07" ], - "status": "todo", + "status": "doing", "risk": "H", "deliverable": "补齐真实 API 声明,自有 emitter/utils TS 化", "acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;为 BASE-DIST-01 提供兼容入口/声明及消费者验证", - "evidence": [] + "evidence": [ + "changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md", + "baselines/thumbnail-runtime-types-validation.json" + ] }, { "id": "PKG-TOOL-THUMB-05", diff --git a/refactor/third-party.json b/refactor/third-party.json index 037a7b439..3acc7b2ca 100644 --- a/refactor/third-party.json +++ b/refactor/third-party.json @@ -1025,6 +1025,39 @@ "sha256": "e00bbf82bf08c452825690372ded39a9b60f4baf438f0e7ce8300e9bc4629c03" } ] + }, + { + "id": "thumbnail-tiny-emitter", + "riskId": "VENDOR-09", + "inputs": [ + "packages/artplayer-tool-thumbnail/src/emitter.ts" + ], + "owners": [ + "PKG-TOOL-THUMB-04", + "PKG-TOOL-THUMB-06" + ], + "sourceStatus": "Local on/once/emit/off structurally correspond to the fixed tiny-emitter 2.1.0 reference; recovered 3.5.31 declared ^2.1.0. Exact original copied revision is unknown. This migration adds types while preserving the local class and dispatch behavior.", + "licenseStatus": "Pinned upstream MIT notice copied verbatim into package THIRD_PARTY_NOTICES and included by the normal standalone build banner. Final packed distribution verification remains 06.", + "upstreamSources": [ + "https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/index.js", + "https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/LICENSE", + "https://raw.githubusercontent.com/scottcorgan/tiny-emitter/2.1.0/package.json" + ], + "upstreamReviewedAt": "2026-09-13", + "updatePolicy": "Keep attribution and pinned upstream comparison. Review observable dispatch changes independently from type migration; run upstream/historical/candidate parity tests and validate all bundle and packed notices.", + "fingerprints": [ + { + "path": "packages/artplayer-tool-thumbnail/src/emitter.ts", + "algorithm": "sha256-lf", + "sha256": "2844fa8d279d35d676f61eef4f8a3e173146548c64b8e49f2dabfc25aec56668" + } + ], + "reviewEvidence": [ + "refactor/baselines/thumbnail-vendor/sources.json", + "test/thumbnail-runtime.test.js", + "test/thumbnail-vendor.test.js", + "refactor/changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md" + ] } ], "integrations": [ diff --git a/test/thumbnail-runtime.test.js b/test/thumbnail-runtime.test.js new file mode 100644 index 000000000..4786116a0 --- /dev/null +++ b/test/thumbnail-runtime.test.js @@ -0,0 +1,131 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +// eslint-disable-next-line test/no-import-node-test -- Runtime compatibility tests. +import test from 'node:test' +import vm from 'node:vm' +import { thumbnailCandidate, thumbnailEnvironment, thumbnailHistorical } from './helpers/thumbnail.js' + +const candidate = await thumbnailCandidate() +function create(implementation) { + const env = thumbnailEnvironment(implementation) + return { ...env, tool: new env.Factory({ fileInput: new env.Element('input') }) } +} +const referenceModule = { exports: {} } +vm.runInNewContext(fs.readFileSync(new URL('../refactor/baselines/thumbnail-vendor/tiny-emitter-2.1.0.txt', import.meta.url), 'utf8'), { module: referenceModule }) +const Upstream = referenceModule.exports +const upstream = () => new Upstream() +const emitters = [upstream, ...thumbnailHistorical().map(implementation => () => create(implementation).tool), () => create(candidate).tool] + +test('Thumbnail typed facade preserves descriptors, lazy fields, default class and unknown options', () => { + const { tool, Factory, exported } = create(candidate) + const baseline = create(thumbnailHistorical().find(item => item.name === 'workspace.js')) + const describe = object => Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(object)).map(([name, value]) => [name, { + enumerable: value.enumerable, + configurable: value.configurable, + writable: value.writable, + getter: typeof value.get, + setter: typeof value.set, + type: typeof value.value, + arity: typeof value.value === 'function' ? value.value.length : undefined, + }])) + assert.deepEqual(describe(Factory.prototype), describe(baseline.Factory.prototype)) + assert.deepEqual(describe(Factory), describe(baseline.Factory)) + assert.deepEqual(describe(tool), describe(baseline.tool)) + assert.deepEqual(Object.keys(tool), ['processing', 'option', 'video', 'duration', 'inputChange', 'ondrop']) + assert.equal(exported.default, undefined) + assert.equal(typeof thumbnailEnvironment(candidate, true).exported, 'function') + const metadata = { id: 123 } + assert.equal(tool.setup({ custom: metadata, width: 21.5 }), tool) + assert.equal(tool.option.custom, metadata) + assert.equal(tool.option.width, 21.5) + assert.notEqual(Factory.DEFAULTS, Factory.DEFAULTS) + assert.deepEqual(Object.keys(Factory.DEFAULTS), Object.keys(baseline.Factory.DEFAULTS)) + assert(Number.isNaN(Factory.DEFAULTS.end)) + tool.destroy() +}) + +test('Thumbnail emitter keeps upstream snapshot order, duplicate removal and original once callback identity', () => { + for (const create of emitters) { + const emitter = create() + const seen = [] + const callback = value => seen.push(value) + assert.equal(emitter.on('value', () => emitter.off('value', callback)), emitter) + emitter.on('value', callback).once('value', callback).on('value', callback) + emitter.emit('value', 7).emit('value', 8) + assert.deepEqual(seen, [7, 7, 7]) + assert.equal(emitter.off('value'), emitter) + assert.equal(emitter.e.value, undefined) + emitter.once('removed', callback).off('removed', callback).emit('removed', 9) + assert.deepEqual(seen, [7, 7, 7]) + } +}) + +test('Thumbnail emitter preserves ctx, variadic arguments, symbol events and ignored callback returns', () => { + for (const create of emitters) { + const emitter = create() + const ctx = { calls: 0 } + const name = Symbol('event') + function callback(...args) { + assert.equal(this, ctx) + assert.deepEqual(args, [1, 'two', null]) + this.calls++ + return 123 + } + assert.equal(emitter.on(name, callback, ctx), emitter) + assert.equal(emitter.once(name, callback, ctx), emitter) + assert.equal(emitter.emit(name, 1, 'two', null), emitter) + assert.equal(ctx.calls, 2) + emitter.off(name, callback).emit(name) + assert.equal(ctx.calls, 2) + } +}) + +test('Thumbnail emitter synchronous listener failures retain identity and stop the current snapshot', () => { + for (const create of emitters) { + const emitter = create() + const failure = { reason: 'listener failure' } + let later = 0 + emitter.once('event', () => { + throw failure + }) + emitter.on('event', () => later++) + assert.throws(() => emitter.emit('event'), error => error === failure) + assert.equal(later, 0) + emitter.emit('event') + assert.equal(later, 1) + } +}) + +test('Thumbnail emitter additions during dispatch wait until the next snapshot', () => { + for (const create of emitters) { + const emitter = create() + const seen = [] + emitter.once('event', () => { + seen.push('first') + emitter.on('event', () => seen.push('new')) + }) + emitter.on('event', () => seen.push('second')) + emitter.emit('event') + assert.deepEqual(seen, ['first', 'second']) + emitter.emit('event') + assert.deepEqual(seen, ['first', 'second', 'second', 'new']) + } +}) + +test('Thumbnail typed facade retains synchronous validation, option mutation and grid geometry', () => { + const { tool } = create(candidate) + tool.setup({ number: 10, width: 20, height: 30, column: 3, begin: 5 }) + tool.duration = 100 + assert.deepEqual(JSON.parse(JSON.stringify(tool.creatScreenshotDate())), Array.from({ length: 10 }, (_, i) => ({ time: 10 + i * 10, x: i % 3 * 20, y: Math.floor(i / 3) * 30 }))) + const canvas = tool.creatCanvas() + assert.equal(canvas.width, 60) + assert.equal(canvas.height, 150) + const errors = [] + tool.on('error', value => errors.push(value)) + assert.throws(() => tool.setup({ width: '80' }), /not a number/) + assert.equal(tool.option.width, 20) + assert.throws(() => tool.download(), /does not seem to be ready/) + assert.equal(errors.length, 2) + assert.equal(tool.errorHandle(true, 'unused'), undefined) + tool.destroy() +}) diff --git a/test/thumbnail-vendor.test.js b/test/thumbnail-vendor.test.js new file mode 100644 index 000000000..34c2b7c13 --- /dev/null +++ b/test/thumbnail-vendor.test.js @@ -0,0 +1,31 @@ +import assert from 'node:assert/strict' +import { createHash } from 'node:crypto' +import fs from 'node:fs' +// eslint-disable-next-line test/no-import-node-test -- Pinned provenance and distributable notice checks. +import test from 'node:test' + +const read = file => fs.readFileSync(new URL(`../${file}`, import.meta.url), 'utf8') +const base = 'refactor/baselines/thumbnail-vendor/' +const license = read(`${base}tiny-emitter-license.txt`).replaceAll('\r\n', '\n').trim() + +test('Thumbnail emitter reference files retain pinned bytes, version and full upstream attribution', () => { + const manifest = JSON.parse(read(`${base}sources.json`)) + for (const source of manifest.sources) + assert.equal(createHash('sha256').update(fs.readFileSync(new URL(`../${source.file}`, import.meta.url))).digest('hex'), source.sha256) + const pkg = JSON.parse(read(`${base}tiny-emitter-package.json`)) + assert.equal(pkg.name, 'tiny-emitter') + assert.equal(pkg.version, '2.1.0') + assert.equal(pkg.license, 'MIT') + assert.match(license, /Copyright \(c\) 2017 Scott Corgan/) + assert(read('packages/artplayer-tool-thumbnail/THIRD_PARTY_NOTICES').replaceAll('\r\n', '\n').includes(license)) +}) + +test('Thumbnail standalone distributions retain the complete notice and equal their docs copies', () => { + for (const suffix of ['.js', '.legacy.js', '.mjs']) { + const filename = `artplayer-tool-thumbnail${suffix}` + const bundle = read(`packages/artplayer-tool-thumbnail/dist/${filename}`) + const banner = bundle.match(/^\/\*![\s\S]*?\*\//)?.[0].replace(/^ \* ?/gm, '').replaceAll('\r\n', '\n') + assert(banner?.includes(license), `${filename} must carry the complete upstream notice`) + assert.equal(read(`docs/compiled/${filename}`), bundle) + } +}) diff --git a/test/types/thumbnail-runtime.ts b/test/types/thumbnail-runtime.ts new file mode 100644 index 000000000..362ab9820 --- /dev/null +++ b/test/types/thumbnail-runtime.ts @@ -0,0 +1,45 @@ +import Thumbnail from '../../packages/artplayer-tool-thumbnail/src' + +const input = document.createElement('input') +const tool = new Thumbnail({ fileInput: input, number: 10, tracking: { id: 'example' } }) +const same: Thumbnail = tool.setup({ width: 80 }).on('update', (url, progress) => { + const value: string = url + const percent: number = progress + void [value, percent] +}) +tool.once('file', function (file) { + this.name = file.name +}, { name: '' }) +tool.on('error', (message) => { + const value: string | undefined = message + void value +}) +tool.on('custom', (...payload) => { + const values: unknown[] = payload + void values +}) +tool.emit(Symbol('extension'), 'value') +const pending: Promise = tool.start() +const point: { time: number, x: number, y: number } | undefined = tool.creatScreenshotDate()[0] +const canvas: HTMLCanvasElement = tool.creatCanvas() +const defaults: number = Thumbnail.DEFAULTS.height +const wrapper = document.createElementNS('http://www.w3.org/2000/svg', 'svg') +tool.setup({ fileInput: wrapper }) +tool.loadVideo(input.files?.[0]) +tool.loadVideo(null) +const destroyed: void = tool.destroy() +void [same, pending, point, canvas, defaults, destroyed] + +// @ts-expect-error Numeric options cannot be strings. +tool.setup({ width: '80' }) +// @ts-expect-error A file-input selector string has never been supported. +const invalidInput = new Thumbnail({ fileInput: '#input' }) +// @ts-expect-error Known update events carry a URL followed by numeric progress. +tool.emit('update', 123, '50%') +// @ts-expect-error Known callbacks must accept their actual payload. +tool.on('video', (file: File) => file.name) +// @ts-expect-error The public start result is completion, not the thumbnail URL. +const incorrect: Promise = tool.start() +// @ts-expect-error Runtime ESM exposes only a default class, no .default self-alias. +const InvalidAlias = Thumbnail.default +void [incorrect, invalidInput, InvalidAlias]