mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
refactor(vtt-thumbnail): [PKG-VTT-THUMB-04] type runtime data and resource boundaries
This commit is contained in:
1 parent
80588a3046
commit
7f2906ae52
22 files changed
+2940
-75
No files matched your search
@@ -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,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&&m<n.clientWidth?function(t,e,i){c(t,"backgroundImage",`url(${e.url})`),c(t,"height",`${e.h}px`),c(t,"width",`${e.w}px`),c(t,"backgroundPosition",`-${e.x}px -${e.y}px`),i<=e.w/2?c(t,"left",0):i>n.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<r.length&&!r[s].trim();)s++}if(l(),s===r.length)return[];/^WEBVTT(?:[\t ].*)?$/.test(r[s].trim())&&!r[s].includes("--\x3e")||t(s+1,"expected WEBVTT header"),s++;const c=[];for(;s<r.length&&(l(),s!==r.length);){const i=r[s].trim();if(/^NOTE(?:[\t ]|$)/.test(i)||"STYLE"===i||"REGION"===i){for(;s<r.length&&r[s].trim();)s++;continue}const u=s+1;i.includes("--\x3e")||(s++,s!==r.length&&r[s].trim()||t(u,"cue identifier must be followed by timing"));const a=s+1,d=r[s].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/);d||t(a,"invalid cue timing");const f=e(d[1],a),m=e(d[2],a);m<f&&t(a,"cue end precedes its start"),s++,l(),s===r.length&&t(a,"missing sprite image");const h=n(r[s].trim(),o,s+1);c.push({start:Math.floor(f),end:Math.floor(m),...h}),s++}return c}(m,f),p=i({lifetime:a,thumbnails:h,progress:u,duration:()=>r.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}}}});
|
||||
!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&&m<n.clientWidth?function(t,e,i){c(t,"backgroundImage",`url(${e.url})`),c(t,"height",`${e.h}px`),c(t,"width",`${e.w}px`),c(t,"backgroundPosition",`-${e.x}px -${e.y}px`),i<=Number(e.w)/2?c(t,"left",0):i>n.clientWidth-Number(e.w)/2?c(t,"left",n.clientWidth-Number(e.w)+"px"):c(t,"left",i-Number(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{setStyle:s,isMobile:l,addClass:c}=r.constructor.utils,{$progress:u}=r.template,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<r.length&&!r[s].trim();)s++}if(l(),s===r.length)return[];/^WEBVTT(?:[\t ].*)?$/.test(r[s].trim())&&!r[s].includes("--\x3e")||t(s+1,"expected WEBVTT header"),s++;const c=[];for(;s<r.length&&(l(),s!==r.length);){const i=r[s].trim();if(/^NOTE(?:[\t ]|$)/.test(i)||"STYLE"===i||"REGION"===i){for(;s<r.length&&r[s].trim();)s++;continue}const u=s+1;i.includes("--\x3e")||(s++,s!==r.length&&r[s].trim()||t(u,"cue identifier must be followed by timing"));const a=s+1,d=r[s].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/);d||t(a,"invalid cue timing");const f=e(d[1],a),m=e(d[2],a);m<f&&t(a,"cue end precedes its start"),s++,l(),s===r.length&&t(a,"missing sprite image");const h=n(r[s].trim(),o,s+1);c.push({start:Math.floor(f),end:Math.floor(m),...h}),s++}return c}(m,f),p=i({lifetime:a,thumbnails:h,progress:u,duration:()=>r.duration,setStyle:s,isMobile:l}),b=o.style||{};return a.closed||r.controls.add({name:"vtt-thumbnail",position:"top",index:20,style:b,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}}}});
|
||||
@@ -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=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&&p<n.clientWidth?function(t,e,o){u(t,"backgroundImage",`url(${e.url})`),u(t,"height",`${e.h}px`),u(t,"width",`${e.w}px`),u(t,"backgroundPosition",`-${e.x}px -${e.y}px`),o<=e.w/2?u(t,"left",0):o>n.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<n.length&&!n[o].trim();)o++}if(i(),o===n.length)return[];/^WEBVTT(?:[\t ].*)?$/.test(n[o].trim())&&!n[o].includes("--\x3e")||s(o+1,"expected WEBVTT header"),o++;const l=[];for(;o<n.length&&(i(),o!==n.length);){const t=n[o].trim();if(/^NOTE(?:[\t ]|$)/.test(t)||"STYLE"===t||"REGION"===t){for(;o<n.length&&n[o].trim();)o++;continue}const a=o+1;t.includes("--\x3e")||(o++,o!==n.length&&n[o].trim()||s(a,"cue identifier must be followed by timing"));const d=o+1,f=n[o].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/);f||s(d,"invalid cue timing");const p=c(f[1],d),m=c(f[2],d);m<p&&s(d,"cue end precedes its start"),o++,i(),o===n.length&&s(d,"missing sprite image");const h=u(n[o].trim(),e,o+1);l.push(r({start:Math.floor(p),end:Math.floor(m)},h)),o++}return l}(h,m),b=a({lifetime:f,thumbnails:y,progress:d,duraLine truncated
|
||||
!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,r=(e,n,o)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[n]=o,i=(t,i)=>{for(var l in i||(i={}))n.call(i,l)&&r(t,l,i[l]);if(e)for(var l of e(i))o.call(i,l)&&r(t,l,i[l]);return t},l=(t,e,n)=>new Promise((o,r)=>{var i=t=>{try{s(n.next(t))}catch(e){r(e)}},l=t=>{try{s(n.throw(t))}catch(e){r(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(i,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("."),r=n.split(":").reduce((t,e)=>60*t+Number(e),0)+Number(o)/1e3;return Number.isSafeInteger(Math.floor(r))||s(e,"timestamp exceeds the supported numeric range"),r}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 r=o[3].split(",").map(t=>t.trim());4!==r.length&&s(n,"expected four rectangle coordinates");const l={};for(let i=0;i<4;i++){const t=o[2][i],e=r[i];/^(?:\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 i({url:c},l)}function a({lifetime:t,thumbnails:e,progress:n,duration:o,setStyle:r,isMobile:i}){let s=null,c=0;function u(e,n,o){t.closed||r(e,n,o)}return t.own(()=>{if(c++,null!==s){const t=s;s=null,clearTimeout(t)}}),r=>(a,d,f)=>l(null,null,function*(){if(t.closed)return;const l="played"===a&&f&&i;if("hover"!==a&&!l)return;const m=n.clientWidth*d,p=d*o();if(u(r,"display","flex"),t.closed)return;const h=function(t,e){return t.find(t=>e>=t.start&&e<=t.end)}(e,p);if(!h)return u(r,"display","none");if(m>0&&m<n.clientWidth?function(t,e,o){u(t,"backgroundImage",`url(${e.url})`),u(t,"height",`${e.h}px`),u(t,"width",`${e.w}px`),u(t,"backgroundPosition",`-${e.x}px -${e.y}px`),o<=Number(e.w)/2?u(t,"left",0):o>n.clientWidth-Number(e.w)/2?u(t,"left",n.clientWidth-Number(e.w)+"px"):u(t,"left",o-Number(e.w)/2+"px")}(r,h,m):i||u(r,"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(r,"display","none"))},500);t.closed||e!==c?clearTimeout(n):s=n}})}return function(t){return e=>l(null,null,function*(){const{setStyle:n,isMobile:o,addClass:r}=e.constructor.utils,{$progress:d}=e.template,f=function(t){let e=Boolean(t.isDestroy);const n=new Set;let o;const r=new Promise(t=>{o=t}),i={get closed(){return e},own:t=>(e?l(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,r]),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{i.listen("destroy",i.dispose)}catch(s){throw i.dispose(),s}return i}(e),m={name:"artplayerPluginVttThumbnail"};try{if(f.closed)return m;const p=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()}})}(p,f);if(f.closed)return m;const b=function(t,e=""){"string"!==typeof t&&s(1,"expected text");const n=t.replace(/^\uFEFF/,"").split(/\r\n|\r|\n/);let o=0;function r(){for(;o<n.length&&!n[o].trim();)o++}if(r(),o===n.length)return[];/^WEBVTT(?:[\t ].*)?$/.test(n[o].trim())&&!n[o].includes("--\x3e")||s(o+1,"expected WEBVTT header"),o++;const l=[];for(;o<n.length&&(r(),o!==n.length);){const t=n[o].trim();if(/^NOTE(?:[\t ]|$)/.test(t)||"STYLE"===t||"REGION"===t){for(;o<n.length&&n[o].trim();)o++;continue}const a=o+1;t.includes("--\x3e")||(o++,o!==n.length&&n[o].trim()||s(a,"cue identifier must be followed by timing"));const d=o+1,f=n[o].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/);f||s(d,"invalid cue timing");const m=c(f[1],d),p=c(f[2],d);p<m&&s(d,"cue end precedes its start"),o++,r(),o===n.length&&s(d,"missing sprite image");const h=u(n[o].trim(),e,o+1);l.push(i({start:Math.floor(m),end:Math.floor(p)},h)),o++}return l}(h,p),y=a({lifetime:Line truncated
|
||||
@@ -168,12 +168,12 @@ function createPreview({ lifetime, thumbnails, progress, duration, setStyle, isM
|
||||
style(control, "height", `${cue.h}px`);
|
||||
style(control, "width", `${cue.w}px`);
|
||||
style(control, "backgroundPosition", `-${cue.x}px -${cue.y}px`);
|
||||
if (width <= cue.w / 2)
|
||||
if (width <= Number(cue.w) / 2)
|
||||
style(control, "left", 0);
|
||||
else if (width > progress.clientWidth - cue.w / 2)
|
||||
style(control, "left", `${progress.clientWidth - cue.w}px`);
|
||||
else if (width > progress.clientWidth - Number(cue.w) / 2)
|
||||
style(control, "left", `${progress.clientWidth - Number(cue.w)}px`);
|
||||
else
|
||||
style(control, "left", `${width - cue.w / 2}px`);
|
||||
style(control, "left", `${width - Number(cue.w) / 2}px`);
|
||||
}
|
||||
return (control) => async (type, percentage, event) => {
|
||||
if (lifetime.closed)
|
||||
@@ -234,7 +234,8 @@ async function requestVtt(url = "", lifetime) {
|
||||
}
|
||||
function artplayerPluginVttThumbnail(option) {
|
||||
return async (art) => {
|
||||
const { constructor: { utils: { setStyle, isMobile, addClass } }, template: { $progress } } = art;
|
||||
const { setStyle, isMobile, addClass } = art.constructor.utils;
|
||||
const { $progress } = art.template;
|
||||
const lifetime = createLifetime(art);
|
||||
const result = { name: "artplayerPluginVttThumbnail" };
|
||||
try {
|
||||
|
||||
@@ -3,27 +3,27 @@
|
||||
The factory remains `artplayerPluginVttThumbnail(option)`. Its registration function is
|
||||
asynchronous and resolves to `{ name: 'artplayerPluginVttThumbnail' }`. Existing public
|
||||
declarations still describe a synchronous result; task PKG-VTT-THUMB-04 owns that conflict
|
||||
and the strict TypeScript migration. Do not infer the return shape from those declarations.
|
||||
and the public declaration/consumer migration. The seven internal TS files are strict. Do not infer the return shape from those declarations.
|
||||
|
||||
## Modules and ownership
|
||||
|
||||
- `src/index.js` composes registration, captures the VTT URL before awaiting, reads style
|
||||
- `src/index.ts` composes registration, captures the VTT URL before awaiting, reads style
|
||||
afterwards, and installs the `vtt-thumbnail` control with its existing CSS class/index.
|
||||
- `src/lifetime.js` owns one registration's destroy listener, event listeners and cleanup
|
||||
- `src/lifetime.ts` owns one registration's destroy listener, event listeners and cleanup
|
||||
callbacks. Close is idempotent. Cleanup continues after a callback fails. An independent
|
||||
cancellation Promise settles pending work even if fetch does not honor AbortSignal.
|
||||
- `src/request.js` owns a native AbortController when available and reads the response body.
|
||||
- `src/request.ts` 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` parses thumbnail metadata and exports the pure `findThumbnail` lookup.
|
||||
- `src/parseVtt.ts` 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
|
||||
- `src/preview.ts` 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
|
||||
retain the latest published behavior.
|
||||
- `src/getVttArray.js` retains the old source helper signature as a fetch/parse wrapper.
|
||||
- `src/getVttArray.ts` retains the old source helper signature as a fetch/parse wrapper.
|
||||
The player registration uses the owned request module instead. Distribution/deep-import
|
||||
review belongs to PKG-VTT-THUMB-06.
|
||||
|
||||
@@ -32,6 +32,26 @@ removing it, cleanup compares the live `art.controls['vtt-thumbnail']` alias wit
|
||||
element. It must not remove a later replacement. Partial listener/control installation is
|
||||
rolled back if initialization throws, preserving the original rejection.
|
||||
|
||||
## Runtime types
|
||||
|
||||
`src/types.ts` defines options, result, rectangle/cue data, owned events, cleanup and
|
||||
preview inputs. Request cancellation is typed as a void branch; actual registration is
|
||||
Promise<Result>. The lifetime accepts only destroy/setBar and uses the core event tuples.
|
||||
The preview receives only its required DOM/style/duration inputs.
|
||||
|
||||
Assertions are limited to the real Artplayer constructor boundary, validated regex groups
|
||||
and loop indexes, the completed four-key rectangle, and native responses after the closed
|
||||
check. The latter is valid because native fetch returns Response and cancellation only
|
||||
resolves void after closing; malformed custom responses still throw at runtime. Numeric
|
||||
rectangle strings are explicitly converted for arithmetic while the zero style write stays
|
||||
numeric. No any, unchecked JS or skipLibCheck exception is introduced.
|
||||
|
||||
The package tsconfig also checks test/types/vtt-thumbnail-runtime.ts. It tests the async
|
||||
result, required options, CSS/URL types, event tuples, cancellation, readonly state and raw
|
||||
rectangle strings, including expected errors. It separately proves the old public result
|
||||
is synchronous in declarations; those declarations are not silently corrected in this
|
||||
runtime checkpoint. New/old declaration consumers and export aliases remain task 04.
|
||||
|
||||
## Compatibility and intentional fixes
|
||||
|
||||
Option/style remain live at their historical read points. The return stays a Promise;
|
||||
|
||||
+1
-1
@@ -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,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&&m<n.clientWidth?function(t,e,i){c(t,"backgroundImage",`url(${e.url})`),c(t,"height",`${e.h}px`),c(t,"width",`${e.w}px`),c(t,"backgroundPosition",`-${e.x}px -${e.y}px`),i<=e.w/2?c(t,"left",0):i>n.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<r.length&&!r[s].trim();)s++}if(l(),s===r.length)return[];/^WEBVTT(?:[\t ].*)?$/.test(r[s].trim())&&!r[s].includes("--\x3e")||t(s+1,"expected WEBVTT header"),s++;const c=[];for(;s<r.length&&(l(),s!==r.length);){const i=r[s].trim();if(/^NOTE(?:[\t ]|$)/.test(i)||"STYLE"===i||"REGION"===i){for(;s<r.length&&r[s].trim();)s++;continue}const u=s+1;i.includes("--\x3e")||(s++,s!==r.length&&r[s].trim()||t(u,"cue identifier must be followed by timing"));const a=s+1,d=r[s].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/);d||t(a,"invalid cue timing");const f=e(d[1],a),m=e(d[2],a);m<f&&t(a,"cue end precedes its start"),s++,l(),s===r.length&&t(a,"missing sprite image");const h=n(r[s].trim(),o,s+1);c.push({start:Math.floor(f),end:Math.floor(m),...h}),s++}return c}(m,f),p=i({lifetime:a,thumbnails:h,progress:u,duration:()=>r.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}}}});
|
||||
!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&&m<n.clientWidth?function(t,e,i){c(t,"backgroundImage",`url(${e.url})`),c(t,"height",`${e.h}px`),c(t,"width",`${e.w}px`),c(t,"backgroundPosition",`-${e.x}px -${e.y}px`),i<=Number(e.w)/2?c(t,"left",0):i>n.clientWidth-Number(e.w)/2?c(t,"left",n.clientWidth-Number(e.w)+"px"):c(t,"left",i-Number(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{setStyle:s,isMobile:l,addClass:c}=r.constructor.utils,{$progress:u}=r.template,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<r.length&&!r[s].trim();)s++}if(l(),s===r.length)return[];/^WEBVTT(?:[\t ].*)?$/.test(r[s].trim())&&!r[s].includes("--\x3e")||t(s+1,"expected WEBVTT header"),s++;const c=[];for(;s<r.length&&(l(),s!==r.length);){const i=r[s].trim();if(/^NOTE(?:[\t ]|$)/.test(i)||"STYLE"===i||"REGION"===i){for(;s<r.length&&r[s].trim();)s++;continue}const u=s+1;i.includes("--\x3e")||(s++,s!==r.length&&r[s].trim()||t(u,"cue identifier must be followed by timing"));const a=s+1,d=r[s].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/);d||t(a,"invalid cue timing");const f=e(d[1],a),m=e(d[2],a);m<f&&t(a,"cue end precedes its start"),s++,l(),s===r.length&&t(a,"missing sprite image");const h=n(r[s].trim(),o,s+1);c.push({start:Math.floor(f),end:Math.floor(m),...h}),s++}return c}(m,f),p=i({lifetime:a,thumbnails:h,progress:u,duration:()=>r.duration,setStyle:s,isMobile:l}),b=o.style||{};return a.closed||r.controls.add({name:"vtt-thumbnail",position:"top",index:20,style:b,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}}}});
|
||||
+1
-1
@@ -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=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&&p<n.clientWidth?function(t,e,o){u(t,"backgroundImage",`url(${e.url})`),u(t,"height",`${e.h}px`),u(t,"width",`${e.w}px`),u(t,"backgroundPosition",`-${e.x}px -${e.y}px`),o<=e.w/2?u(t,"left",0):o>n.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<n.length&&!n[o].trim();)o++}if(i(),o===n.length)return[];/^WEBVTT(?:[\t ].*)?$/.test(n[o].trim())&&!n[o].includes("--\x3e")||s(o+1,"expected WEBVTT header"),o++;const l=[];for(;o<n.length&&(i(),o!==n.length);){const t=n[o].trim();if(/^NOTE(?:[\t ]|$)/.test(t)||"STYLE"===t||"REGION"===t){for(;o<n.length&&n[o].trim();)o++;continue}const a=o+1;t.includes("--\x3e")||(o++,o!==n.length&&n[o].trim()||s(a,"cue identifier must be followed by timing"));const d=o+1,f=n[o].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/);f||s(d,"invalid cue timing");const p=c(f[1],d),m=c(f[2],d);m<p&&s(d,"cue end precedes its start"),o++,i(),o===n.length&&s(d,"missing sprite image");const h=u(n[o].trim(),e,o+1);l.push(r({start:Math.floor(p),end:Math.floor(m)},h)),o++}return l}(h,m),b=a({lifetime:f,thumbnails:y,progress:d,duraLine truncated
|
||||
!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,r=(e,n,o)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[n]=o,i=(t,i)=>{for(var l in i||(i={}))n.call(i,l)&&r(t,l,i[l]);if(e)for(var l of e(i))o.call(i,l)&&r(t,l,i[l]);return t},l=(t,e,n)=>new Promise((o,r)=>{var i=t=>{try{s(n.next(t))}catch(e){r(e)}},l=t=>{try{s(n.throw(t))}catch(e){r(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(i,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("."),r=n.split(":").reduce((t,e)=>60*t+Number(e),0)+Number(o)/1e3;return Number.isSafeInteger(Math.floor(r))||s(e,"timestamp exceeds the supported numeric range"),r}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 r=o[3].split(",").map(t=>t.trim());4!==r.length&&s(n,"expected four rectangle coordinates");const l={};for(let i=0;i<4;i++){const t=o[2][i],e=r[i];/^(?:\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 i({url:c},l)}function a({lifetime:t,thumbnails:e,progress:n,duration:o,setStyle:r,isMobile:i}){let s=null,c=0;function u(e,n,o){t.closed||r(e,n,o)}return t.own(()=>{if(c++,null!==s){const t=s;s=null,clearTimeout(t)}}),r=>(a,d,f)=>l(null,null,function*(){if(t.closed)return;const l="played"===a&&f&&i;if("hover"!==a&&!l)return;const m=n.clientWidth*d,p=d*o();if(u(r,"display","flex"),t.closed)return;const h=function(t,e){return t.find(t=>e>=t.start&&e<=t.end)}(e,p);if(!h)return u(r,"display","none");if(m>0&&m<n.clientWidth?function(t,e,o){u(t,"backgroundImage",`url(${e.url})`),u(t,"height",`${e.h}px`),u(t,"width",`${e.w}px`),u(t,"backgroundPosition",`-${e.x}px -${e.y}px`),o<=Number(e.w)/2?u(t,"left",0):o>n.clientWidth-Number(e.w)/2?u(t,"left",n.clientWidth-Number(e.w)+"px"):u(t,"left",o-Number(e.w)/2+"px")}(r,h,m):i||u(r,"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(r,"display","none"))},500);t.closed||e!==c?clearTimeout(n):s=n}})}return function(t){return e=>l(null,null,function*(){const{setStyle:n,isMobile:o,addClass:r}=e.constructor.utils,{$progress:d}=e.template,f=function(t){let e=Boolean(t.isDestroy);const n=new Set;let o;const r=new Promise(t=>{o=t}),i={get closed(){return e},own:t=>(e?l(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,r]),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{i.listen("destroy",i.dispose)}catch(s){throw i.dispose(),s}return i}(e),m={name:"artplayerPluginVttThumbnail"};try{if(f.closed)return m;const p=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()}})}(p,f);if(f.closed)return m;const b=function(t,e=""){"string"!==typeof t&&s(1,"expected text");const n=t.replace(/^\uFEFF/,"").split(/\r\n|\r|\n/);let o=0;function r(){for(;o<n.length&&!n[o].trim();)o++}if(r(),o===n.length)return[];/^WEBVTT(?:[\t ].*)?$/.test(n[o].trim())&&!n[o].includes("--\x3e")||s(o+1,"expected WEBVTT header"),o++;const l=[];for(;o<n.length&&(r(),o!==n.length);){const t=n[o].trim();if(/^NOTE(?:[\t ]|$)/.test(t)||"STYLE"===t||"REGION"===t){for(;o<n.length&&n[o].trim();)o++;continue}const a=o+1;t.includes("--\x3e")||(o++,o!==n.length&&n[o].trim()||s(a,"cue identifier must be followed by timing"));const d=o+1,f=n[o].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/);f||s(d,"invalid cue timing");const m=c(f[1],d),p=c(f[2],d);p<m&&s(d,"cue end precedes its start"),o++,r(),o===n.length&&s(d,"missing sprite image");const h=u(n[o].trim(),e,o+1);l.push(i({start:Math.floor(m),end:Math.floor(p)},h)),o++}return l}(h,p),y=a({lifetime:Line truncated
|
||||
+6
-5
@@ -168,12 +168,12 @@ function createPreview({ lifetime, thumbnails, progress, duration, setStyle, isM
|
||||
style(control, "height", `${cue.h}px`);
|
||||
style(control, "width", `${cue.w}px`);
|
||||
style(control, "backgroundPosition", `-${cue.x}px -${cue.y}px`);
|
||||
if (width <= cue.w / 2)
|
||||
if (width <= Number(cue.w) / 2)
|
||||
style(control, "left", 0);
|
||||
else if (width > progress.clientWidth - cue.w / 2)
|
||||
style(control, "left", `${progress.clientWidth - cue.w}px`);
|
||||
else if (width > progress.clientWidth - Number(cue.w) / 2)
|
||||
style(control, "left", `${progress.clientWidth - Number(cue.w)}px`);
|
||||
else
|
||||
style(control, "left", `${width - cue.w / 2}px`);
|
||||
style(control, "left", `${width - Number(cue.w) / 2}px`);
|
||||
}
|
||||
return (control) => async (type, percentage, event) => {
|
||||
if (lifetime.closed)
|
||||
@@ -234,7 +234,8 @@ async function requestVtt(url = "", lifetime) {
|
||||
}
|
||||
function artplayerPluginVttThumbnail(option) {
|
||||
return async (art) => {
|
||||
const { constructor: { utils: { setStyle, isMobile, addClass } }, template: { $progress } } = art;
|
||||
const { setStyle, isMobile, addClass } = art.constructor.utils;
|
||||
const { $progress } = art.template;
|
||||
const lifetime = createLifetime(art);
|
||||
const result = { name: "artplayerPluginVttThumbnail" };
|
||||
try {
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import type { Thumbnail } from './types'
|
||||
import parseVtt from './parseVtt'
|
||||
|
||||
// Preserve the historical source helper while registration owns its requests.
|
||||
export default async function getVttArray(vttUrl = '') {
|
||||
export default async function getVttArray(vttUrl = ''): Promise<Thumbnail[]> {
|
||||
return parseVtt(await (await fetch(vttUrl)).text(), vttUrl)
|
||||
}
|
||||
+7
-4
@@ -1,13 +1,16 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Option, Result } from './types'
|
||||
import createLifetime from './lifetime'
|
||||
import parseVtt from './parseVtt'
|
||||
import createPreview from './preview'
|
||||
import requestVtt from './request'
|
||||
|
||||
export default function artplayerPluginVttThumbnail(option) {
|
||||
return async (art) => {
|
||||
const { constructor: { utils: { setStyle, isMobile, addClass } }, template: { $progress } } = art
|
||||
export default function artplayerPluginVttThumbnail(option: Option) {
|
||||
return async (art: Artplayer): Promise<Result> => {
|
||||
const { setStyle, isMobile, addClass } = (art.constructor as typeof Artplayer).utils
|
||||
const { $progress } = art.template
|
||||
const lifetime = createLifetime(art)
|
||||
const result = { name: 'artplayerPluginVttThumbnail' }
|
||||
const result: Result = { name: 'artplayerPluginVttThumbnail' }
|
||||
try {
|
||||
if (lifetime.closed)
|
||||
return result
|
||||
+11
-8
@@ -1,11 +1,14 @@
|
||||
export default function createLifetime(art) {
|
||||
import type { Events } from 'artplayer'
|
||||
import type { Cleanup, Lifetime, LifetimeHost, OwnedEvent } from './types'
|
||||
|
||||
export default function createLifetime(art: LifetimeHost): Lifetime {
|
||||
let closed = Boolean(art.isDestroy)
|
||||
const cleanups = new Set()
|
||||
let cancel
|
||||
const cancelled = new Promise((resolve) => {
|
||||
const cleanups = new Set<Cleanup>()
|
||||
let cancel!: Cleanup
|
||||
const cancelled = new Promise<void>((resolve) => {
|
||||
cancel = resolve
|
||||
})
|
||||
const lifetime = {
|
||||
const lifetime: Lifetime = {
|
||||
get closed() { return closed },
|
||||
own(cleanup) {
|
||||
if (closed)
|
||||
@@ -14,13 +17,13 @@ export default function createLifetime(art) {
|
||||
cleanups.add(cleanup)
|
||||
return () => cleanups.delete(cleanup)
|
||||
},
|
||||
listen(name, callback) {
|
||||
listen<Name extends OwnedEvent>(name: Name, callback: (...args: Events[Name]) => unknown) {
|
||||
if (closed)
|
||||
return
|
||||
lifetime.own(() => art.off(name, callback))
|
||||
art.on(name, callback)
|
||||
},
|
||||
wait(value) { return Promise.race([value, cancelled]) },
|
||||
wait<Value>(value: Value | PromiseLike<Value>) { return Promise.race([value, cancelled]) },
|
||||
dispose() {
|
||||
if (closed)
|
||||
return
|
||||
@@ -32,7 +35,7 @@ export default function createLifetime(art) {
|
||||
run(cleanup)
|
||||
},
|
||||
}
|
||||
function run(cleanup) {
|
||||
function run(cleanup: Cleanup) {
|
||||
try {
|
||||
cleanup()
|
||||
}
|
||||
+24
-22
@@ -1,35 +1,37 @@
|
||||
function invalid(line, message) {
|
||||
import type { Rectangle, Thumbnail } from './types'
|
||||
|
||||
function invalid(line: number, message: string): never {
|
||||
throw new TypeError(`Invalid VTT thumbnail at line ${line}: ${message}`)
|
||||
}
|
||||
|
||||
function timestamp(value, line) {
|
||||
function timestamp(value: string, line: number): number {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
function rectangle(text, vttUrl, line) {
|
||||
function rectangle(text: string, vttUrl: string, line: number): Rectangle & { url: string } {
|
||||
const match = text.match(/^(.+)#([xywh]{4})=(.*)$/)
|
||||
if (!match || new Set(match[2]).size !== 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())
|
||||
const values = match[3]!.split(',').map(value => value.trim())
|
||||
if (values.length !== 4)
|
||||
invalid(line, 'expected four rectangle coordinates')
|
||||
const rect = {}
|
||||
const rect = {} as Rectangle
|
||||
for (let index = 0; index < 4; index++) {
|
||||
const key = match[2][index]
|
||||
const value = values[index]
|
||||
const key = match[2]![index] as keyof Rectangle
|
||||
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]
|
||||
let url = match[1]!
|
||||
if (!/^\/|(?:https?|ftp|file):\/\//i.test(url)) {
|
||||
const segments = vttUrl.split('/')
|
||||
segments.pop()
|
||||
@@ -39,55 +41,55 @@ function rectangle(text, vttUrl, line) {
|
||||
return { url, ...rect }
|
||||
}
|
||||
|
||||
export function findThumbnail(thumbnails, second) {
|
||||
export function findThumbnail(thumbnails: readonly Thumbnail[], second: number): Thumbnail | undefined {
|
||||
return thumbnails.find(item => second >= item.start && second <= item.end)
|
||||
}
|
||||
|
||||
export default function parseVtt(text, vttUrl = '') {
|
||||
export default function parseVtt(text: unknown, vttUrl = ''): Thumbnail[] {
|
||||
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())
|
||||
while (index < lines.length && !lines[index]!.trim())
|
||||
index++
|
||||
}
|
||||
skipEmpty()
|
||||
if (index === lines.length)
|
||||
return []
|
||||
if (!/^WEBVTT(?:[\t ].*)?$/.test(lines[index].trim()) || lines[index].includes('-->'))
|
||||
if (!/^WEBVTT(?:[\t ].*)?$/.test(lines[index]!.trim()) || lines[index]!.includes('-->'))
|
||||
invalid(index + 1, 'expected WEBVTT header')
|
||||
index++
|
||||
const thumbnails = []
|
||||
const thumbnails: Thumbnail[] = []
|
||||
while (index < lines.length) {
|
||||
skipEmpty()
|
||||
if (index === lines.length)
|
||||
break
|
||||
const first = lines[index].trim()
|
||||
const first = lines[index]!.trim()
|
||||
if (/^NOTE(?:[\t ]|$)/.test(first) || first === 'STYLE' || first === 'REGION') {
|
||||
while (index < lines.length && lines[index].trim())
|
||||
while (index < lines.length && lines[index]!.trim())
|
||||
index++
|
||||
continue
|
||||
}
|
||||
const identifierLine = index + 1
|
||||
if (!first.includes('-->')) {
|
||||
index++
|
||||
if (index === lines.length || !lines[index].trim())
|
||||
if (index === lines.length || !lines[index]!.trim())
|
||||
invalid(identifierLine, 'cue identifier must be followed by timing')
|
||||
}
|
||||
const timingLine = index + 1
|
||||
const timing = lines[index].trim().match(/^([\d:.]+)[\t ]*-->[\t ]*([\d:.]+)(?:[\t ].*)?$/)
|
||||
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)
|
||||
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)
|
||||
const image = rectangle(lines[index]!.trim(), vttUrl, index + 1)
|
||||
thumbnails.push({ start: Math.floor(start), end: Math.floor(end), ...image })
|
||||
index++
|
||||
}
|
||||
+12
-10
@@ -1,7 +1,9 @@
|
||||
import type { Events } from 'artplayer'
|
||||
import type { PreviewOptions, StyleKey, Thumbnail } from './types'
|
||||
import { findThumbnail } from './parseVtt'
|
||||
|
||||
export default function createPreview({ lifetime, thumbnails, progress, duration, setStyle, isMobile }) {
|
||||
let timer = null
|
||||
export default function createPreview({ lifetime, thumbnails, progress, duration, setStyle, isMobile }: PreviewOptions) {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
let generation = 0
|
||||
lifetime.own(() => {
|
||||
generation++
|
||||
@@ -11,23 +13,23 @@ export default function createPreview({ lifetime, thumbnails, progress, duration
|
||||
clearTimeout(previous)
|
||||
}
|
||||
})
|
||||
function style(control, key, value) {
|
||||
function style(control: HTMLElement, key: StyleKey, value: string | number) {
|
||||
if (!lifetime.closed)
|
||||
setStyle(control, key, value)
|
||||
setStyle<keyof CSSStyleDeclaration>(control, key, value)
|
||||
}
|
||||
function show(control, cue, width) {
|
||||
function show(control: HTMLElement, cue: Thumbnail, width: number) {
|
||||
style(control, 'backgroundImage', `url(${cue.url})`)
|
||||
style(control, 'height', `${cue.h}px`)
|
||||
style(control, 'width', `${cue.w}px`)
|
||||
style(control, 'backgroundPosition', `-${cue.x}px -${cue.y}px`)
|
||||
if (width <= cue.w / 2)
|
||||
if (width <= Number(cue.w) / 2)
|
||||
style(control, 'left', 0)
|
||||
else if (width > progress.clientWidth - cue.w / 2)
|
||||
style(control, 'left', `${progress.clientWidth - cue.w}px`)
|
||||
else if (width > progress.clientWidth - Number(cue.w) / 2)
|
||||
style(control, 'left', `${progress.clientWidth - Number(cue.w)}px`)
|
||||
else
|
||||
style(control, 'left', `${width - cue.w / 2}px`)
|
||||
style(control, 'left', `${width - Number(cue.w) / 2}px`)
|
||||
}
|
||||
return control => async (type, percentage, event) => {
|
||||
return (control: HTMLElement) => async (type: Events['setBar'][0], percentage: number, event?: Event) => {
|
||||
if (lifetime.closed)
|
||||
return
|
||||
const dragging = type === 'played' && event && isMobile
|
||||
+6
-4
@@ -1,4 +1,6 @@
|
||||
export default async function requestVtt(url = '', lifetime) {
|
||||
import type { Lifetime } from './types'
|
||||
|
||||
export default async function requestVtt(url = '', lifetime: Lifetime): Promise<string | void> {
|
||||
if (lifetime.closed)
|
||||
return
|
||||
const controller = typeof AbortController === 'function' ? new AbortController() : null
|
||||
@@ -9,11 +11,11 @@ export default async function requestVtt(url = '', lifetime) {
|
||||
const response = await lifetime.wait(controller ? fetch(url, { signal: controller.signal }) : fetch(url))
|
||||
if (lifetime.closed)
|
||||
return
|
||||
if (response.ok === false)
|
||||
throw new Error(`Failed to fetch VTT thumbnails: HTTP ${response.status}`)
|
||||
if (response!.ok === false)
|
||||
throw new Error(`Failed to fetch VTT thumbnails: HTTP ${response!.status}`)
|
||||
if (lifetime.closed)
|
||||
return
|
||||
return await lifetime.wait(response.text())
|
||||
return await lifetime.wait(response!.text())
|
||||
}
|
||||
finally {
|
||||
release()
|
||||
@@ -0,0 +1,45 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Events, Utils } from 'artplayer'
|
||||
|
||||
export interface Option {
|
||||
vtt?: string
|
||||
style?: Partial<CSSStyleDeclaration>
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
name: 'artplayerPluginVttThumbnail'
|
||||
}
|
||||
|
||||
export interface Rectangle {
|
||||
x: string
|
||||
y: string
|
||||
w: string
|
||||
h: string
|
||||
}
|
||||
|
||||
export interface Thumbnail extends Rectangle {
|
||||
start: number
|
||||
end: number
|
||||
url: string
|
||||
}
|
||||
|
||||
export type Cleanup = () => void
|
||||
export type OwnedEvent = 'destroy' | 'setBar'
|
||||
export interface Lifetime {
|
||||
readonly closed: boolean
|
||||
own: (cleanup: Cleanup) => () => boolean
|
||||
listen: <Name extends OwnedEvent>(name: Name, callback: (...args: Events[Name]) => unknown) => void
|
||||
wait: <Value>(value: Value | PromiseLike<Value>) => Promise<Value | void>
|
||||
dispose: Cleanup
|
||||
}
|
||||
|
||||
export type LifetimeHost = Pick<Artplayer, 'on' | 'off' | 'isDestroy'>
|
||||
export type StyleKey = 'backgroundImage' | 'height' | 'width' | 'backgroundPosition' | 'left' | 'display'
|
||||
export interface PreviewOptions {
|
||||
lifetime: Lifetime
|
||||
thumbnails: readonly Thumbnail[]
|
||||
progress: Pick<HTMLElement, 'clientWidth'>
|
||||
duration: () => number
|
||||
setStyle: Utils['setStyle']
|
||||
isMobile: boolean
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": { "allowJs": false },
|
||||
"include": ["src/**/*.ts", "types/**/*.d.ts", "../../types/assets.d.ts", "../../test/types/vtt-thumbnail-runtime.ts"]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,22 @@
|
||||
# PKG-VTT-THUMB-04 严格运行时类型检查点
|
||||
|
||||
六个 JS 实现迁为 TS,并增加 types.ts,明确 Option/Result、字符串矩形和 Thumbnail、
|
||||
资源生命周期、事件元组与最小预览输入。分包 allowJs=false,继承 strict、
|
||||
noUncheckedIndexedAccess、skipLibCheck=false、types=[],不引入 any 或忽略检查。
|
||||
运行时注册准确返回 Promise<Result>,取消分支为 void;parser 输入用 unknown 并先验证。
|
||||
|
||||
新增由分包 tsconfig 检查的正反例,覆盖 URL/CSS、必传参数、异步结果、关闭状态、
|
||||
事件元组和矩形字段。非空/类型断言仅用于已校验正则/数组边界、四键对象构造、
|
||||
真实 Artplayer constructor 和关闭检查后的原生 Response;包内架构说明具体不变量。
|
||||
数值计算显式 Number 转换,保留正常坐标/进度样式及零值写入。
|
||||
|
||||
公开声明、package exports、版本、依赖保持原样。类型用例明确复现旧声明返回同步
|
||||
对象与实际 Promise 的冲突,没有靠给 Promise 增加 name 属性或类型断言伪造同步结果。
|
||||
04 继续 doing,下一步验证旧编译器/消费形式、异步类型视图、CJS default 兼容、
|
||||
模块声明和编辑器;不能将严格内部类型通过等同于公开声明已完成。
|
||||
|
||||
本次沿用 246 项本包契约/错误/候选测试,并验证正式构建和真实浏览器。
|
||||
准确入口、类型和 CI 结果见 [验证记录](../baselines/vtt-thumbnail-runtime-types.json)。
|
||||
正常构建生成 dist 与 docs 副本;无新依赖。内部源码路径从 .js 迁至 .ts,历史
|
||||
源码深导入仍归 06 分发审查,当前不能声称所有深路径已验收。
|
||||
独立本地检查点提交;回退可恢复前次 JS 实现,不影响历史冻结测试。不推送或发布。
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 93 / doing 12 / blocked 0 / done 117 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 92 / doing 13 / blocked 0 / done 117 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
| PKG-VTT-THUMB-01 | artplayer-plugin-vtt-thumbnail<br>核对包契约与历史用法 | BASE-05 | vtt/style、异步返回、预览控件名与样式契约 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | M | done |
|
||||
| PKG-VTT-THUMB-02 | artplayer-plugin-vtt-thumbnail<br>建立特有行为与错误测试 | PKG-VTT-THUMB-01, ENG-03, ENG-05 | VTT 解析、xywh、相对 URL、时间边界、请求失败和定位 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | M | done |
|
||||
| PKG-VTT-THUMB-03 | artplayer-plugin-vtt-thumbnail<br>整理内部职责与资源 | PKG-VTT-THUMB-02, CORE-09, CORE-19 | 纯解析/区间查找与 DOM、请求/定时器生命周期分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | M | done |
|
||||
| PKG-VTT-THUMB-04 | artplayer-plugin-vtt-thumbnail<br>迁移自有源码和公开类型 | PKG-VTT-THUMB-03, ENG-04, ENG-06, CORE-07 | cue/rect/result 和参数可选性保持兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | todo |
|
||||
| PKG-VTT-THUMB-04 | artplayer-plugin-vtt-thumbnail<br>迁移自有源码和公开类型 | PKG-VTT-THUMB-03, ENG-04, ENG-06, CORE-07 | cue/rect/result 和参数可选性保持兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | doing |
|
||||
| PKG-VTT-THUMB-05 | artplayer-plugin-vtt-thumbnail<br>验证新旧核心和组合 | PKG-VTT-THUMB-04, CORE-22 | 鼠标和移动进度、全屏、切源、chapter 组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | todo |
|
||||
| PKG-VTT-THUMB-06 | artplayer-plugin-vtt-thumbnail<br>验证分发并同步文档 | PKG-VTT-THUMB-05, ENG-07 | vtt.thumbnail.js、VTT/图片资源解析和产物验证 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | M | todo |
|
||||
|
||||
@@ -500,6 +500,7 @@
|
||||
- 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) [记录](changes/2026-09-13-PKG-VTT-THUMB-03-parser.md) [记录](baselines/vtt-thumbnail-parser.json)
|
||||
- PKG-VTT-THUMB-04: [记录](changes/2026-09-13-PKG-VTT-THUMB-04-runtime-types.md) [记录](baselines/vtt-thumbnail-runtime-types.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)
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-VTT-THUMB-04 严格运行时 TS 检查点(doing)
|
||||
|
||||
六个 JS 实现迁为 TS,另有明确数据/资源类型模块,分包 allowJs=false。TS 5.9.3/5.1.6
|
||||
严格检查及正反例通过,移除 expect-error 后各复现 8 个预期类型错误。内部注册准确为
|
||||
Promise<Result>,公开旧同步声明保持原样,旧消费者/类型视图和导出兼容仍待 04。
|
||||
源码/main/legacy 的解析与生命周期各 47 通过,原生三入口各 18 通过,共 54 项;
|
||||
实际 ESM 和完整 CI 2064 项通过,全仓严格生产 TS 增至 346 文件。包内架构和构建同步。
|
||||
见[变更](changes/2026-09-13-PKG-VTT-THUMB-04-runtime-types.md)和
|
||||
[验证](baselines/vtt-thumbnail-runtime-types.json)。222 项:117 done、13 doing、92 todo。
|
||||
本次为独立本地检查点提交,不推送、不发布。
|
||||
|
||||
|
||||
## PKG-VTT-THUMB-03 内部模块与解析修复完成
|
||||
|
||||
在资源检查点上完成纯解析/首个区间查找,支持 cue ID、注释/元数据块和 timing settings,
|
||||
|
||||
+3
-1
@@ -4200,7 +4200,9 @@
|
||||
"evidence": [
|
||||
"refactor/baselines/vtt-thumbnail-release.json",
|
||||
"refactor/baselines/vtt-thumbnail-contract.md",
|
||||
"refactor/scripts/vtt-thumbnail-contract.test.mjs"
|
||||
"refactor/scripts/vtt-thumbnail-contract.test.mjs",
|
||||
"test/types/vtt-thumbnail-runtime.ts",
|
||||
"refactor/baselines/vtt-thumbnail-runtime-types.json"
|
||||
],
|
||||
"compatibleResolution": "Preserve valid consumers and actual async runtime, with explicit decisions for irreconcilable inference changes.",
|
||||
"closureCriteria": "Isolated old/new compiler and installed consumers verify options, styles, callback and result forms."
|
||||
|
||||
+5
-2
@@ -1921,11 +1921,14 @@
|
||||
"ENG-06",
|
||||
"CORE-07"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "doing",
|
||||
"risk": "M",
|
||||
"deliverable": "cue/rect/result 和参数可选性保持兼容",
|
||||
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-13-PKG-VTT-THUMB-04-runtime-types.md",
|
||||
"baselines/vtt-thumbnail-runtime-types.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-VTT-THUMB-05",
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Option, Result, Thumbnail } from '../../packages/artplayer-plugin-vtt-thumbnail/src/types'
|
||||
import factory from '../../packages/artplayer-plugin-vtt-thumbnail/src/index'
|
||||
import createLifetime from '../../packages/artplayer-plugin-vtt-thumbnail/src/lifetime'
|
||||
import parseVtt, { findThumbnail } from '../../packages/artplayer-plugin-vtt-thumbnail/src/parseVtt'
|
||||
import requestVtt from '../../packages/artplayer-plugin-vtt-thumbnail/src/request'
|
||||
import legacyFactory from '../../packages/artplayer-plugin-vtt-thumbnail/types/artplayer-plugin-vtt-thumbnail'
|
||||
|
||||
declare const art: Artplayer
|
||||
const option: Option = { vtt: '/cues.vtt', style: { opacity: '0.8', width: '80px' } }
|
||||
const registration = factory(option)
|
||||
const result: Promise<Result> = registration(art)
|
||||
result.then(value => value.name satisfies 'artplayerPluginVttThumbnail')
|
||||
// @ts-expect-error Registration is asynchronous, without a synchronous name property.
|
||||
void result.name
|
||||
// @ts-expect-error Omitted options remain invalid in typed calls.
|
||||
factory()
|
||||
// @ts-expect-error VTT URL is a string.
|
||||
factory({ vtt: 12 })
|
||||
// @ts-expect-error CSS width retains the existing CSSStyleDeclaration input type.
|
||||
factory({ style: { width: 12 } })
|
||||
|
||||
const lifetime = createLifetime(art)
|
||||
lifetime.listen('setBar', (_type, percentage, event) => {
|
||||
percentage.toFixed()
|
||||
event?.preventDefault()
|
||||
})
|
||||
// @ts-expect-error The lifetime only owns the events this plugin actually uses.
|
||||
lifetime.listen('not-an-event', () => {})
|
||||
const value: Promise<number | void> = lifetime.wait(Promise.resolve(3))
|
||||
const text: Promise<string | void> = requestVtt('/cues.vtt', lifetime)
|
||||
void value
|
||||
void text
|
||||
// @ts-expect-error Closed is a read-only state.
|
||||
lifetime.closed = false
|
||||
|
||||
const cues: Thumbnail[] = parseVtt('WEBVTT')
|
||||
const selected: Thumbnail | undefined = findThumbnail(cues, 1)
|
||||
selected?.x.toUpperCase()
|
||||
// @ts-expect-error Rectangle strings preserve historical formatting.
|
||||
const width: number = cues[0]!.w
|
||||
void width
|
||||
|
||||
const legacyRegistration = legacyFactory(option)
|
||||
legacyRegistration(art).name satisfies 'artplayerPluginVttThumbnail'
|
||||
// @ts-expect-error Published declarations are falsely synchronous; do not hide the conflict.
|
||||
const legacyReplacement: typeof legacyRegistration = registration
|
||||
void legacyReplacement
|
||||
Reference in new issue
Block a user