mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 03:46:16 -08:00
refactor(vtt-thumbnail): [PKG-VTT-THUMB-03] validate cues and separate pure lookup
This commit is contained in:
1 parent
524ddf784f
commit
80588a3046
19 files changed
+3049
-169
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){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&&h<n.clientWidth?function(t,e,o){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`),o<=e.w/2?c(t,"left",0):o>n.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<o.length;i+=2){const e=o[i],r=o[i+1];if(!r.trim())continue;const s=/((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?) ?--> ?((?:\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;t<m.length;t++)p[m[t]]=y[t];l.push(p)}return l}(d,a),h=e({lifetime:c,thumbnails:f,progress:s,duration:()=>o.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&&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}}}});
|
||||
@@ -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&&h<o.clientWidth?function(t,e,n){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`),n<=e.w/2?c(t,"left",0):n>o.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<o.length;i+=2){const t=o[i],r=o[i+1];if(!r.trim())continue;const s=/((?:\d{2}:)?(?:\d{2}:)?\d{2}(?:.\d{3})?) ?--> ?((?:\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;e<m.length;e++)p[m[e]]=y[e];l.push(p)}return l}(f,d),p=n({lifetime:c,thumbnails:h,progress:u,duration:()=>l.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&&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
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user