diff --git a/docs/compiled/artplayer-plugin-vtt-thumbnail.js b/docs/compiled/artplayer-plugin-vtt-thumbnail.js index 502865ead..6433dcb4d 100644 --- a/docs/compiled/artplayer-plugin-vtt-thumbnail.js +++ b/docs/compiled/artplayer-plugin-vtt-thumbnail.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVttThumbnail=e(),define(function(){return t.artplayerPluginVttThumbnail})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVttThumbnail=e()}(this,function(){"use strict";function t(t){const e=t.split("."),n=e[0].split(":")||[],o=(l=e[1]||"0",i=3,r="0",l.length>i?String(l):((i-=l.length)>r.length&&(r+=r.repeat(i/r.length)),String(l)+r.slice(0,i)));var l,i,r;const s=Number(o)/1e3;return 3600*Number(n[n.length-3]||0)+60*Number(n[n.length-2]||0)+Number(n[n.length-1]||0)+s}function e({lifetime:t,thumbnails:e,progress:n,duration:o,setStyle:l,isMobile:i}){let r=null,s=0;function c(e,n,o){t.closed||l(e,n,o)}return t.own(()=>{if(s++,null!==r){const t=r;r=null,clearTimeout(t)}}),l=>async(u,a,d)=>{if(t.closed)return;const f="played"===u&&d&&i;if("hover"!==u&&!f)return;const h=n.clientWidth*a,p=a*o();if(c(l,"display","flex"),t.closed)return;const m=e.find(t=>p>=t.start&&p<=t.end);if(!m)return c(l,"display","none");if(h>0&&hn.clientWidth-e.w/2?c(t,"left",n.clientWidth-e.w+"px"):c(t,"left",o-e.w/2+"px")}(l,m,h):i||c(l,"display","none"),f&&!t.closed){const e=++s;if(null!==r&&clearTimeout(r),t.closed)return;const n=setTimeout(()=>{e!==s||t.closed||(r=null,c(l,"display","none"))},500);t.closed||e!==s?clearTimeout(n):r=n}}}return function(n){return async o=>{const{constructor:{utils:{setStyle:l,isMobile:i,addClass:r}},template:{$progress:s}}=o,c=function(t){let e=Boolean(t.isDestroy);const n=new Set;let o;const l=new Promise(t=>{o=t}),i={get closed(){return e},own:t=>(e?r(t):n.add(t),()=>n.delete(t)),listen(n,o){e||(i.own(()=>t.off(n,o)),t.on(n,o))},wait:t=>Promise.race([t,l]),dispose(){if(e)return;e=!0,o();const t=[...n].reverse();n.clear();for(const e of t)r(e)}};function r(t){try{t()}catch(e){console.warn("Failed to clean up VTT thumbnails:",e)}}try{i.listen("destroy",i.dispose)}catch(s){throw i.dispose(),s}return i}(o),u={name:"artplayerPluginVttThumbnail"};try{if(c.closed)return u;const a=n.vtt,d=await async function(t="",e){if(e.closed)return;const n="function"===typeof AbortController?new AbortController:null,o=e.own(()=>n?.abort());try{if(e.closed)return;const o=await e.wait(n?fetch(t,{signal:n.signal}):fetch(t));if(e.closed)return;if(!1===o.ok)throw new Error(`Failed to fetch VTT thumbnails: HTTP ${o.status}`);if(e.closed)return;return await e.wait(o.text())}finally{o()}}(a,c);if(c.closed)return u;const f=function(e,n=""){const o=e.split(/[\n\r]/g).filter(t=>t.trim()),l=[];for(let i=1;i ?((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?)/,c=e.match(s),u=/(.*)#(\w{4})=(.*)/,a=r.match(u),d=Math.floor(t(c[1])),f=Math.floor(t(c[2]));let h=a[1];if(!/^\/|(?:https?|ftp|file):\/\//i.test(h)){const t=n.split("/");t.pop(),t.push(h),h=t.join("/")}const p={start:d,end:f,url:h},m=a[2].split(""),y=a[3].split(",");for(let t=0;to.duration,setStyle:l,isMobile:i}),p=n.style||{};return c.closed||o.controls.add({name:"vtt-thumbnail",position:"top",index:20,style:p,mounted(t){c.own(()=>{o.controls["vtt-thumbnail"]===t&&o.controls.remove("vtt-thumbnail")}),c.closed||(r(t,"art-control-thumbnails"),c.listen("setBar",h(t)))}}),u}catch(a){throw c.dispose(),a}}}}); +!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVttThumbnail=e(),define(function(){return t.artplayerPluginVttThumbnail})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVttThumbnail=e()}(this,function(){"use strict";function t(t,e){throw new TypeError(`Invalid VTT thumbnail at line ${t}: ${e}`)}function e(e,n){/^\d{2,}(?::\d{2}){0,2}(?:\.\d{3})?$/.test(e)||t(n,"invalid timestamp");const[i,o="0"]=e.split("."),r=i.split(":").reduce((t,e)=>60*t+Number(e),0)+Number(o)/1e3;return Number.isSafeInteger(Math.floor(r))||t(n,"timestamp exceeds the supported numeric range"),r}function n(e,n,i){const o=e.match(/^(.+)#([xywh]{4})=(.*)$/);o&&4===new Set(o[2]).size||t(i,"expected an image URL and four distinct xywh keys");const r=o[3].split(",").map(t=>t.trim());4!==r.length&&t(i,"expected four rectangle coordinates");const s={};for(let c=0;c<4;c++){const e=o[2][c],n=r[c];/^(?:\d+(?:\.\d+)?|\.\d+)$/.test(n)&&Number.isFinite(Number(n))||t(i,"rectangle coordinates must be finite non-negative decimals"),"w"!==e&&"h"!==e||0!==Number(n)||t(i,"rectangle width and height must be positive"),s[e]=n}let l=o[1];if(!/^\/|(?:https?|ftp|file):\/\//i.test(l)){const t=n.split("/");t.pop(),t.push(l),l=t.join("/")}return{url:l,...s}}function i({lifetime:t,thumbnails:e,progress:n,duration:i,setStyle:o,isMobile:r}){let s=null,l=0;function c(e,n,i){t.closed||o(e,n,i)}return t.own(()=>{if(l++,null!==s){const t=s;s=null,clearTimeout(t)}}),o=>async(u,a,d)=>{if(t.closed)return;const f="played"===u&&d&&r;if("hover"!==u&&!f)return;const m=n.clientWidth*a,h=a*i();if(c(o,"display","flex"),t.closed)return;const p=function(t,e){return t.find(t=>e>=t.start&&e<=t.end)}(e,h);if(!p)return c(o,"display","none");if(m>0&&mn.clientWidth-e.w/2?c(t,"left",n.clientWidth-e.w+"px"):c(t,"left",i-e.w/2+"px")}(o,p,m):r||c(o,"display","none"),f&&!t.closed){const e=++l;if(null!==s&&clearTimeout(s),t.closed)return;const n=setTimeout(()=>{e!==l||t.closed||(s=null,c(o,"display","none"))},500);t.closed||e!==l?clearTimeout(n):s=n}}}return function(o){return async r=>{const{constructor:{utils:{setStyle:s,isMobile:l,addClass:c}},template:{$progress:u}}=r,a=function(t){let e=Boolean(t.isDestroy);const n=new Set;let i;const o=new Promise(t=>{i=t}),r={get closed(){return e},own:t=>(e?s(t):n.add(t),()=>n.delete(t)),listen(n,i){e||(r.own(()=>t.off(n,i)),t.on(n,i))},wait:t=>Promise.race([t,o]),dispose(){if(e)return;e=!0,i();const t=[...n].reverse();n.clear();for(const e of t)s(e)}};function s(t){try{t()}catch(e){console.warn("Failed to clean up VTT thumbnails:",e)}}try{r.listen("destroy",r.dispose)}catch(l){throw r.dispose(),l}return r}(r),d={name:"artplayerPluginVttThumbnail"};try{if(a.closed)return d;const f=o.vtt,m=await async function(t="",e){if(e.closed)return;const n="function"===typeof AbortController?new AbortController:null,i=e.own(()=>n?.abort());try{if(e.closed)return;const i=await e.wait(n?fetch(t,{signal:n.signal}):fetch(t));if(e.closed)return;if(!1===i.ok)throw new Error(`Failed to fetch VTT thumbnails: HTTP ${i.status}`);if(e.closed)return;return await e.wait(i.text())}finally{i()}}(f,a);if(a.closed)return d;const h=function(i,o=""){"string"!==typeof i&&t(1,"expected text");const r=i.replace(/^\uFEFF/,"").split(/\r\n|\r|\n/);let s=0;function l(){for(;s[\t ]*([\d:.]+)(?:[\t ].*)?$/);d||t(a,"invalid cue timing");const f=e(d[1],a),m=e(d[2],a);mr.duration,setStyle:s,isMobile:l}),g=o.style||{};return a.closed||r.controls.add({name:"vtt-thumbnail",position:"top",index:20,style:g,mounted(t){a.own(()=>{r.controls["vtt-thumbnail"]===t&&r.controls.remove("vtt-thumbnail")}),a.closed||(c(t,"art-control-thumbnails"),a.listen("setBar",p(t)))}}),d}catch(f){throw a.dispose(),f}}}}); diff --git a/docs/compiled/artplayer-plugin-vtt-thumbnail.legacy.js b/docs/compiled/artplayer-plugin-vtt-thumbnail.legacy.js index 53193cab0..e2631f933 100644 --- a/docs/compiled/artplayer-plugin-vtt-thumbnail.legacy.js +++ b/docs/compiled/artplayer-plugin-vtt-thumbnail.legacy.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVttThumbnail=e(),define(function(){return t.artplayerPluginVttThumbnail})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVttThumbnail=e()}(this,function(){"use strict";var t=(t,e,n)=>new Promise((o,l)=>{var i=t=>{try{s(n.next(t))}catch(e){l(e)}},r=t=>{try{s(n.throw(t))}catch(e){l(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(i,r);s((n=n.apply(t,e)).next())});function e(t){const e=t.split("."),n=e[0].split(":")||[],o=(l=e[1]||"0",i=3,r="0",l.length>i?String(l):((i-=l.length)>r.length&&(r+=r.repeat(i/r.length)),String(l)+r.slice(0,i)));var l,i,r;const s=Number(o)/1e3;return 3600*Number(n[n.length-3]||0)+60*Number(n[n.length-2]||0)+Number(n[n.length-1]||0)+s}function n({lifetime:e,thumbnails:n,progress:o,duration:l,setStyle:i,isMobile:r}){let s=null,u=0;function c(t,n,o){e.closed||i(t,n,o)}return e.own(()=>{if(u++,null!==s){const t=s;s=null,clearTimeout(t)}}),i=>(a,d,f)=>t(null,null,function*(){if(e.closed)return;const t="played"===a&&f&&r;if("hover"!==a&&!t)return;const h=o.clientWidth*d,p=d*l();if(c(i,"display","flex"),e.closed)return;const m=n.find(t=>p>=t.start&&p<=t.end);if(!m)return c(i,"display","none");if(h>0&&ho.clientWidth-e.w/2?c(t,"left",o.clientWidth-e.w+"px"):c(t,"left",n-e.w/2+"px")}(i,m,h):r||c(i,"display","none"),t&&!e.closed){const t=++u;if(null!==s&&clearTimeout(s),e.closed)return;const n=setTimeout(()=>{t!==u||e.closed||(s=null,c(i,"display","none"))},500);e.closed||t!==u?clearTimeout(n):s=n}})}return function(o){return l=>t(null,null,function*(){const{constructor:{utils:{setStyle:i,isMobile:r,addClass:s}},template:{$progress:u}}=l,c=function(t){let e=Boolean(t.isDestroy);const n=new Set;let o;const l=new Promise(t=>{o=t}),i={get closed(){return e},own:t=>(e?r(t):n.add(t),()=>n.delete(t)),listen(n,o){e||(i.own(()=>t.off(n,o)),t.on(n,o))},wait:t=>Promise.race([t,l]),dispose(){if(e)return;e=!0,o();const t=[...n].reverse();n.clear();for(const e of t)r(e)}};function r(t){try{t()}catch(e){console.warn("Failed to clean up VTT thumbnails:",e)}}try{i.listen("destroy",i.dispose)}catch(s){throw i.dispose(),s}return i}(l),a={name:"artplayerPluginVttThumbnail"};try{if(c.closed)return a;const d=o.vtt,f=yield function(e="",n){return t(this,null,function*(){if(n.closed)return;const t="function"===typeof AbortController?new AbortController:null,o=n.own(()=>null==t?void 0:t.abort());try{if(n.closed)return;const o=yield n.wait(t?fetch(e,{signal:t.signal}):fetch(e));if(n.closed)return;if(!1===o.ok)throw new Error(`Failed to fetch VTT thumbnails: HTTP ${o.status}`);if(n.closed)return;return yield n.wait(o.text())}finally{o()}})}(d,c);if(c.closed)return a;const h=function(t,n=""){const o=t.split(/[\n\r]/g).filter(t=>t.trim()),l=[];for(let i=1;i ?((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?)/,u=t.match(s),c=/(.*)#(\w{4})=(.*)/,a=r.match(c),d=Math.floor(e(u[1])),f=Math.floor(e(u[2]));let h=a[1];if(!/^\/|(?:https?|ftp|file):\/\//i.test(h)){const t=n.split("/");t.pop(),t.push(h),h=t.join("/")}const p={start:d,end:f,url:h},m=a[2].split(""),y=a[3].split(",");for(let e=0;el.duration,setStyle:i,isMobile:r}),m=o.style||{};return c.closed||l.controls.add({name:"vtt-thumbnail",position:"top",index:20,style:m,mounted(t){c.own(()=>{l.controls["vtt-thumbnail"]===t&&l.controls.remove("vtt-thumbnail")}),c.closed||(s(t,"art-control-thumbnails"),c.listen("setBar",p(t)))}}),a}catch(d){throw c.dispose(),d}})}}); +!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVttThumbnail=e(),define(function(){return t.artplayerPluginVttThumbnail})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVttThumbnail=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,i=(e,n,o)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[n]=o,r=(t,r)=>{for(var l in r||(r={}))n.call(r,l)&&i(t,l,r[l]);if(e)for(var l of e(r))o.call(r,l)&&i(t,l,r[l]);return t},l=(t,e,n)=>new Promise((o,i)=>{var r=t=>{try{s(n.next(t))}catch(e){i(e)}},l=t=>{try{s(n.throw(t))}catch(e){i(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(r,l);s((n=n.apply(t,e)).next())});function s(t,e){throw new TypeError(`Invalid VTT thumbnail at line ${t}: ${e}`)}function c(t,e){/^\d{2,}(?::\d{2}){0,2}(?:\.\d{3})?$/.test(t)||s(e,"invalid timestamp");const[n,o="0"]=t.split("."),i=n.split(":").reduce((t,e)=>60*t+Number(e),0)+Number(o)/1e3;return Number.isSafeInteger(Math.floor(i))||s(e,"timestamp exceeds the supported numeric range"),i}function u(t,e,n){const o=t.match(/^(.+)#([xywh]{4})=(.*)$/);o&&4===new Set(o[2]).size||s(n,"expected an image URL and four distinct xywh keys");const i=o[3].split(",").map(t=>t.trim());4!==i.length&&s(n,"expected four rectangle coordinates");const l={};for(let r=0;r<4;r++){const t=o[2][r],e=i[r];/^(?:\d+(?:\.\d+)?|\.\d+)$/.test(e)&&Number.isFinite(Number(e))||s(n,"rectangle coordinates must be finite non-negative decimals"),"w"!==t&&"h"!==t||0!==Number(e)||s(n,"rectangle width and height must be positive"),l[t]=e}let c=o[1];if(!/^\/|(?:https?|ftp|file):\/\//i.test(c)){const t=e.split("/");t.pop(),t.push(c),c=t.join("/")}return r({url:c},l)}function a({lifetime:t,thumbnails:e,progress:n,duration:o,setStyle:i,isMobile:r}){let s=null,c=0;function u(e,n,o){t.closed||i(e,n,o)}return t.own(()=>{if(c++,null!==s){const t=s;s=null,clearTimeout(t)}}),i=>(a,d,f)=>l(null,null,function*(){if(t.closed)return;const l="played"===a&&f&&r;if("hover"!==a&&!l)return;const p=n.clientWidth*d,m=d*o();if(u(i,"display","flex"),t.closed)return;const h=function(t,e){return t.find(t=>e>=t.start&&e<=t.end)}(e,m);if(!h)return u(i,"display","none");if(p>0&&pn.clientWidth-e.w/2?u(t,"left",n.clientWidth-e.w+"px"):u(t,"left",o-e.w/2+"px")}(i,h,p):r||u(i,"display","none"),l&&!t.closed){const e=++c;if(null!==s&&clearTimeout(s),t.closed)return;const n=setTimeout(()=>{e!==c||t.closed||(s=null,u(i,"display","none"))},500);t.closed||e!==c?clearTimeout(n):s=n}})}return function(t){return e=>l(null,null,function*(){const{constructor:{utils:{setStyle:n,isMobile:o,addClass:i}},template:{$progress:d}}=e,f=function(t){let e=Boolean(t.isDestroy);const n=new Set;let o;const i=new Promise(t=>{o=t}),r={get closed(){return e},own:t=>(e?l(t):n.add(t),()=>n.delete(t)),listen(n,o){e||(r.own(()=>t.off(n,o)),t.on(n,o))},wait:t=>Promise.race([t,i]),dispose(){if(e)return;e=!0,o();const t=[...n].reverse();n.clear();for(const e of t)l(e)}};function l(t){try{t()}catch(e){console.warn("Failed to clean up VTT thumbnails:",e)}}try{r.listen("destroy",r.dispose)}catch(s){throw r.dispose(),s}return r}(e),p={name:"artplayerPluginVttThumbnail"};try{if(f.closed)return p;const m=t.vtt,h=yield function(t="",e){return l(this,null,function*(){if(e.closed)return;const n="function"===typeof AbortController?new AbortController:null,o=e.own(()=>null==n?void 0:n.abort());try{if(e.closed)return;const o=yield e.wait(n?fetch(t,{signal:n.signal}):fetch(t));if(e.closed)return;if(!1===o.ok)throw new Error(`Failed to fetch VTT thumbnails: HTTP ${o.status}`);if(e.closed)return;return yield e.wait(o.text())}finally{o()}})}(m,f);if(f.closed)return p;const y=function(t,e=""){"string"!==typeof t&&s(1,"expected text");const n=t.replace(/^\uFEFF/,"").split(/\r\n|\r|\n/);let o=0;function i(){for(;o[\t ]*([\d:.]+)(?:[\t ].*)?$/);f||s(d,"invalid cue timing");const p=c(f[1],d),m=c(f[2],d);me.duration,setStyle:n,isMobile:o}),g=t.style||{};return f.closed||e.controls.add({name:"vtt-thumbnail",position:"top",index:20,style:g,mounted(t){f.own(()=>{e.controls["vtt-thumbnail"]===t&&e.controls.remove("vtt-thumbnail")}),f.closed||(i(t,"art-control-thumbnails"),f.listen("setBar",b(t)))}}),p}catch(m){throw f.dispose(),m}})}}); diff --git a/docs/compiled/artplayer-plugin-vtt-thumbnail.mjs b/docs/compiled/artplayer-plugin-vtt-thumbnail.mjs index 27d351f66..c42550625 100644 --- a/docs/compiled/artplayer-plugin-vtt-thumbnail.mjs +++ b/docs/compiled/artplayer-plugin-vtt-thumbnail.mjs @@ -57,58 +57,96 @@ function createLifetime(art) { } return lifetime; } -function padEnd(str, targetLength, padString) { - if (str.length > targetLength) { - return String(str); - } else { - targetLength = targetLength - str.length; - if (targetLength > padString.length) { - padString += padString.repeat(targetLength / padString.length); - } - return String(str) + padString.slice(0, targetLength); +function invalid(line, message) { + throw new TypeError(`Invalid VTT thumbnail at line ${line}: ${message}`); +} +function timestamp(value, line) { + if (!/^\d{2,}(?::\d{2}){0,2}(?:\.\d{3})?$/.test(value)) + invalid(line, "invalid timestamp"); + const [whole, fraction = "0"] = value.split("."); + const seconds = whole.split(":").reduce((total, part) => total * 60 + Number(part), 0) + Number(fraction) / 1e3; + if (!Number.isSafeInteger(Math.floor(seconds))) + invalid(line, "timestamp exceeds the supported numeric range"); + return seconds; +} +function rectangle(text, vttUrl, line) { + const match = text.match(/^(.+)#([xywh]{4})=(.*)$/); + if (!match || new Set(match[2]).size !== 4) + invalid(line, "expected an image URL and four distinct xywh keys"); + const values = match[3].split(",").map((value) => value.trim()); + if (values.length !== 4) + invalid(line, "expected four rectangle coordinates"); + const rect = {}; + for (let index = 0; index < 4; index++) { + const key = match[2][index]; + const value = values[index]; + if (!/^(?:\d+(?:\.\d+)?|\.\d+)$/.test(value) || !Number.isFinite(Number(value))) + invalid(line, "rectangle coordinates must be finite non-negative decimals"); + if ((key === "w" || key === "h") && Number(value) === 0) + invalid(line, "rectangle width and height must be positive"); + rect[key] = value; } + let url = match[1]; + if (!/^\/|(?:https?|ftp|file):\/\//i.test(url)) { + const segments = vttUrl.split("/"); + segments.pop(); + segments.push(url); + url = segments.join("/"); + } + return { url, ...rect }; } -function t2d(time) { - const arr = time.split("."); - const left = arr[0].split(":") || []; - const right = padEnd(arr[1] || "0", 3, "0"); - const ms = Number(right) / 1e3; - const h = Number(left[left.length - 3] || 0) * 3600; - const m = Number(left[left.length - 2] || 0) * 60; - const s = Number(left[left.length - 1] || 0); - return h + m + s + ms; +function findThumbnail(thumbnails, second) { + return thumbnails.find((item) => second >= item.start && second <= item.end); } -function parseVtt(vttString, vttUrl = "") { - const lines = vttString.split(/[\n\r]/g).filter((item) => item.trim()); - const vttArray = []; - for (let i = 1; i < lines.length; i += 2) { - const time = lines[i]; - const text = lines[i + 1]; - if (!text.trim()) +function parseVtt(text, vttUrl = "") { + if (typeof text !== "string") + invalid(1, "expected text"); + const lines = text.replace(/^\uFEFF/, "").split(/\r\n|\r|\n/); + let index = 0; + function skipEmpty() { + while (index < lines.length && !lines[index].trim()) + index++; + } + skipEmpty(); + if (index === lines.length) + return []; + if (!/^WEBVTT(?:[\t ].*)?$/.test(lines[index].trim()) || lines[index].includes("-->")) + invalid(index + 1, "expected WEBVTT header"); + index++; + const thumbnails = []; + while (index < lines.length) { + skipEmpty(); + if (index === lines.length) + break; + const first = lines[index].trim(); + if (/^NOTE(?:[\t ]|$)/.test(first) || first === "STYLE" || first === "REGION") { + while (index < lines.length && lines[index].trim()) + index++; continue; - const timeReg = /((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?) ?--> ?((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?)/; - const timeMatch = time.match(timeReg); - const textReg = /(.*)#(\w{4})=(.*)/; - const textMatch = text.match(textReg); - const start = Math.floor(t2d(timeMatch[1])); - const end = Math.floor(t2d(timeMatch[2])); - let url = textMatch[1]; - const isAbsoluteUrl = /^\/|(?:https?|ftp|file):\/\//i.test(url); - if (!isAbsoluteUrl) { - const urlArr = vttUrl.split("/"); - urlArr.pop(); - urlArr.push(url); - url = urlArr.join("/"); } - const result = { start, end, url }; - const keys = textMatch[2].split(""); - const values = textMatch[3].split(","); - for (let j = 0; j < keys.length; j++) { - result[keys[j]] = values[j]; + const identifierLine = index + 1; + if (!first.includes("-->")) { + index++; + if (index === lines.length || !lines[index].trim()) + invalid(identifierLine, "cue identifier must be followed by timing"); } - vttArray.push(result); + const timingLine = index + 1; + const timing = lines[index].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/); + if (!timing) + invalid(timingLine, "invalid cue timing"); + const start = timestamp(timing[1], timingLine); + const end = timestamp(timing[2], timingLine); + if (end < start) + invalid(timingLine, "cue end precedes its start"); + index++; + skipEmpty(); + if (index === lines.length) + invalid(timingLine, "missing sprite image"); + const image = rectangle(lines[index].trim(), vttUrl, index + 1); + thumbnails.push({ start: Math.floor(start), end: Math.floor(end), ...image }); + index++; } - return vttArray; + return thumbnails; } function createPreview({ lifetime, thumbnails, progress, duration, setStyle, isMobile }) { let timer = null; @@ -148,7 +186,7 @@ function createPreview({ lifetime, thumbnails, progress, duration, setStyle, isM style(control, "display", "flex"); if (lifetime.closed) return; - const cue = thumbnails.find((item) => second >= item.start && second <= item.end); + const cue = findThumbnail(thumbnails, second); if (!cue) return style(control, "display", "none"); if (width > 0 && width < progress.clientWidth) diff --git a/package.json b/package.json index 320e8b372..ee439cd7e 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/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.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/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.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", @@ -86,7 +86,7 @@ "test:thumbnail": "node --test test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js refactor/scripts/thumbnail-contract.test.mjs refactor/scripts/thumbnail-types.test.mjs", "test:thumbnail-types-package": "node refactor/scripts/thumbnail-package-types.mjs", "test:auto-thumbnail": "node --test test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js refactor/scripts/auto-thumbnail-contract.test.mjs", - "test:vtt-thumbnail": "node --test test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js refactor/scripts/vtt-thumbnail-contract.test.mjs" + "test:vtt-thumbnail": "node --test test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js refactor/scripts/vtt-thumbnail-contract.test.mjs" }, "browserslist": "last 1 Chrome version", "devDependencies": { diff --git a/packages/artplayer-plugin-vtt-thumbnail/ARCHITECTURE.md b/packages/artplayer-plugin-vtt-thumbnail/ARCHITECTURE.md index dce8ac4cc..f83e770fa 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/ARCHITECTURE.md +++ b/packages/artplayer-plugin-vtt-thumbnail/ARCHITECTURE.md @@ -15,9 +15,10 @@ and the strict TypeScript migration. Do not infer the return shape from those de - `src/request.js` owns a native AbortController when available and reads the response body. Destroy cancels both phases; late rejections are observed. Failed HTTP responses reject before parsing. The controller cleanup is released when the request ends. -- `src/parseVtt.js` is pure parsing with the historical timestamp floor, first inclusive - match data, raw xywh strings and lexical URL joining. Its old malformed-input limitations - remain under PKG-VTT-THUMB-03; extracting this function is not a parser rewrite. +- `src/parseVtt.js` parses thumbnail metadata and exports the pure `findThumbnail` lookup. + It preserves timestamp flooring, first inclusive matches, decimal xywh strings and lexical + URL joining. It handles cue IDs, NOTE/STYLE/REGION blocks, BOM/line endings and timing + settings; invalid input raises TypeError with the original line before any UI is installed. - `src/preview.js` owns the mobile 500ms timer and creates the setBar callback. Timer generations reject old callbacks. Every style write checks lifetime state, including callbacks captured before destroy. Geometry, mobile/desktop edges and cue selection @@ -43,6 +44,20 @@ later option.vtt changes. Empty cues remain valid and hide the preview. HTTP fai reject explicitly; before this fix, a 404 with a parseable body incorrectly displayed cues. The historical failure tests retain the former outcome rather than claiming it was valid API. +This is a thumbnail metadata profile, not a complete subtitle renderer or WebVTT validator. +Caption style/region blocks and timing settings do not style the player's DOM. Preserve +legacy compact arrows, seconds-only timestamps, numeric overflow between minute/second +fields, zero-length cues, unsorted/overlapping cues and dense pairs. Lookup does not sort: +the first inclusive match in input order wins. Fractions still floor only after conversion. +Three-or-more-digit hours now use the entire field rather than silently truncating it. + +Malformed headers/timing, reversed ranges and missing/duplicate/unknown rectangle keys +reject. Coordinates must be finite non-negative decimal strings, and width/height positive; +coordinate whitespace is trimmed before forming CSS. Broken input no longer creates +invalid CSS or partially mounted controls. Each cue has one image/rectangle payload line; +multiline subtitle text, timestamp-map offsets and full caption semantics are outside this +plugin. Empty or comment-only input remains a valid empty preview. + The old 1.0.x export objects, earlier `thumbnails` control name and incorrect declarations are separately frozen and remain compatibility work for tasks 04-06. Current runtime work does not claim those consumer migrations or the full package refactor are finished. @@ -65,5 +80,9 @@ core/device coverage, installed tarballs, mobile Safari or the online editor. Modify parsing in parseVtt, requests in request, layout/timers in preview, and ownership in lifetime. Preserve the normal/failure lifecycle tests alongside any parser improvements. +Pure parser comparison can use ARTPLAYER_VTT_PARSER_BASELINE=1, which loads the exact +parser from the 524ddf78 resource checkpoint. Bundle-mode parser tests also run malformed +payloads and extended registration through the actual factory; pure parsing assertions +still target source and must not be described as tests of an installed tarball. See ../../refactor/baselines/vtt-thumbnail-contract.md and the task 03 change record for historical evidence and outstanding acceptance work. diff --git a/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.js b/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.js index 502865ead..6433dcb4d 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.js +++ b/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVttThumbnail=e(),define(function(){return t.artplayerPluginVttThumbnail})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVttThumbnail=e()}(this,function(){"use strict";function t(t){const e=t.split("."),n=e[0].split(":")||[],o=(l=e[1]||"0",i=3,r="0",l.length>i?String(l):((i-=l.length)>r.length&&(r+=r.repeat(i/r.length)),String(l)+r.slice(0,i)));var l,i,r;const s=Number(o)/1e3;return 3600*Number(n[n.length-3]||0)+60*Number(n[n.length-2]||0)+Number(n[n.length-1]||0)+s}function e({lifetime:t,thumbnails:e,progress:n,duration:o,setStyle:l,isMobile:i}){let r=null,s=0;function c(e,n,o){t.closed||l(e,n,o)}return t.own(()=>{if(s++,null!==r){const t=r;r=null,clearTimeout(t)}}),l=>async(u,a,d)=>{if(t.closed)return;const f="played"===u&&d&&i;if("hover"!==u&&!f)return;const h=n.clientWidth*a,p=a*o();if(c(l,"display","flex"),t.closed)return;const m=e.find(t=>p>=t.start&&p<=t.end);if(!m)return c(l,"display","none");if(h>0&&hn.clientWidth-e.w/2?c(t,"left",n.clientWidth-e.w+"px"):c(t,"left",o-e.w/2+"px")}(l,m,h):i||c(l,"display","none"),f&&!t.closed){const e=++s;if(null!==r&&clearTimeout(r),t.closed)return;const n=setTimeout(()=>{e!==s||t.closed||(r=null,c(l,"display","none"))},500);t.closed||e!==s?clearTimeout(n):r=n}}}return function(n){return async o=>{const{constructor:{utils:{setStyle:l,isMobile:i,addClass:r}},template:{$progress:s}}=o,c=function(t){let e=Boolean(t.isDestroy);const n=new Set;let o;const l=new Promise(t=>{o=t}),i={get closed(){return e},own:t=>(e?r(t):n.add(t),()=>n.delete(t)),listen(n,o){e||(i.own(()=>t.off(n,o)),t.on(n,o))},wait:t=>Promise.race([t,l]),dispose(){if(e)return;e=!0,o();const t=[...n].reverse();n.clear();for(const e of t)r(e)}};function r(t){try{t()}catch(e){console.warn("Failed to clean up VTT thumbnails:",e)}}try{i.listen("destroy",i.dispose)}catch(s){throw i.dispose(),s}return i}(o),u={name:"artplayerPluginVttThumbnail"};try{if(c.closed)return u;const a=n.vtt,d=await async function(t="",e){if(e.closed)return;const n="function"===typeof AbortController?new AbortController:null,o=e.own(()=>n?.abort());try{if(e.closed)return;const o=await e.wait(n?fetch(t,{signal:n.signal}):fetch(t));if(e.closed)return;if(!1===o.ok)throw new Error(`Failed to fetch VTT thumbnails: HTTP ${o.status}`);if(e.closed)return;return await e.wait(o.text())}finally{o()}}(a,c);if(c.closed)return u;const f=function(e,n=""){const o=e.split(/[\n\r]/g).filter(t=>t.trim()),l=[];for(let i=1;i ?((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?)/,c=e.match(s),u=/(.*)#(\w{4})=(.*)/,a=r.match(u),d=Math.floor(t(c[1])),f=Math.floor(t(c[2]));let h=a[1];if(!/^\/|(?:https?|ftp|file):\/\//i.test(h)){const t=n.split("/");t.pop(),t.push(h),h=t.join("/")}const p={start:d,end:f,url:h},m=a[2].split(""),y=a[3].split(",");for(let t=0;to.duration,setStyle:l,isMobile:i}),p=n.style||{};return c.closed||o.controls.add({name:"vtt-thumbnail",position:"top",index:20,style:p,mounted(t){c.own(()=>{o.controls["vtt-thumbnail"]===t&&o.controls.remove("vtt-thumbnail")}),c.closed||(r(t,"art-control-thumbnails"),c.listen("setBar",h(t)))}}),u}catch(a){throw c.dispose(),a}}}}); +!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVttThumbnail=e(),define(function(){return t.artplayerPluginVttThumbnail})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVttThumbnail=e()}(this,function(){"use strict";function t(t,e){throw new TypeError(`Invalid VTT thumbnail at line ${t}: ${e}`)}function e(e,n){/^\d{2,}(?::\d{2}){0,2}(?:\.\d{3})?$/.test(e)||t(n,"invalid timestamp");const[i,o="0"]=e.split("."),r=i.split(":").reduce((t,e)=>60*t+Number(e),0)+Number(o)/1e3;return Number.isSafeInteger(Math.floor(r))||t(n,"timestamp exceeds the supported numeric range"),r}function n(e,n,i){const o=e.match(/^(.+)#([xywh]{4})=(.*)$/);o&&4===new Set(o[2]).size||t(i,"expected an image URL and four distinct xywh keys");const r=o[3].split(",").map(t=>t.trim());4!==r.length&&t(i,"expected four rectangle coordinates");const s={};for(let c=0;c<4;c++){const e=o[2][c],n=r[c];/^(?:\d+(?:\.\d+)?|\.\d+)$/.test(n)&&Number.isFinite(Number(n))||t(i,"rectangle coordinates must be finite non-negative decimals"),"w"!==e&&"h"!==e||0!==Number(n)||t(i,"rectangle width and height must be positive"),s[e]=n}let l=o[1];if(!/^\/|(?:https?|ftp|file):\/\//i.test(l)){const t=n.split("/");t.pop(),t.push(l),l=t.join("/")}return{url:l,...s}}function i({lifetime:t,thumbnails:e,progress:n,duration:i,setStyle:o,isMobile:r}){let s=null,l=0;function c(e,n,i){t.closed||o(e,n,i)}return t.own(()=>{if(l++,null!==s){const t=s;s=null,clearTimeout(t)}}),o=>async(u,a,d)=>{if(t.closed)return;const f="played"===u&&d&&r;if("hover"!==u&&!f)return;const m=n.clientWidth*a,h=a*i();if(c(o,"display","flex"),t.closed)return;const p=function(t,e){return t.find(t=>e>=t.start&&e<=t.end)}(e,h);if(!p)return c(o,"display","none");if(m>0&&mn.clientWidth-e.w/2?c(t,"left",n.clientWidth-e.w+"px"):c(t,"left",i-e.w/2+"px")}(o,p,m):r||c(o,"display","none"),f&&!t.closed){const e=++l;if(null!==s&&clearTimeout(s),t.closed)return;const n=setTimeout(()=>{e!==l||t.closed||(s=null,c(o,"display","none"))},500);t.closed||e!==l?clearTimeout(n):s=n}}}return function(o){return async r=>{const{constructor:{utils:{setStyle:s,isMobile:l,addClass:c}},template:{$progress:u}}=r,a=function(t){let e=Boolean(t.isDestroy);const n=new Set;let i;const o=new Promise(t=>{i=t}),r={get closed(){return e},own:t=>(e?s(t):n.add(t),()=>n.delete(t)),listen(n,i){e||(r.own(()=>t.off(n,i)),t.on(n,i))},wait:t=>Promise.race([t,o]),dispose(){if(e)return;e=!0,i();const t=[...n].reverse();n.clear();for(const e of t)s(e)}};function s(t){try{t()}catch(e){console.warn("Failed to clean up VTT thumbnails:",e)}}try{r.listen("destroy",r.dispose)}catch(l){throw r.dispose(),l}return r}(r),d={name:"artplayerPluginVttThumbnail"};try{if(a.closed)return d;const f=o.vtt,m=await async function(t="",e){if(e.closed)return;const n="function"===typeof AbortController?new AbortController:null,i=e.own(()=>n?.abort());try{if(e.closed)return;const i=await e.wait(n?fetch(t,{signal:n.signal}):fetch(t));if(e.closed)return;if(!1===i.ok)throw new Error(`Failed to fetch VTT thumbnails: HTTP ${i.status}`);if(e.closed)return;return await e.wait(i.text())}finally{i()}}(f,a);if(a.closed)return d;const h=function(i,o=""){"string"!==typeof i&&t(1,"expected text");const r=i.replace(/^\uFEFF/,"").split(/\r\n|\r|\n/);let s=0;function l(){for(;s[\t ]*([\d:.]+)(?:[\t ].*)?$/);d||t(a,"invalid cue timing");const f=e(d[1],a),m=e(d[2],a);mr.duration,setStyle:s,isMobile:l}),g=o.style||{};return a.closed||r.controls.add({name:"vtt-thumbnail",position:"top",index:20,style:g,mounted(t){a.own(()=>{r.controls["vtt-thumbnail"]===t&&r.controls.remove("vtt-thumbnail")}),a.closed||(c(t,"art-control-thumbnails"),a.listen("setBar",p(t)))}}),d}catch(f){throw a.dispose(),f}}}}); diff --git a/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.legacy.js b/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.legacy.js index 53193cab0..e2631f933 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.legacy.js +++ b/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.legacy.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVttThumbnail=e(),define(function(){return t.artplayerPluginVttThumbnail})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVttThumbnail=e()}(this,function(){"use strict";var t=(t,e,n)=>new Promise((o,l)=>{var i=t=>{try{s(n.next(t))}catch(e){l(e)}},r=t=>{try{s(n.throw(t))}catch(e){l(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(i,r);s((n=n.apply(t,e)).next())});function e(t){const e=t.split("."),n=e[0].split(":")||[],o=(l=e[1]||"0",i=3,r="0",l.length>i?String(l):((i-=l.length)>r.length&&(r+=r.repeat(i/r.length)),String(l)+r.slice(0,i)));var l,i,r;const s=Number(o)/1e3;return 3600*Number(n[n.length-3]||0)+60*Number(n[n.length-2]||0)+Number(n[n.length-1]||0)+s}function n({lifetime:e,thumbnails:n,progress:o,duration:l,setStyle:i,isMobile:r}){let s=null,u=0;function c(t,n,o){e.closed||i(t,n,o)}return e.own(()=>{if(u++,null!==s){const t=s;s=null,clearTimeout(t)}}),i=>(a,d,f)=>t(null,null,function*(){if(e.closed)return;const t="played"===a&&f&&r;if("hover"!==a&&!t)return;const h=o.clientWidth*d,p=d*l();if(c(i,"display","flex"),e.closed)return;const m=n.find(t=>p>=t.start&&p<=t.end);if(!m)return c(i,"display","none");if(h>0&&ho.clientWidth-e.w/2?c(t,"left",o.clientWidth-e.w+"px"):c(t,"left",n-e.w/2+"px")}(i,m,h):r||c(i,"display","none"),t&&!e.closed){const t=++u;if(null!==s&&clearTimeout(s),e.closed)return;const n=setTimeout(()=>{t!==u||e.closed||(s=null,c(i,"display","none"))},500);e.closed||t!==u?clearTimeout(n):s=n}})}return function(o){return l=>t(null,null,function*(){const{constructor:{utils:{setStyle:i,isMobile:r,addClass:s}},template:{$progress:u}}=l,c=function(t){let e=Boolean(t.isDestroy);const n=new Set;let o;const l=new Promise(t=>{o=t}),i={get closed(){return e},own:t=>(e?r(t):n.add(t),()=>n.delete(t)),listen(n,o){e||(i.own(()=>t.off(n,o)),t.on(n,o))},wait:t=>Promise.race([t,l]),dispose(){if(e)return;e=!0,o();const t=[...n].reverse();n.clear();for(const e of t)r(e)}};function r(t){try{t()}catch(e){console.warn("Failed to clean up VTT thumbnails:",e)}}try{i.listen("destroy",i.dispose)}catch(s){throw i.dispose(),s}return i}(l),a={name:"artplayerPluginVttThumbnail"};try{if(c.closed)return a;const d=o.vtt,f=yield function(e="",n){return t(this,null,function*(){if(n.closed)return;const t="function"===typeof AbortController?new AbortController:null,o=n.own(()=>null==t?void 0:t.abort());try{if(n.closed)return;const o=yield n.wait(t?fetch(e,{signal:t.signal}):fetch(e));if(n.closed)return;if(!1===o.ok)throw new Error(`Failed to fetch VTT thumbnails: HTTP ${o.status}`);if(n.closed)return;return yield n.wait(o.text())}finally{o()}})}(d,c);if(c.closed)return a;const h=function(t,n=""){const o=t.split(/[\n\r]/g).filter(t=>t.trim()),l=[];for(let i=1;i ?((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?)/,u=t.match(s),c=/(.*)#(\w{4})=(.*)/,a=r.match(c),d=Math.floor(e(u[1])),f=Math.floor(e(u[2]));let h=a[1];if(!/^\/|(?:https?|ftp|file):\/\//i.test(h)){const t=n.split("/");t.pop(),t.push(h),h=t.join("/")}const p={start:d,end:f,url:h},m=a[2].split(""),y=a[3].split(",");for(let e=0;el.duration,setStyle:i,isMobile:r}),m=o.style||{};return c.closed||l.controls.add({name:"vtt-thumbnail",position:"top",index:20,style:m,mounted(t){c.own(()=>{l.controls["vtt-thumbnail"]===t&&l.controls.remove("vtt-thumbnail")}),c.closed||(s(t,"art-control-thumbnails"),c.listen("setBar",p(t)))}}),a}catch(d){throw c.dispose(),d}})}}); +!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVttThumbnail=e(),define(function(){return t.artplayerPluginVttThumbnail})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVttThumbnail=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,i=(e,n,o)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[n]=o,r=(t,r)=>{for(var l in r||(r={}))n.call(r,l)&&i(t,l,r[l]);if(e)for(var l of e(r))o.call(r,l)&&i(t,l,r[l]);return t},l=(t,e,n)=>new Promise((o,i)=>{var r=t=>{try{s(n.next(t))}catch(e){i(e)}},l=t=>{try{s(n.throw(t))}catch(e){i(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(r,l);s((n=n.apply(t,e)).next())});function s(t,e){throw new TypeError(`Invalid VTT thumbnail at line ${t}: ${e}`)}function c(t,e){/^\d{2,}(?::\d{2}){0,2}(?:\.\d{3})?$/.test(t)||s(e,"invalid timestamp");const[n,o="0"]=t.split("."),i=n.split(":").reduce((t,e)=>60*t+Number(e),0)+Number(o)/1e3;return Number.isSafeInteger(Math.floor(i))||s(e,"timestamp exceeds the supported numeric range"),i}function u(t,e,n){const o=t.match(/^(.+)#([xywh]{4})=(.*)$/);o&&4===new Set(o[2]).size||s(n,"expected an image URL and four distinct xywh keys");const i=o[3].split(",").map(t=>t.trim());4!==i.length&&s(n,"expected four rectangle coordinates");const l={};for(let r=0;r<4;r++){const t=o[2][r],e=i[r];/^(?:\d+(?:\.\d+)?|\.\d+)$/.test(e)&&Number.isFinite(Number(e))||s(n,"rectangle coordinates must be finite non-negative decimals"),"w"!==t&&"h"!==t||0!==Number(e)||s(n,"rectangle width and height must be positive"),l[t]=e}let c=o[1];if(!/^\/|(?:https?|ftp|file):\/\//i.test(c)){const t=e.split("/");t.pop(),t.push(c),c=t.join("/")}return r({url:c},l)}function a({lifetime:t,thumbnails:e,progress:n,duration:o,setStyle:i,isMobile:r}){let s=null,c=0;function u(e,n,o){t.closed||i(e,n,o)}return t.own(()=>{if(c++,null!==s){const t=s;s=null,clearTimeout(t)}}),i=>(a,d,f)=>l(null,null,function*(){if(t.closed)return;const l="played"===a&&f&&r;if("hover"!==a&&!l)return;const p=n.clientWidth*d,m=d*o();if(u(i,"display","flex"),t.closed)return;const h=function(t,e){return t.find(t=>e>=t.start&&e<=t.end)}(e,m);if(!h)return u(i,"display","none");if(p>0&&pn.clientWidth-e.w/2?u(t,"left",n.clientWidth-e.w+"px"):u(t,"left",o-e.w/2+"px")}(i,h,p):r||u(i,"display","none"),l&&!t.closed){const e=++c;if(null!==s&&clearTimeout(s),t.closed)return;const n=setTimeout(()=>{e!==c||t.closed||(s=null,u(i,"display","none"))},500);t.closed||e!==c?clearTimeout(n):s=n}})}return function(t){return e=>l(null,null,function*(){const{constructor:{utils:{setStyle:n,isMobile:o,addClass:i}},template:{$progress:d}}=e,f=function(t){let e=Boolean(t.isDestroy);const n=new Set;let o;const i=new Promise(t=>{o=t}),r={get closed(){return e},own:t=>(e?l(t):n.add(t),()=>n.delete(t)),listen(n,o){e||(r.own(()=>t.off(n,o)),t.on(n,o))},wait:t=>Promise.race([t,i]),dispose(){if(e)return;e=!0,o();const t=[...n].reverse();n.clear();for(const e of t)l(e)}};function l(t){try{t()}catch(e){console.warn("Failed to clean up VTT thumbnails:",e)}}try{r.listen("destroy",r.dispose)}catch(s){throw r.dispose(),s}return r}(e),p={name:"artplayerPluginVttThumbnail"};try{if(f.closed)return p;const m=t.vtt,h=yield function(t="",e){return l(this,null,function*(){if(e.closed)return;const n="function"===typeof AbortController?new AbortController:null,o=e.own(()=>null==n?void 0:n.abort());try{if(e.closed)return;const o=yield e.wait(n?fetch(t,{signal:n.signal}):fetch(t));if(e.closed)return;if(!1===o.ok)throw new Error(`Failed to fetch VTT thumbnails: HTTP ${o.status}`);if(e.closed)return;return yield e.wait(o.text())}finally{o()}})}(m,f);if(f.closed)return p;const y=function(t,e=""){"string"!==typeof t&&s(1,"expected text");const n=t.replace(/^\uFEFF/,"").split(/\r\n|\r|\n/);let o=0;function i(){for(;o[\t ]*([\d:.]+)(?:[\t ].*)?$/);f||s(d,"invalid cue timing");const p=c(f[1],d),m=c(f[2],d);me.duration,setStyle:n,isMobile:o}),g=t.style||{};return f.closed||e.controls.add({name:"vtt-thumbnail",position:"top",index:20,style:g,mounted(t){f.own(()=>{e.controls["vtt-thumbnail"]===t&&e.controls.remove("vtt-thumbnail")}),f.closed||(i(t,"art-control-thumbnails"),f.listen("setBar",b(t)))}}),p}catch(m){throw f.dispose(),m}})}}); diff --git a/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.mjs b/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.mjs index 27d351f66..c42550625 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.mjs +++ b/packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.mjs @@ -57,58 +57,96 @@ function createLifetime(art) { } return lifetime; } -function padEnd(str, targetLength, padString) { - if (str.length > targetLength) { - return String(str); - } else { - targetLength = targetLength - str.length; - if (targetLength > padString.length) { - padString += padString.repeat(targetLength / padString.length); - } - return String(str) + padString.slice(0, targetLength); +function invalid(line, message) { + throw new TypeError(`Invalid VTT thumbnail at line ${line}: ${message}`); +} +function timestamp(value, line) { + if (!/^\d{2,}(?::\d{2}){0,2}(?:\.\d{3})?$/.test(value)) + invalid(line, "invalid timestamp"); + const [whole, fraction = "0"] = value.split("."); + const seconds = whole.split(":").reduce((total, part) => total * 60 + Number(part), 0) + Number(fraction) / 1e3; + if (!Number.isSafeInteger(Math.floor(seconds))) + invalid(line, "timestamp exceeds the supported numeric range"); + return seconds; +} +function rectangle(text, vttUrl, line) { + const match = text.match(/^(.+)#([xywh]{4})=(.*)$/); + if (!match || new Set(match[2]).size !== 4) + invalid(line, "expected an image URL and four distinct xywh keys"); + const values = match[3].split(",").map((value) => value.trim()); + if (values.length !== 4) + invalid(line, "expected four rectangle coordinates"); + const rect = {}; + for (let index = 0; index < 4; index++) { + const key = match[2][index]; + const value = values[index]; + if (!/^(?:\d+(?:\.\d+)?|\.\d+)$/.test(value) || !Number.isFinite(Number(value))) + invalid(line, "rectangle coordinates must be finite non-negative decimals"); + if ((key === "w" || key === "h") && Number(value) === 0) + invalid(line, "rectangle width and height must be positive"); + rect[key] = value; } + let url = match[1]; + if (!/^\/|(?:https?|ftp|file):\/\//i.test(url)) { + const segments = vttUrl.split("/"); + segments.pop(); + segments.push(url); + url = segments.join("/"); + } + return { url, ...rect }; } -function t2d(time) { - const arr = time.split("."); - const left = arr[0].split(":") || []; - const right = padEnd(arr[1] || "0", 3, "0"); - const ms = Number(right) / 1e3; - const h = Number(left[left.length - 3] || 0) * 3600; - const m = Number(left[left.length - 2] || 0) * 60; - const s = Number(left[left.length - 1] || 0); - return h + m + s + ms; +function findThumbnail(thumbnails, second) { + return thumbnails.find((item) => second >= item.start && second <= item.end); } -function parseVtt(vttString, vttUrl = "") { - const lines = vttString.split(/[\n\r]/g).filter((item) => item.trim()); - const vttArray = []; - for (let i = 1; i < lines.length; i += 2) { - const time = lines[i]; - const text = lines[i + 1]; - if (!text.trim()) +function parseVtt(text, vttUrl = "") { + if (typeof text !== "string") + invalid(1, "expected text"); + const lines = text.replace(/^\uFEFF/, "").split(/\r\n|\r|\n/); + let index = 0; + function skipEmpty() { + while (index < lines.length && !lines[index].trim()) + index++; + } + skipEmpty(); + if (index === lines.length) + return []; + if (!/^WEBVTT(?:[\t ].*)?$/.test(lines[index].trim()) || lines[index].includes("-->")) + invalid(index + 1, "expected WEBVTT header"); + index++; + const thumbnails = []; + while (index < lines.length) { + skipEmpty(); + if (index === lines.length) + break; + const first = lines[index].trim(); + if (/^NOTE(?:[\t ]|$)/.test(first) || first === "STYLE" || first === "REGION") { + while (index < lines.length && lines[index].trim()) + index++; continue; - const timeReg = /((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?) ?--> ?((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?)/; - const timeMatch = time.match(timeReg); - const textReg = /(.*)#(\w{4})=(.*)/; - const textMatch = text.match(textReg); - const start = Math.floor(t2d(timeMatch[1])); - const end = Math.floor(t2d(timeMatch[2])); - let url = textMatch[1]; - const isAbsoluteUrl = /^\/|(?:https?|ftp|file):\/\//i.test(url); - if (!isAbsoluteUrl) { - const urlArr = vttUrl.split("/"); - urlArr.pop(); - urlArr.push(url); - url = urlArr.join("/"); } - const result = { start, end, url }; - const keys = textMatch[2].split(""); - const values = textMatch[3].split(","); - for (let j = 0; j < keys.length; j++) { - result[keys[j]] = values[j]; + const identifierLine = index + 1; + if (!first.includes("-->")) { + index++; + if (index === lines.length || !lines[index].trim()) + invalid(identifierLine, "cue identifier must be followed by timing"); } - vttArray.push(result); + const timingLine = index + 1; + const timing = lines[index].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/); + if (!timing) + invalid(timingLine, "invalid cue timing"); + const start = timestamp(timing[1], timingLine); + const end = timestamp(timing[2], timingLine); + if (end < start) + invalid(timingLine, "cue end precedes its start"); + index++; + skipEmpty(); + if (index === lines.length) + invalid(timingLine, "missing sprite image"); + const image = rectangle(lines[index].trim(), vttUrl, index + 1); + thumbnails.push({ start: Math.floor(start), end: Math.floor(end), ...image }); + index++; } - return vttArray; + return thumbnails; } function createPreview({ lifetime, thumbnails, progress, duration, setStyle, isMobile }) { let timer = null; @@ -148,7 +186,7 @@ function createPreview({ lifetime, thumbnails, progress, duration, setStyle, isM style(control, "display", "flex"); if (lifetime.closed) return; - const cue = thumbnails.find((item) => second >= item.start && second <= item.end); + const cue = findThumbnail(thumbnails, second); if (!cue) return style(control, "display", "none"); if (width > 0 && width < progress.clientWidth) diff --git a/packages/artplayer-plugin-vtt-thumbnail/src/parseVtt.js b/packages/artplayer-plugin-vtt-thumbnail/src/parseVtt.js index b68af2428..9a16c3269 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/src/parseVtt.js +++ b/packages/artplayer-plugin-vtt-thumbnail/src/parseVtt.js @@ -1,69 +1,95 @@ -function padEnd(str, targetLength, padString) { - if (str.length > targetLength) { - return String(str) - } - else { - targetLength = targetLength - str.length - if (targetLength > padString.length) { - padString += padString.repeat(targetLength / padString.length) - } - return String(str) + padString.slice(0, targetLength) - } +function invalid(line, message) { + throw new TypeError(`Invalid VTT thumbnail at line ${line}: ${message}`) } -function t2d(time) { - const arr = time.split('.') - const left = arr[0].split(':') || [] - const right = padEnd(arr[1] || '0', 3, '0') - const ms = Number(right) / 1000 - - const h = Number(left[left.length - 3] || 0) * 3600 - const m = Number(left[left.length - 2] || 0) * 60 - const s = Number(left[left.length - 1] || 0) - return h + m + s + ms +function timestamp(value, line) { + if (!/^\d{2,}(?::\d{2}){0,2}(?:\.\d{3})?$/.test(value)) + invalid(line, 'invalid timestamp') + const [whole, fraction = '0'] = value.split('.') + const seconds = whole.split(':').reduce((total, part) => total * 60 + Number(part), 0) + Number(fraction) / 1000 + if (!Number.isSafeInteger(Math.floor(seconds))) + invalid(line, 'timestamp exceeds the supported numeric range') + return seconds } -export default function parseVtt(vttString, vttUrl = '') { - const lines = vttString.split(/[\n\r]/g).filter(item => item.trim()) - const vttArray = [] +function rectangle(text, vttUrl, line) { + const match = text.match(/^(.+)#([xywh]{4})=(.*)$/) + if (!match || new Set(match[2]).size !== 4) + invalid(line, 'expected an image URL and four distinct xywh keys') + const values = match[3].split(',').map(value => value.trim()) + if (values.length !== 4) + invalid(line, 'expected four rectangle coordinates') + const rect = {} + for (let index = 0; index < 4; index++) { + const key = match[2][index] + const value = values[index] + if (!/^(?:\d+(?:\.\d+)?|\.\d+)$/.test(value) || !Number.isFinite(Number(value))) + invalid(line, 'rectangle coordinates must be finite non-negative decimals') + if ((key === 'w' || key === 'h') && Number(value) === 0) + invalid(line, 'rectangle width and height must be positive') + rect[key] = value + } + let url = match[1] + if (!/^\/|(?:https?|ftp|file):\/\//i.test(url)) { + const segments = vttUrl.split('/') + segments.pop() + segments.push(url) + url = segments.join('/') + } + return { url, ...rect } +} - for (let i = 1; i < lines.length; i += 2) { - const time = lines[i] - const text = lines[i + 1] +export function findThumbnail(thumbnails, second) { + return thumbnails.find(item => second >= item.start && second <= item.end) +} - if (!text.trim()) +export default function parseVtt(text, vttUrl = '') { + if (typeof text !== 'string') + invalid(1, 'expected text') + const lines = text.replace(/^\uFEFF/, '').split(/\r\n|\r|\n/) + let index = 0 + function skipEmpty() { + while (index < lines.length && !lines[index].trim()) + index++ + } + skipEmpty() + if (index === lines.length) + return [] + if (!/^WEBVTT(?:[\t ].*)?$/.test(lines[index].trim()) || lines[index].includes('-->')) + invalid(index + 1, 'expected WEBVTT header') + index++ + const thumbnails = [] + while (index < lines.length) { + skipEmpty() + if (index === lines.length) + break + const first = lines[index].trim() + if (/^NOTE(?:[\t ]|$)/.test(first) || first === 'STYLE' || first === 'REGION') { + while (index < lines.length && lines[index].trim()) + index++ continue - - const timeReg - = /((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?) ?--> ?((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?)/ - const timeMatch = time.match(timeReg) - - const textReg = /(.*)#(\w{4})=(.*)/ - const textMatch = text.match(textReg) - - const start = Math.floor(t2d(timeMatch[1])) - const end = Math.floor(t2d(timeMatch[2])) - - let url = textMatch[1] - const isAbsoluteUrl = /^\/|(?:https?|ftp|file):\/\//i.test(url) - if (!isAbsoluteUrl) { - const urlArr = vttUrl.split('/') - urlArr.pop() - urlArr.push(url) - url = urlArr.join('/') } - - const result = { start, end, url } - - const keys = textMatch[2].split('') - const values = textMatch[3].split(',') - - for (let j = 0; j < keys.length; j++) { - result[keys[j]] = values[j] + const identifierLine = index + 1 + if (!first.includes('-->')) { + index++ + if (index === lines.length || !lines[index].trim()) + invalid(identifierLine, 'cue identifier must be followed by timing') } - - vttArray.push(result) + const timingLine = index + 1 + const timing = lines[index].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/) + if (!timing) + invalid(timingLine, 'invalid cue timing') + const start = timestamp(timing[1], timingLine) + const end = timestamp(timing[2], timingLine) + if (end < start) + invalid(timingLine, 'cue end precedes its start') + index++ + skipEmpty() + if (index === lines.length) + invalid(timingLine, 'missing sprite image') + const image = rectangle(lines[index].trim(), vttUrl, index + 1) + thumbnails.push({ start: Math.floor(start), end: Math.floor(end), ...image }) + index++ } - - return vttArray + return thumbnails } diff --git a/packages/artplayer-plugin-vtt-thumbnail/src/preview.js b/packages/artplayer-plugin-vtt-thumbnail/src/preview.js index 62d84a3d3..b23940c85 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/src/preview.js +++ b/packages/artplayer-plugin-vtt-thumbnail/src/preview.js @@ -1,3 +1,5 @@ +import { findThumbnail } from './parseVtt' + export default function createPreview({ lifetime, thumbnails, progress, duration, setStyle, isMobile }) { let timer = null let generation = 0 @@ -36,7 +38,7 @@ export default function createPreview({ lifetime, thumbnails, progress, duration style(control, 'display', 'flex') if (lifetime.closed) return - const cue = thumbnails.find(item => second >= item.start && second <= item.end) + const cue = findThumbnail(thumbnails, second) if (!cue) return style(control, 'display', 'none') if (width > 0 && width < progress.clientWidth) diff --git a/refactor/baselines/vtt-thumbnail-parser.json b/refactor/baselines/vtt-thumbnail-parser.json new file mode 100644 index 000000000..f3489e9b1 --- /dev/null +++ b/refactor/baselines/vtt-thumbnail-parser.json @@ -0,0 +1,2557 @@ +{ + "schemaVersion": 1, + "task": "PKG-VTT-THUMB-03", + "status": "done", + "parentCommit": "524ddf784fdc143d3020040e4de1a9e0921698ce", + "checks": [ + { + "command": "frozen 524ddf78 parser and bundle", + "tests": 26, + "pass": 5, + "fail": 21, + "file": "refactor/.cache/vtt-thumbnail03-parser-before-final.log", + "sha256": "b958f0579dcf801ef310f90b8578739288e15c2a65391241e4addda491dc15f0", + "bundleSha256": "2b64d4f4e357692b4bf697dd226ca4938638078c58e302166947fbb2594bf95a" + }, + { + "command": "parser and lifecycle source", + "tests": 47, + "pass": 47, + "file": "refactor/.cache/vtt-thumbnail03-parser-unit-source.log", + "sha256": "525eaca046c50a46814153f99c2dcda5142b85e508c059a9bda01b9e91da93ac", + "scope": "26 parser tests plus 21 lifecycle tests. Pure parser assertions use source; the 15 malformed payload cases, extended registration and 21 lifecycle cases also execute the selected factory." + }, + { + "command": "native browser source", + "file": "refactor/.cache/vtt-thumbnail03-parser-browser-source.json", + "sha256": "a4edf319187bd6ab0fa4ca2be0f49218b72ee1ce748d757c0ed584bbd0b45ac6", + "resultsDirectory": "refactor/.cache/vtt-thumbnail03-parser-browser-source-results", + "stats": { + "startTime": "2026-09-13T00:46:09.142Z", + "duration": 13536.269, + "expected": 18, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "VTT native published core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: destroy aborts a pending native fetch and settles registration", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: destroy aborts a pending native fetch and settles registration", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: destroy aborts a pending native fetch and settles registration", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: destroy aborts a pending native fetch and settles registration", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: destroy aborts a pending native fetch and settles registration", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "Load request cancelled" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: destroy aborts a pending native fetch and settles registration", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "f31ac55a2d4cb3efcbf32d824237c288ce3d429d2d7bd5eb7de9df34639f5f72", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "Load request cancelled" + } + } + ], + "consoleErrors": [] + } + ] + }, + { + "command": "parser and lifecycle main", + "tests": 47, + "pass": 47, + "file": "refactor/.cache/vtt-thumbnail03-parser-unit-main.log", + "sha256": "c51e4bcd0dd2fd100277556483d74d38638140719b580b1ec810ce067c0a562c", + "scope": "26 parser tests plus 21 lifecycle tests. Pure parser assertions use source; the 15 malformed payload cases, extended registration and 21 lifecycle cases also execute the selected factory." + }, + { + "command": "native browser main", + "file": "refactor/.cache/vtt-thumbnail03-parser-browser-main.json", + "sha256": "bf7ffee07525d6516627dba6941cfce29d73dbb2a9e21e79487685b6c4a9a1c2", + "resultsDirectory": "refactor/.cache/vtt-thumbnail03-parser-browser-main-results", + "stats": { + "startTime": "2026-09-13T00:46:23.888Z", + "duration": 12638.268, + "expected": 18, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "VTT native published core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: destroy aborts a pending native fetch and settles registration", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: destroy aborts a pending native fetch and settles registration", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: destroy aborts a pending native fetch and settles registration", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: destroy aborts a pending native fetch and settles registration", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: destroy aborts a pending native fetch and settles registration", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "Load request cancelled" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: destroy aborts a pending native fetch and settles registration", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "Load request cancelled" + } + } + ], + "consoleErrors": [] + } + ] + }, + { + "command": "parser and lifecycle legacy", + "tests": 47, + "pass": 47, + "file": "refactor/.cache/vtt-thumbnail03-parser-unit-legacy.log", + "sha256": "6db481857a04e7e6fa6ea0b47ce7dd5ce28bd0a9fe93f70c7f416b962b2e7f56", + "scope": "26 parser tests plus 21 lifecycle tests. Pure parser assertions use source; the 15 malformed payload cases, extended registration and 21 lifecycle cases also execute the selected factory." + }, + { + "command": "native browser legacy", + "file": "refactor/.cache/vtt-thumbnail03-parser-browser-legacy.json", + "sha256": "7ea1ec9c761e87a7b5b735d9dd1731628e8efd8faf6ec37241764b4cf27bf29c", + "resultsDirectory": "refactor/.cache/vtt-thumbnail03-parser-browser-legacy-results", + "stats": { + "startTime": "2026-09-13T00:46:37.635Z", + "duration": 13811.528999999999, + "expected": 18, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "VTT native published core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: destroy aborts a pending native fetch and settles registration", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: destroy aborts a pending native fetch and settles registration", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + }, + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: destroy aborts a pending native fetch and settles registration", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: destroy aborts a pending native fetch and settles registration", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "NS_BINDING_ABORTED" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native published core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native published core: destroy aborts a pending native fetch and settles registration", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "published", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "Load request cancelled" + } + } + ], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: real progress hover selects sprite regions and destroy removes owned UI", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "pixels": [ + [ + 255, + 0, + 0, + 255 + ], + [ + 0, + 0, + 255, + 255 + ] + ] + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: malformed sprite rejects before mounting and a corrected registration recovers", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "recoveredAfter": { + "name": "TypeError", + "message": "Invalid VTT thumbnail at line 4: rectangle coordinates must be finite non-negative decimals" + } + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [], + "consoleErrors": [] + }, + { + "title": "VTT native candidate core: destroy aborts a pending native fetch and settles registration", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "candidate": { + "core": "candidate", + "implementation": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "nativeAbort": true + }, + "core": { + "published": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "candidate": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "e8945bc9d3ff0a6e247772a662f874df13875f7dbeb949968943b9589f6e2946", + "bytes": 378073 + } + }, + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/vtt-held.vtt", + "resourceType": "fetch", + "failure": { + "errorText": "Load request cancelled" + } + } + ], + "consoleErrors": [] + } + ] + }, + { + "command": "actual ESM import and controlled registration/hover/destroy", + "file": "packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.mjs", + "sha256": "e846cdff71fffd46146c837fb31af119fc113ce48b837b8170fc704349354b94", + "status": "pass", + "scope": "Native import of actual .mjs; controlled host registration/hover/destroy, not installed tarball" + }, + { + "command": "yarn ci:check", + "tests": { + "unit": 1727, + "engineering": 14, + "baseline": 323, + "total": 2064 + }, + "file": "refactor/.cache/vtt-thumbnail03-parser-ci.log", + "sha256": "704b458de2452cf63964dd1d379d0d63ec219a462aa727832d9334d673e58f52" + }, + { + "command": "focused eslint --no-fix", + "result": "pass", + "file": "refactor/.cache/vtt-thumbnail03-parser-lint-final.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ], + "limitations": [ + "Thumbnail metadata profile only, not full subtitle conformance", + "Public types and older CJS forms await 04", + "Full device/core combinations, installed packages and demo/editor await 05/06" + ], + "formatReference": { + "url": "https://www.w3.org/TR/webvtt1/", + "checked": "2026-09-13", + "scope": "Cue identifiers, timing settings, NOTE and caption metadata blocks; legacy extensions remain explicit, no full subtitle conformance claim" + }, + "source": { + "packages/artplayer-plugin-vtt-thumbnail/src/getVttArray.js": "3c5d78bec07628eafc5eb0b4afd86118cfc40242a1ae9597eef893c9533e61dc", + "packages/artplayer-plugin-vtt-thumbnail/src/index.js": "e6e5a51a8011e0cdc312c713f746bd9e229d4edb563f29c9d7b5eae3f39d371d", + "packages/artplayer-plugin-vtt-thumbnail/src/lifetime.js": "e13e98ca5f2f1de0f591437a7ca93addef0b0e2e19bcae9e30aa687a71a6a714", + "packages/artplayer-plugin-vtt-thumbnail/src/parseVtt.js": "aea38a4e85c0335f22ac4c3095b57b669eef724f5e40c0c48892c596be5545af", + "packages/artplayer-plugin-vtt-thumbnail/src/preview.js": "a0e43d74b77e2f6941028d38c45ca59626d58d033d7d2f1ac8ee88ded0943bfa", + "packages/artplayer-plugin-vtt-thumbnail/src/request.js": "dd161a0780b926bb93de50779c961c7d2b1c66ceb8a5eea31b8fe7d9123c55f1" + }, + "demo": { + "file": "docs/assets/sample/bbb-thumbnails.vtt", + "cuesCompared": 120, + "sha256": "1b88c30cf67d5c3da8281a150e8277858781c8077df0c6bfd2f32550da7323ba" + }, + "artifacts": { + "packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.js": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "docs/compiled/artplayer-plugin-vtt-thumbnail.js": "bd6333a850278d985cf1f8d5dc9ff2b1ba9c8948b19cfafabecc6a97304f2754", + "packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.legacy.js": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "docs/compiled/artplayer-plugin-vtt-thumbnail.legacy.js": "137c768a13febe328c274e4dd3d520755e5e746dc25cb5a69dc27899903616d6", + "packages/artplayer-plugin-vtt-thumbnail/dist/artplayer-plugin-vtt-thumbnail.mjs": "e846cdff71fffd46146c837fb31af119fc113ce48b837b8170fc704349354b94", + "docs/compiled/artplayer-plugin-vtt-thumbnail.mjs": "e846cdff71fffd46146c837fb31af119fc113ce48b837b8170fc704349354b94" + } +} diff --git a/refactor/changes/2026-09-13-PKG-VTT-THUMB-03-parser.md b/refactor/changes/2026-09-13-PKG-VTT-THUMB-03-parser.md new file mode 100644 index 000000000..6d80ec706 --- /dev/null +++ b/refactor/changes/2026-09-13-PKG-VTT-THUMB-03-parser.md @@ -0,0 +1,38 @@ +# PKG-VTT-THUMB-03 解析与模块整理完成 + +在 524ddf78 的请求/资源检查点基础上,完成纯解析与区间查找边界。 +parseVtt 导出纯 findThumbnail,preview 只负责消费 cue、定位和计时器。 +注册、请求、生命周期、解析、视图及旧源码 helper 的职责详见包内 ARCHITECTURE.md。 + +## 格式支持和修复 + +新解析器识别 BOM/CRLF、header 注释、cue ID、NOTE 注释、STYLE/REGION 块与 timing settings。 +这些块不用于修改播放器样式。三位及更长小时数不再被截断。无效头、时间戳、逆向区间、 +缺失图片、重复/未知 xywh 键、非有限/负数坐标、零尺寸等给出带原始行号的 TypeError, +在安装 UI 之前整体拒绝。坐标前后空白现在规范为可用 CSS 数字。 + +格式结构参考 [W3C WebVTT](https://www.w3.org/TR/webvtt1/)(2026-09-13 核对), +采用自有实现,没有引入第三方解析器或复制其实现。这里仅解析 thumbnail metadata, +不是完整字幕解析/渲染器;不处理多行字幕 payload、timestamp-map 偏移或全部字幕设置。 + +## 兼容边界 + +保留 floor 时间边界、首个包含端点的命中、原始输入顺序、重叠/未排序 cue、正常小数矩形、 +四键排列与字面 URL 拼接。还保留历史紧凑箭头、纯秒时间、分钟/秒字段的进位算术、 +零时长 cue、紧密 cue 对及 time/payload 间空行;不因标准校验重新收紧已可用的旧输入。 +空文件/仅头/仅注释均为空预览。真实 demo 120 个 cue 与旧 parser 深度比较完全相同。 +坏格式曾生成错误 CSS 或无法定位的异常,不视为要求继续保持的有效 API。 + +## 验证 + +26 项解析测试在固定旧 parser/运行产物上 5 通过、21 失败;新源码通过。 +坏输入还通过候选工厂验证 Promise 拒绝及监听器/控件清理;扩展 cue 验证重叠首个命中。 +浏览器用真实 HTTP 文本、sprite、旧/新核心验证扩展 VTT 悬停与坏格式拒绝后的重试恢复, +继续运行原生请求取消和 destroy(false) 清理。各入口和最终 CI 以 +[验证记录](../baselines/vtt-thumbnail-parser.json)为准,不把纯 parser 测试称为安装包证据。 + +本任务完成的是 03 的内部结构和已复现输入/生命周期修复。04 继续严格 TS、公开类型和 +旧消费形式;05/06 继续旧核心范围、移动/全屏/chapter 组合、安装/demo/编辑器。 +类型/导出/完整生命周期风险继续由后续任务承接,不能据此声称包已可发布。 +无新依赖或版本变化;正常构建生成 main/legacy/ESM 及 docs 副本。本任务独立提交, +回退只恢复解析检查点,前次资源修复仍可保留;不推送或发布。 diff --git a/refactor/plan.md b/refactor/plan.md index 8f248f527..71e52a926 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。 -状态:todo 93 / doing 13 / blocked 0 / done 116 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 93 / doing 12 / blocked 0 / done 117 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -184,7 +184,7 @@ | --- | --- | --- | --- | --- | --- | --- | | PKG-VTT-THUMB-01 | artplayer-plugin-vtt-thumbnail
核对包契约与历史用法 | BASE-05 | vtt/style、异步返回、预览控件名与样式契约 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | M | done | | PKG-VTT-THUMB-02 | artplayer-plugin-vtt-thumbnail
建立特有行为与错误测试 | PKG-VTT-THUMB-01, ENG-03, ENG-05 | VTT 解析、xywh、相对 URL、时间边界、请求失败和定位 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | M | done | -| PKG-VTT-THUMB-03 | artplayer-plugin-vtt-thumbnail
整理内部职责与资源 | PKG-VTT-THUMB-02, CORE-09, CORE-19 | 纯解析/区间查找与 DOM、请求/定时器生命周期分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | M | doing | +| PKG-VTT-THUMB-03 | artplayer-plugin-vtt-thumbnail
整理内部职责与资源 | PKG-VTT-THUMB-02, CORE-09, CORE-19 | 纯解析/区间查找与 DOM、请求/定时器生命周期分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | M | done | | PKG-VTT-THUMB-04 | artplayer-plugin-vtt-thumbnail
迁移自有源码和公开类型 | PKG-VTT-THUMB-03, ENG-04, ENG-06, CORE-07 | cue/rect/result 和参数可选性保持兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | todo | | PKG-VTT-THUMB-05 | artplayer-plugin-vtt-thumbnail
验证新旧核心和组合 | PKG-VTT-THUMB-04, CORE-22 | 鼠标和移动进度、全屏、切源、chapter 组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | todo | | PKG-VTT-THUMB-06 | artplayer-plugin-vtt-thumbnail
验证分发并同步文档 | PKG-VTT-THUMB-05, ENG-07 | vtt.thumbnail.js、VTT/图片资源解析和产物验证 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | M | todo | @@ -499,7 +499,7 @@ - PKG-AUTO-THUMB-03: [记录](changes/2026-09-13-PKG-AUTO-THUMB-03-lifecycle-checkpoint.md) [记录](baselines/auto-thumbnail-lifecycle-checkpoint.json) [记录](changes/2026-09-13-PKG-AUTO-THUMB-03-hidden-renderer.md) [记录](baselines/auto-thumbnail-hidden-renderer.json) [记录](baselines/auto-thumbnail-timeline-media.json) [记录](changes/2026-09-13-PKG-AUTO-THUMB-03-frame-presentation.md) [记录](baselines/auto-thumbnail-frame-presentation.json) - PKG-VTT-THUMB-01: [记录](baselines/vtt-thumbnail-release.json) [记录](baselines/vtt-thumbnail-contract.md) [记录](changes/2026-09-13-PKG-VTT-THUMB-01-contract.md) - PKG-VTT-THUMB-02: [记录](baselines/vtt-thumbnail-failures.json) [记录](changes/2026-09-13-PKG-VTT-THUMB-02-failures.md) -- PKG-VTT-THUMB-03: [记录](changes/2026-09-13-PKG-VTT-THUMB-03-resources.md) [记录](baselines/vtt-thumbnail-resources.json) +- PKG-VTT-THUMB-03: [记录](changes/2026-09-13-PKG-VTT-THUMB-03-resources.md) [记录](baselines/vtt-thumbnail-resources.json) [记录](changes/2026-09-13-PKG-VTT-THUMB-03-parser.md) [记录](baselines/vtt-thumbnail-parser.json) - PKG-HLS-01: [记录](baselines/hls-control-contract.md) [记录](baselines/hls-control-release.json) [记录](changes/2026-09-11-PKG-HLS-01-contract.md) - PKG-HLS-02: [记录](hls-validation.md) [记录](baselines/hls-sdk.json) [记录](baselines/hls-validation.json) [记录](changes/2026-09-11-PKG-HLS-02-tests.md) - PKG-HLS-03: [记录](changes/2026-09-12-PKG-HLS-03-modules.md) [记录](baselines/hls-modules-validation.json) [记录](hls-validation.md) diff --git a/refactor/progress.md b/refactor/progress.md index 18838bb27..74edc91a1 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,16 @@ # 进度与证据 +## PKG-VTT-THUMB-03 内部模块与解析修复完成 + +在资源检查点上完成纯解析/首个区间查找,支持 cue ID、注释/元数据块和 timing settings, +修复长小时截断及非法输入无定位/坏 CSS。26 项解析在旧版 5 通过/21 失败;正常 demo 120 cue +完全相同。源码/main/legacy 的解析+生命周期各 47 通过,原生三入口各 18 通过,共 54 项。 +实际 ESM 与完整 CI 2064 项(1727 单元、14 工程、323 基线)通过;无依赖/声明/版本变更。 +见[变更](changes/2026-09-13-PKG-VTT-THUMB-03-parser.md)与[验证](baselines/vtt-thumbnail-parser.json)。 +解析风险已关闭,类型/导出/完整设备和组合仍待 04–06。下一步严格 TS 及旧消费者。 +222 项:117 done、12 doing、93 todo;独立本地任务提交,不推送、不发布。 + + ## PKG-VTT-THUMB-03 请求与预览资源检查点(doing) 生产入口拆为注册、生命周期、请求、预览和纯解析,保留旧源码 helper。已修复销毁后 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index 1820d1246..dc251eb55 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -190,7 +190,7 @@ | AUTO-THUMB-ENCODE-01 | open / 已复现 | Unserialized encodes include blank output and lack media/canvas/input failure handling | PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-05 | | AUTO-THUMB-PIXEL-01 | open / 已复现 | Historical auto-thumbnail yields black sampled JPEG cells on Windows WebKit while played native video and static JPEG controls yield color | PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-05 | | VTT-THUMB-DIST-01 | open / 已复现 | Historical 1.0.0 main/legacy contain invalid regular expressions and cannot load | PKG-VTT-THUMB-06 | -| VTT-THUMB-PARSE-01 | open / 已复现 | Historical compiled arrows differ from shipped source; parsing floors bounds and assumes alternating lines | PKG-VTT-THUMB-02, PKG-VTT-THUMB-03 | +| VTT-THUMB-PARSE-01 | resolved / 已复现 | Historical compiled arrows differ from shipped source; parsing floors bounds and assumes alternating lines | PKG-VTT-THUMB-02, PKG-VTT-THUMB-03 | | VTT-THUMB-LIFE-01 | open / 已复现 | Fetch completion, setBar listeners and mobile timers have no destroy ownership | PKG-VTT-THUMB-02, PKG-VTT-THUMB-03, PKG-VTT-THUMB-05 | | VTT-THUMB-TYPE-01 | open / 已复现 | Published declarations falsely describe async registration as a synchronous result | PKG-VTT-THUMB-04 | | VTT-THUMB-EXPORT-01 | open / 已复现 | Older CommonJS default objects and thumbnails control names differ from latest direct export and vtt-thumbnail name | PKG-VTT-THUMB-04, PKG-VTT-THUMB-05, PKG-VTT-THUMB-06 | diff --git a/refactor/risks.json b/refactor/risks.json index 8dcdb0128..f58ed4d8c 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -4141,7 +4141,7 @@ "id": "VTT-THUMB-PARSE-01", "title": "Historical compiled arrows differ from shipped source; parsing floors bounds and assumes alternating lines", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-VTT-THUMB-02", "PKG-VTT-THUMB-03" @@ -4157,7 +4157,14 @@ "refactor/baselines/vtt-thumbnail-resources.json" ], "compatibleResolution": "Preserve valid cue selection and geometry; test malformed data and standard VTT separately from old compiler defects.", - "closureCriteria": "Parser fixtures include malformed, empty, real demo VTT, fractional and shared bounds, relative URLs and historical accepted formats." + "closureCriteria": "Parser fixtures include malformed, empty, real demo VTT, fractional and shared bounds, relative URLs and historical accepted formats.", + "resolutionEvidence": [ + "test/vtt-thumbnail-parser.test.js", + "test/browser/vtt-thumbnail-lifecycle.spec.js", + "refactor/baselines/vtt-thumbnail-parser.json", + "refactor/changes/2026-09-13-PKG-VTT-THUMB-03-parser.md" + ], + "resolutionRationale": "Historical compiled/source arrow differences stay frozen. Pure parser validates malformed input with original lines, handles metadata blocks/IDs/settings and long hours, retains legacy boundaries/URL/coordinate semantics and compares all 120 demo cues. Candidate factories and native old/new core cases demonstrate rejection before mounting and recovery. This resolves the thumbnail parsing scope; declaration, full device/core and installed-package risks remain separate." }, { "id": "VTT-THUMB-LIFE-01", diff --git a/refactor/tasks.json b/refactor/tasks.json index e6e082fe9..b441cd01c 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -1897,13 +1897,15 @@ "CORE-09", "CORE-19" ], - "status": "doing", + "status": "done", "risk": "M", "deliverable": "纯解析/区间查找与 DOM、请求/定时器生命周期分离", "acceptance": "结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过", "evidence": [ "changes/2026-09-13-PKG-VTT-THUMB-03-resources.md", - "baselines/vtt-thumbnail-resources.json" + "baselines/vtt-thumbnail-resources.json", + "changes/2026-09-13-PKG-VTT-THUMB-03-parser.md", + "baselines/vtt-thumbnail-parser.json" ] }, { diff --git a/test/browser/vtt-thumbnail-lifecycle.spec.js b/test/browser/vtt-thumbnail-lifecycle.spec.js index c76081e9f..e7e8609f9 100644 --- a/test/browser/vtt-thumbnail-lifecycle.spec.js +++ b/test/browser/vtt-thumbnail-lifecycle.spec.js @@ -5,10 +5,11 @@ import { expect, test } from './fixtures.js' const implementation = await vttThumbnailCandidate() const cues = 'WEBVTT\n\n00:00.000 --> 00:04.000\nvtt-sprite.svg#xywh=0,0,80,45\n\n00:04.000 --> 00:08.000\nvtt-sprite.svg#xywh=80,0,80,45\n' const sprite = '' +const extendedCues = '\uFEFFWEBVTT thumbnails\r\n\r\nNOTE source metadata\r\nignored\r\n\r\nSTYLE\r\n::cue { color: red }\r\n\r\nREGION\r\nid:unused\r\n\r\nfirst\r\n00:00.000\t-->\t00:04.000 align:start\r\nvtt-sprite.svg#xywh=0,0,80,45\r\n\r\nsecond\r\n00:04.000 --> 00:08.000\r\nvtt-sprite.svg#xywh=80,0,80,45\r\n' for (const core of ['published', 'candidate']) { test(`VTT native ${core} core: real progress hover selects sprite regions and destroy removes owned UI`, async ({ page }, testInfo) => { - await page.route('**/test/vtt-cues.vtt', route => route.fulfill({ contentType: 'text/vtt', body: cues })) + await page.route('**/test/vtt-cues.vtt', route => route.fulfill({ contentType: 'text/vtt', body: extendedCues })) await page.route('**/test/vtt-sprite.svg', route => route.fulfill({ contentType: 'image/svg+xml', body: sprite })) await page.goto(`/test/player.html?core=${core}`) await page.addScriptTag({ content: implementation.code }) @@ -45,6 +46,32 @@ for (const core of ['published', 'candidate']) { await testInfo.attach('vtt-candidate', { body: JSON.stringify({ core, implementation: hash(implementation.code), pixels }), contentType: 'application/json' }) }) + test(`VTT native ${core} core: malformed sprite rejects before mounting and a corrected registration recovers`, async ({ page }, testInfo) => { + await page.route('**/test/vtt-invalid.vtt', route => route.fulfill({ contentType: 'text/vtt', body: 'WEBVTT\n\n00:00.000 --> 00:08.000\nvtt-sprite.svg#xywh=0,0,bad,45\n' })) + await page.route('**/test/vtt-cues.vtt', route => route.fulfill({ contentType: 'text/vtt', body: extendedCues })) + await page.route('**/test/vtt-sprite.svg', route => route.fulfill({ contentType: 'image/svg+xml', body: sprite })) + await page.goto(`/test/player.html?core=${core}`) + await page.addScriptTag({ content: implementation.code }) + const error = await page.evaluate(async () => { + window.createPlayer('/test/pattern.mp4') + try { + await window.art.plugins.add(window.artplayerPluginVttThumbnail({ vtt: '/test/vtt-invalid.vtt' })) + return null + } + catch (error) { + return { name: error.name, message: error.message } + } + }) + expect(error.name).toBe('TypeError') + expect(error.message).toContain('line 4:') + await expect(page.locator('.art-control-vtt-thumbnail')).toHaveCount(0) + await page.evaluate(() => window.art.plugins.add(window.artplayerPluginVttThumbnail({ vtt: '/test/vtt-cues.vtt' }))) + await expect(page.locator('.art-control-vtt-thumbnail')).toHaveCount(1) + await page.evaluate(() => window.art.destroy(false)) + await expect(page.locator('.art-control-vtt-thumbnail')).toHaveCount(0) + await testInfo.attach('vtt-candidate', { body: JSON.stringify({ core, implementation: hash(implementation.code), recoveredAfter: error }), contentType: 'application/json' }) + }) + test(`VTT native ${core} core: destroy aborts a pending native fetch and settles registration`, async ({ page }, testInfo) => { let release let requested diff --git a/test/vtt-thumbnail-parser.test.js b/test/vtt-thumbnail-parser.test.js new file mode 100644 index 000000000..51b1afa41 --- /dev/null +++ b/test/vtt-thumbnail-parser.test.js @@ -0,0 +1,115 @@ +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import fs from 'node:fs' +import process from 'node:process' +// eslint-disable-next-line test/no-import-node-test -- Pure parser regression runner. +import test from 'node:test' +import { loadModules } from './helpers/load.js' +import { vttThumbnailCandidate, vttThumbnailEnvironment } from './helpers/vtt-thumbnail.js' + +const baseline = process.env.ARTPLAYER_VTT_PARSER_BASELINE === '1' +const module = baseline + ? await import(`data:text/javascript,${encodeURIComponent(execFileSync('git', ['show', '524ddf784fdc143d3020040e4de1a9e0921698ce:packages/artplayer-plugin-vtt-thumbnail/src/parseVtt.js'], { encoding: 'utf8' }))}`) + : await loadModules({ defaultParser: 'packages/artplayer-plugin-vtt-thumbnail/src/parseVtt' }) +const parse = baseline ? module.default : module.defaultParser +const implementation = await vttThumbnailCandidate() +const cue = (time = '00:00.900 --> 00:05.900', image = 'sprite.jpg#xywh=10,20,80,45') => `${time}\n${image}` + +test('VTT parser retains floored boundaries, raw xywh fields and lexical relative URL joining', () => { + assert.deepEqual(parse(`WEBVTT\n\n${cue()}`, 'https://cdn.test/path/cues.vtt'), [{ start: 0, end: 5, url: 'https://cdn.test/path/sprite.jpg', x: '10', y: '20', w: '80', h: '45' }]) + assert.equal(parse(`WEBVTT\n${cue(undefined, '../sprite.jpg#yxhw=20,10,45,80')}`, '/folder/cues.vtt')[0].url, '/folder/../sprite.jpg') +}) + +test('VTT parser accepts BOM, CRLF, header annotations, cue IDs, timing settings and NOTE blocks', () => { + const text = `\uFEFFWEBVTT thumbnails\r\n\r\nNOTE author\r\nignored comment\r\n\r\nfirst-id\r\n${cue('00:00.900\t-->\t00:05.900 align:start')}` + assert.equal(parse(text)[0].w, '80') + assert.equal(parse(text)[0].end, 5) +}) + +test('VTT parser ignores STYLE and REGION blocks without applying caption CSS', () => { + const text = `WEBVTT\n\nSTYLE\n::cue { color: red }\n\nREGION\nid:region1\nwidth:50%\n\n${cue()}` + assert.equal(parse(text).length, 1) +}) + +test('VTT parser preserves compact arrows, seconds-only legacy timestamps and dense cue pairs', () => { + assert.deepEqual(parse(`WEBVTT\n${cue('00-->05')}\n${cue('05-->10')}`).map(item => [item.start, item.end]), [[0, 5], [5, 10]]) + assert.equal(parse(`WEBVTT\n${cue('00:70-->01:20')}`)[0].start, 70, 'Keep previously accepted numeric time arithmetic outside strict subtitle syntax') +}) + +test('VTT parser reads complete long-hour timestamps instead of truncating to their last two digits', () => { + const parsed = parse(`WEBVTT\n${cue('123:00:00.100 --> 123:00:05.900')}`) + assert.equal(parsed[0].start, 442800) + assert.equal(parsed[0].end, 442805) +}) + +test('VTT parser keeps empty documents, empty header and comment-only documents empty', () => { + for (const text of ['', ' \n\t', '\uFEFFWEBVTT', 'WEBVTT\n\nNOTE empty\ncomment\n']) + assert.deepEqual(parse(text), []) +}) + +test('VTT parser retains valid decimal coordinates and all four-key permutations', () => { + for (const keys of ['xywh', 'yxhw', 'whxy', 'hyxw']) { + const values = { x: '0.5', y: '2.25', w: '80.5', h: '45.25' } + const parsed = parse(`WEBVTT\n${cue(undefined, `sprite.jpg#${keys}=${[...keys].map(key => values[key]).join(',')}`)}`)[0] + for (const key of keys) + assert.equal(parsed[key], values[key]) + } +}) + +test('VTT parser preserves legacy zero-length cues and blank separators inside dense pairs', () => { + const parsed = parse('WEBVTT\n00:05.000-->00:05.000\n\nsprite.jpg#xywh=0,0,80,45') + assert.equal(parsed[0].start, 5) + assert.equal(parsed[0].end, 5) +}) + +test('VTT parser refuses non-text and numeric overflow without returning partial cues', () => { + for (const input of [null, undefined, 12, {}]) + assert.throws(() => parse(input), error => error instanceof TypeError && /line 1:/.test(error.message)) + const overflow = '9'.repeat(400) + assert.throws(() => parse(`WEBVTT\n${cue(`${overflow}:00:00.000 --> ${overflow}:00:05.000`)}`), /numeric range/) + assert.throws(() => parse(`WEBVTT\n${cue()}\n\n${cue(undefined, `sprite.jpg#xywh=0,0,${overflow},45`)}`), /finite/) +}) + +for (const [label, text, line] of [ + ['invalid header', `WRONG\n\n${cue()}`, 1], + ['missing timing after cue id', 'WEBVTT\n\nfirst-id', 3], + ['missing image', 'WEBVTT\n\n00:00.000 --> 00:05.000', 3], + ['bad timestamp separator', `WEBVTT\n\n${cue('00:00x100 --> 00:05.000')}`, 3], + ['bad fractional timestamp', `WEBVTT\n\n${cue('00:00.1 --> 00:05.000')}`, 3], + ['reverse interval', `WEBVTT\n\n${cue('00:05.000 --> 00:00.000')}`, 3], + ['missing rectangle', `WEBVTT\n\n${cue(undefined, 'sprite.jpg')}`, 4], + ['duplicate rectangle keys', `WEBVTT\n\n${cue(undefined, 'sprite.jpg#xxwh=0,0,80,45')}`, 4], + ['unknown rectangle keys', `WEBVTT\n\n${cue(undefined, 'sprite.jpg#abcd=0,0,80,45')}`, 4], + ['missing coordinate', `WEBVTT\n\n${cue(undefined, 'sprite.jpg#xywh=0,0,80')}`, 4], + ['extra coordinate', `WEBVTT\n\n${cue(undefined, 'sprite.jpg#xywh=0,0,80,45,5')}`, 4], + ['negative position', `WEBVTT\n\n${cue(undefined, 'sprite.jpg#xywh=-1,0,80,45')}`, 4], + ['non-finite width', `WEBVTT\n\n${cue(undefined, 'sprite.jpg#xywh=0,0,Infinity,45')}`, 4], + ['zero width', `WEBVTT\n\n${cue(undefined, 'sprite.jpg#xywh=0,0,0,45')}`, 4], + ['non-numeric width', `WEBVTT\n\n${cue(undefined, 'sprite.jpg#xywh=0,0,bad,45')}`, 4], +]) { + test(`VTT parser reports ${label} as a TypeError with the original line`, async () => { + assert.throws(() => parse(text), error => error instanceof TypeError && error.message.includes(`line ${line}:`)) + const env = vttThumbnailEnvironment(implementation, { text }) + await assert.rejects(env.factory({})(env.art), error => error.name === 'TypeError' && error.message.includes(`line ${line}:`)) + assert.equal(env.controls.length, 0) + assert([...env.listeners.values()].every(items => items.length === 0)) + }) +} + +test('VTT parser handles every real demo cue identically to the frozen parser', async () => { + const old = await import(`data:text/javascript,${encodeURIComponent(execFileSync('git', ['show', '524ddf784fdc143d3020040e4de1a9e0921698ce:packages/artplayer-plugin-vtt-thumbnail/src/parseVtt.js'], { encoding: 'utf8' }))}`) + const text = fs.readFileSync('docs/assets/sample/bbb-thumbnails.vtt', 'utf8') + const actual = parse(text, '/assets/sample/bbb-thumbnails.vtt') + assert.equal(actual.length, 120) + assert.deepEqual(actual, old.default(text, '/assets/sample/bbb-thumbnails.vtt')) +}) + +test('VTT candidate registers extended VTT and retains first-match selection for overlapping unsorted cues', async () => { + const env = vttThumbnailEnvironment(implementation, { text: `WEBVTT\n\nNOTE comment\n\nlate\n${cue('00:04.100 --> 00:08.900', 'first.jpg#xywh=0,0,80,45')}\n\nearly\n${cue('00:00.900 --> 00:05.900', 'second.jpg#xywh=80,0,80,45')}` }) + await env.factory({})(env.art) + await env.hover(0.4) + assert.equal(env.styles.backgroundImage, 'url(first.jpg)') + await env.hover(0.3) + assert.equal(env.styles.backgroundImage, 'url(second.jpg)') + await env.emit('destroy') +})