refactor(vtt-thumbnail): [PKG-VTT-THUMB-04] preserve legacy types and expose async runtime entry

This commit is contained in:
Harvey Zhao committed 2026-09-13 09:30:57 +08:00
1 parent 7f2906ae52
commit db358a3ae4
33 files changed
+3864 -98

No files matched your search

@@ -2,4 +2,5 @@ node_modules
.DS_Store
yarn.lock
package-lock.json
src
src
tsconfig.json
@@ -2,8 +2,10 @@
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 public declaration/consumer migration. The seven internal TS files are strict. Do not infer the return shape from those declarations.
root/legacy declarations retain the historical synchronous result for existing type extraction
and replacement factories. The optional `/runtime` entry describes the real Promise without
consumer assertions. Both entries load the same binary and function. The seven internal TS
files are strict. Never infer synchronous runtime behavior from the compatibility declarations.
## Modules and ownership
@@ -34,14 +36,16 @@ rolled back if initialization throws, preserving the original rejection.
## Runtime types
`src/types.ts` defines options, result, rectangle/cue data, owned events, cleanup and
`types/artplayer-plugin-vtt-thumbnail.d.ts` owns Option and Result; `src/types.ts` reexports
them and defines 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
check, plus the recursive `.default` factory alias created by Object.assign. The response
assertion 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.
@@ -49,8 +53,22 @@ 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.
is synchronous in declarations. `test/types/vtt-thumbnail-public.ts` separately checks exact
legacy parameter/result/factory extraction, replacement factories and accurate async calls
through `/runtime`, including negative cases. The source factory explicitly returns Promise;
its final assertion only describes the self-reference that Object.assign creates.
Root and legacy use paired `.d.mts`/`.d.cts` default declarations. `/runtime` uses an ESM
default and a CommonJS export assignment, supporting direct and `.default` require calls.
Classic resolution uses `.d.ts` and typesVersions, including TypeScript 4.3. The generated
editor global retains the legacy callable type; its RuntimeFactory namespace member is only
a type, not an additional runtime global. Do not hand-edit generated editor declarations.
The installed-consumer command packs both core and plugin, installs outside the workspace,
verifies member hashes, and repeats an offline frozen install. It tests five actual old npm
declarations with classic default-import consumers on TS 4.3/5.9 and seven candidate modes.
Historical NodeNext and raw require type replacement forms still need investigation in 04;
these checks do not prove all historical compiler/module combinations.
## Compatibility and intentional fixes
@@ -78,9 +96,11 @@ invalid CSS or partially mounted controls. Each cue has one image/rectangle payl
multiline subtitle text, timestamp-map offsets and full caption semantics are outside this
plugin. Empty or comment-only input remains a valid empty preview.
The old 1.0.x export objects, earlier `thumbnails` control name and incorrect declarations
are separately frozen and remain compatibility work for tasks 04-06. Current runtime work
does not claim those consumer migrations or the full package refactor are finished.
The exported callable has a writable self `.default` alias, restoring the usable 1.0.x
CommonJS default access while retaining direct calls. It does not add a name property to the
registration Promise. Earlier `thumbnails` control names and remaining historical declaration
forms are separately frozen and remain compatibility work for tasks 04-06. The full package
refactor is not yet finished.
## Verification and future changes
@@ -88,6 +108,8 @@ Use Yarn 1.22.22 and the repository's pinned Node version:
```sh
yarn test:vtt-thumbnail
yarn test:vtt-thumbnail-types-package
yarn build:ts artplayer-plugin-vtt-thumbnail
yarn test:browser test/browser/vtt-thumbnail-lifecycle.spec.js
yarn build artplayer-plugin-vtt-thumbnail
```
@@ -6,6 +6,30 @@ Vtt thumbnail plugin for ArtPlayer
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-vtt-thumbnail/index.js&example=vtt.thumbnail)
## TypeScript usage
The registration function is asynchronous. The optional runtime entry provides its accurate
Promise type and uses the same implementation as the default entry:
```ts
import Artplayer from 'artplayer'
import vttThumbnail from 'artplayer-plugin-vtt-thumbnail/runtime'
const art = new Artplayer({ container: '#player', url: '/video.mp4' })
const result = await vttThumbnail({ vtt: '/thumbnails/index.vtt' })(art)
console.log(result.name)
```
You can also pass `vttThumbnail({ vtt: '/thumbnails/index.vtt' })` in the player's `plugins`
array. Existing default and `/legacy` imports remain available. Their declarations preserve
the historical synchronous result type for compatibility, although registration has always
returned a Promise at runtime. Reading `result.name` requires awaiting registration; changing
to `/runtime` opts into that accurate type without changing runtime behavior. The option
object remains required, and its `vtt` and `style` fields remain optional.
See [ARCHITECTURE.md](ARCHITECTURE.md) for module ownership, testing commands and remaining
compatibility validation.
## License
MIT © Harvey Zhao
@@ -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<=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}}}});
!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}}}function o(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}}}return Object.assign(o,{default:o})});
@@ -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,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
!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}})}function d(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:f,thuLine truncated
@@ -77,9 +77,9 @@ function rectangle(text, vttUrl, line) {
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];
for (let index2 = 0; index2 < 4; index2++) {
const key = match[2][index2];
const value = values[index2];
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)
@@ -102,49 +102,49 @@ 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;
let index2 = 0;
function skipEmpty() {
while (index < lines.length && !lines[index].trim())
index++;
while (index2 < lines.length && !lines[index2].trim())
index2++;
}
skipEmpty();
if (index === lines.length)
if (index2 === lines.length)
return [];
if (!/^WEBVTT(?:[\t ].*)?$/.test(lines[index].trim()) || lines[index].includes("-->"))
invalid(index + 1, "expected WEBVTT header");
index++;
if (!/^WEBVTT(?:[\t ].*)?$/.test(lines[index2].trim()) || lines[index2].includes("-->"))
invalid(index2 + 1, "expected WEBVTT header");
index2++;
const thumbnails = [];
while (index < lines.length) {
while (index2 < lines.length) {
skipEmpty();
if (index === lines.length)
if (index2 === lines.length)
break;
const first = lines[index].trim();
const first = lines[index2].trim();
if (/^NOTE(?:[\t ]|$)/.test(first) || first === "STYLE" || first === "REGION") {
while (index < lines.length && lines[index].trim())
index++;
while (index2 < lines.length && lines[index2].trim())
index2++;
continue;
}
const identifierLine = index + 1;
const identifierLine = index2 + 1;
if (!first.includes("-->")) {
index++;
if (index === lines.length || !lines[index].trim())
index2++;
if (index2 === lines.length || !lines[index2].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 timingLine = index2 + 1;
const timing = lines[index2].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++;
index2++;
skipEmpty();
if (index === lines.length)
if (index2 === lines.length)
invalid(timingLine, "missing sprite image");
const image = rectangle(lines[index].trim(), vttUrl, index + 1);
const image = rectangle(lines[index2].trim(), vttUrl, index2 + 1);
thumbnails.push({ start: Math.floor(start), end: Math.floor(end), ...image });
index++;
index2++;
}
return thumbnails;
}
@@ -273,6 +273,7 @@ function artplayerPluginVttThumbnail(option) {
}
};
}
const index = Object.assign(artplayerPluginVttThumbnail, { default: artplayerPluginVttThumbnail });
export {
artplayerPluginVttThumbnail as default
index as default
};
@@ -19,19 +19,49 @@
],
"exports": {
".": {
"types": "./types/artplayer-plugin-vtt-thumbnail.d.ts",
"import": "./dist/artplayer-plugin-vtt-thumbnail.mjs",
"require": "./dist/artplayer-plugin-vtt-thumbnail.js"
"import": {
"types": "./types/artplayer-plugin-vtt-thumbnail.d.mts",
"default": "./dist/artplayer-plugin-vtt-thumbnail.mjs"
},
"require": {
"types": "./types/artplayer-plugin-vtt-thumbnail.d.cts",
"default": "./dist/artplayer-plugin-vtt-thumbnail.js"
}
},
"./legacy": {
"types": "./types/artplayer-plugin-vtt-thumbnail.d.ts",
"import": "./dist/artplayer-plugin-vtt-thumbnail.legacy.js",
"require": "./dist/artplayer-plugin-vtt-thumbnail.legacy.js"
"import": {
"types": "./types/artplayer-plugin-vtt-thumbnail.d.mts",
"default": "./dist/artplayer-plugin-vtt-thumbnail.legacy.js"
},
"require": {
"types": "./types/artplayer-plugin-vtt-thumbnail.d.cts",
"default": "./dist/artplayer-plugin-vtt-thumbnail.legacy.js"
}
},
"./runtime": {
"import": {
"types": "./types/runtime.d.mts",
"default": "./dist/artplayer-plugin-vtt-thumbnail.mjs"
},
"require": {
"types": "./types/runtime.d.cts",
"default": "./dist/artplayer-plugin-vtt-thumbnail.js"
}
}
},
"main": "./dist/artplayer-plugin-vtt-thumbnail.js",
"module": "./dist/artplayer-plugin-vtt-thumbnail.mjs",
"types": "./types/artplayer-plugin-vtt-thumbnail.d.ts",
"legacy": "./dist/artplayer-plugin-vtt-thumbnail.legacy.js",
"browserslist": "last 1 Chrome version"
"browserslist": "last 1 Chrome version",
"typesVersions": {
"*": {
"legacy": [
"types/artplayer-plugin-vtt-thumbnail.d.ts"
],
"runtime": [
"types/runtime.d.ts"
]
}
}
}
@@ -1,11 +1,12 @@
import type Artplayer from 'artplayer'
import type { RuntimeFactory } from '../types/artplayer-plugin-vtt-thumbnail'
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: Option) {
function artplayerPluginVttThumbnail(option: Option) {
return async (art: Artplayer): Promise<Result> => {
const { setStyle, isMobile, addClass } = (art.constructor as typeof Artplayer).utils
const { $progress } = art.template
@@ -47,3 +48,6 @@ export default function artplayerPluginVttThumbnail(option: Option) {
}
}
}
// Object.assign adds the alias to the same callable referenced by that alias.
export default Object.assign(artplayerPluginVttThumbnail, { default: artplayerPluginVttThumbnail }) as RuntimeFactory
@@ -1,14 +1,7 @@
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 type { Option, Result } from '../types/artplayer-plugin-vtt-thumbnail'
export interface Rectangle {
x: string
@@ -1,5 +1,14 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "allowJs": false },
"include": ["src/**/*.ts", "types/**/*.d.ts", "../../types/assets.d.ts", "../../test/types/vtt-thumbnail-runtime.ts"]
"compilerOptions": {
"allowJs": false
},
"include": [
"src/**/*.ts",
"types/**/*.d.ts",
"../../types/assets.d.ts",
"../../test/types/vtt-thumbnail-runtime.ts",
"types/**/*.d.mts",
"types/**/*.d.cts"
]
}
@@ -0,0 +1,5 @@
import type { Factory } from './artplayer-plugin-vtt-thumbnail.js'
declare const artplayerPluginVttThumbnail: Factory
export default artplayerPluginVttThumbnail
export type { Factory, Option, Result, RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
@@ -0,0 +1,5 @@
import type { Factory } from './artplayer-plugin-vtt-thumbnail.js'
declare const artplayerPluginVttThumbnail: Factory
export default artplayerPluginVttThumbnail
export type { Factory, Option, Result, RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
@@ -1,9 +1,23 @@
import type Artplayer from 'artplayer'
declare const artplayerPluginVttThumbnail: (option: { vtt?: string, style?: Partial<CSSStyleDeclaration> }) => (
art: Artplayer,
) => {
export interface Option {
vtt?: string
style?: Partial<CSSStyleDeclaration>
}
export interface Result {
name: 'artplayerPluginVttThumbnail'
}
/** Historical factory type. Actual registration is asynchronous. */
export type Factory = (option: Option) => (art: Artplayer) => Result
/** Accurate runtime view available without casts through the /runtime entry. */
export interface RuntimeFactory {
(option: Option): (art: Artplayer) => Promise<Result>
default: RuntimeFactory
}
/** Preserve historical extraction and replacement-function compatibility. */
declare function artplayerPluginVttThumbnail(option: Option): (art: Artplayer) => Result
export default artplayerPluginVttThumbnail
@@ -0,0 +1,4 @@
import type { RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
declare const artplayerPluginVttThumbnail: RuntimeFactory
export = artplayerPluginVttThumbnail
@@ -0,0 +1,5 @@
import type { RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
declare const artplayerPluginVttThumbnail: RuntimeFactory
export default artplayerPluginVttThumbnail
export type { Option, Result, RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
@@ -0,0 +1,5 @@
import type { RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
declare const artplayerPluginVttThumbnail: RuntimeFactory
export default artplayerPluginVttThumbnail
export type { Option, Result, RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'