refactor(multiple-subtitles): [PKG-MULTI-SUB-04] expose compatible runtime type entry

This commit is contained in:
Harvey Zhao committed 2026-09-13 12:14:41 +08:00
1 parent 7f226ff822
commit d44e81ce22
34 files changed
+3346 -60

No files matched your search

+37 -8
View File
@@ -1,16 +1,45 @@
declare const artplayerPluginMultipleSubtitles: (option: {
subtitles: {
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginMultipleSubtitlesDefinitions {
export interface TrackOption {
url?: string
name?: string
type?: 'vtt' | 'srt' | 'ass'
encoding?: string
onParser?: (...args: object[]) => object
}[]
}) => (art: Artplayer) => {
name: 'multipleSubtitles'
}
export interface Option {
subtitles: TrackOption[]
}
export interface RuntimeOption {
subtitles?: TrackOption[]
}
export interface LegacyResult {
name: 'multipleSubtitles'
}
export interface Result extends LegacyResult {
tracks: (names?: string[]) => void
reset: () => void
}
/** Historical synchronous extraction; actual registration is asynchronous. */
export type Factory = (option: Option) => (art: Artplayer) => LegacyResult
/** Accurate runtime view available through the /runtime entry. */
export interface RuntimeFactory {
(option: RuntimeOption): (art: Artplayer) => Promise<Result>
default: RuntimeFactory
}
/** Preserve existing parameter extraction and replacement-function compatibility. */
export function artplayerPluginMultipleSubtitles(option: Option): (art: Artplayer) => LegacyResult
}
declare const artplayerPluginMultipleSubtitles: typeof artplayerPluginMultipleSubtitlesDefinitions.artplayerPluginMultipleSubtitles
declare namespace artplayerPluginMultipleSubtitles {
export type TrackOption = artplayerPluginMultipleSubtitlesDefinitions.TrackOption
export type Option = artplayerPluginMultipleSubtitlesDefinitions.Option
export type RuntimeOption = artplayerPluginMultipleSubtitlesDefinitions.RuntimeOption
export type LegacyResult = artplayerPluginMultipleSubtitlesDefinitions.LegacyResult
export type Result = artplayerPluginMultipleSubtitlesDefinitions.Result
export type Factory = artplayerPluginMultipleSubtitlesDefinitions.Factory
export type RuntimeFactory = artplayerPluginMultipleSubtitlesDefinitions.RuntimeFactory
}
export default artplayerPluginMultipleSubtitles
export = artplayerPluginMultipleSubtitles
export as namespace artplayerPluginMultipleSubtitles;
@@ -55,4 +55,4 @@
*
* d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
*/
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},t=function(t){t||(t={"&amp":"&","&lt":"<","&gt":">","&lrm":"\u200e","&rlm":"\u200f","&nbsp":"\xa0"}),this.entities=t,this.parse=function(s,a){s=s.replace(/\0/g,"\ufffd");var r=Date.now(),o=0,l=s.split(/\r\n|\r|\n/),c=!1,f=[],u=[],d=[];function p(e,t){d.push({message:e,line:o+1,col:t})}var m=l[o],h=m.length,g="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(h<b||m.indexOf(g)!==0+v||h>b&&" "!==m[b]&&"\t"!==m[b])&&p('No valid signature. (File needs to start with "WEBVTT".)'),o++;""!=l[o]&&void 0!=l[o];){if(p("No blank line after the signature."),-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}for(;void 0!=l[o];){for(var y;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;y=Object.assign({},e,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var T=!0;if(-1==l[o].indexOf("--\x3e")){if(y.id=l[o],/^NOTE($|[ \t])/.test(y.id)){for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&p("Cannot have timestamp in a comment."),o++;continue}if(/^STYLE($|[ \t])/.test(y.id)){var x=[],w=!1;for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&(p("Cannot have timestamp in a style block."),w=!0),x.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(x.join("\n"));continue}if(""==l[++o]||void 0==l[o]){p("Cue identifier cannot be standalone.");continue}if(-1==l[o].indexOf("--\x3e")){T=!1,p("Cue identifier needs to be followed by timestamp.");continue}}c=!1;var A=new n(l[o],p),O=0;if(u.length>0&&(O=u[u.length-1].startTime),!T||A.parse(y,O)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=y.text&&(y.text+="\n"),y.text+=l[o],o++}var N=new i(y.text,p,a,t);y.tree=N.parse(y.startTime,y.endTime),u.push(y)}else for(y=null,o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}}return u.sort(function(e,t){return e.startTime<t.startTime?-1:e.startTime>t.startTime?1:e.endTime>t.endTime?-1:e.endTime<t.endTime?1:0}),{cues:u,errors:d,time:Date.now()-r,styles:f}}},n=function(e,t){var n=/[\u0020\t\f]/,i=/[^\u0020\t\f]/,s=(e=e,0),a=function(e){t(e,s+1)};function r(t){for(;void 0!=e[s]&&t.test(e[s]);)s++}function o(t){for(var n="";void 0!=e[s]&&t.test(e[s]);)n+=e[s],s++;return n}function l(){var t,n,i,r,l="minutes";if(void 0!=e[s])if(/\d/.test(e[s]))if(((t=o(/\d/)).length>2||parseInt(t,10)>59)&&(l="hours"),":"==e[s])if(s++,2==(n=o(/\d/)).length){if("hours"==l||":"==e[s]){if(":"!=e[s])return void a("No seconds found or minutes is greater than 59.");if(s++,2!=(i=o(/\d/)).length)return void a("Must be exactly two digits.")}else{if(2!=t.length)return void a("Must be exactly two digits.");i=n,n=t,t="0"}if("."==e[s])if(s++,3==(r=o(/\d/)).length)if(parseInt(n,10)>59)a("You cannot have more than 59 minutes.");else{if(!(parseInt(i,10)>59))return 60*parseInt(t,10)*60+60*parseInt(n,10)+parseInt(i,10)+parseInt(r,10)/1e3;a("You cannot have more than 59 seconds.")}else a("Milliseconds must be given in three digits.");else a('No decimal separator (".") found.')}else a("Must be exactly two digits.");else a("No time unit separator found.");else a("Timestamp must start with a character in the range 0-9.");else a("No timestamp found.")}this.parse=function(t,o){if(r(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime<o&&a("Start timestamp is not greater than or equal to start timestamp of previous cue."),i.test(e[s])&&a("Timestamp not separated from '--\x3e' by whitespace."),r(n),"-"==e[s])if(s++,"-"==e[s])if(s++,">"==e[s]){if(s++,i.test(e[s])&&a("'--\x3e' not separated from timestamp by whitespace."),r(n),t.endTime=l(),void 0!=t.endTime)return t.endTime<=t.startTime&&a("End timestamp is not greater than start timestamp."),i.test(e[s]),r(n),function(e,t){for(var i=e.split(n),s=[],r=0;r<i.length;r++)if(""!=i[r]){var o=i[r].indexOf(":"),l=i[r].slice(0,o),c=i[r].slice(o+1);if(-1!=s.indexOf(l)&&a("Duplicate setting."),s.push(l),""==c)return void a("No value for setting defined.");if("vertical"==l){if("rl"!=c&&"lr"!=c){a("Writing direction can only be set to 'rl' or 'rl'.");continue}t.direction=c}else if("line"==l){if(/,/.test(c)){var f=c.split(",");c=f[0];var u=f[1]}if(!/^[-\d](\d*)(\.\d+)?%?$/.test(c)){a("Line position takes a number or percentage.");continue}if(-1!=c.indexOf("-",1)){a("Line position can only have '-' at the start.");continue}if(-1!=c.indexOf("%")&&c.indexOf("%")!=c.Line truncated
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},t=function(t){t||(t={"&amp":"&","&lt":"<","&gt":">","&lrm":"\u200e","&rlm":"\u200f","&nbsp":"\xa0"}),this.entities=t,this.parse=function(s,a){s=s.replace(/\0/g,"\ufffd");var r=Date.now(),o=0,l=s.split(/\r\n|\r|\n/),c=!1,f=[],u=[],d=[];function p(e,t){d.push({message:e,line:o+1,col:t})}var m=l[o],h=m.length,g="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(h<b||m.indexOf(g)!==0+v||h>b&&" "!==m[b]&&"\t"!==m[b])&&p('No valid signature. (File needs to start with "WEBVTT".)'),o++;""!=l[o]&&void 0!=l[o];){if(p("No blank line after the signature."),-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}for(;void 0!=l[o];){for(var y;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;y=Object.assign({},e,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var T=!0;if(-1==l[o].indexOf("--\x3e")){if(y.id=l[o],/^NOTE($|[ \t])/.test(y.id)){for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&p("Cannot have timestamp in a comment."),o++;continue}if(/^STYLE($|[ \t])/.test(y.id)){var x=[],w=!1;for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&(p("Cannot have timestamp in a style block."),w=!0),x.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(x.join("\n"));continue}if(""==l[++o]||void 0==l[o]){p("Cue identifier cannot be standalone.");continue}if(-1==l[o].indexOf("--\x3e")){T=!1,p("Cue identifier needs to be followed by timestamp.");continue}}c=!1;var A=new n(l[o],p),O=0;if(u.length>0&&(O=u[u.length-1].startTime),!T||A.parse(y,O)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=y.text&&(y.text+="\n"),y.text+=l[o],o++}var N=new i(y.text,p,a,t);y.tree=N.parse(y.startTime,y.endTime),u.push(y)}else for(y=null,o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}}return u.sort(function(e,t){return e.startTime<t.startTime?-1:e.startTime>t.startTime?1:e.endTime>t.endTime?-1:e.endTime<t.endTime?1:0}),{cues:u,errors:d,time:Date.now()-r,styles:f}}},n=function(e,t){var n=/[\u0020\t\f]/,i=/[^\u0020\t\f]/,s=(e=e,0),a=function(e){t(e,s+1)};function r(t){for(;void 0!=e[s]&&t.test(e[s]);)s++}function o(t){for(var n="";void 0!=e[s]&&t.test(e[s]);)n+=e[s],s++;return n}function l(){var t,n,i,r,l="minutes";if(void 0!=e[s])if(/\d/.test(e[s]))if(((t=o(/\d/)).length>2||parseInt(t,10)>59)&&(l="hours"),":"==e[s])if(s++,2==(n=o(/\d/)).length){if("hours"==l||":"==e[s]){if(":"!=e[s])return void a("No seconds found or minutes is greater than 59.");if(s++,2!=(i=o(/\d/)).length)return void a("Must be exactly two digits.")}else{if(2!=t.length)return void a("Must be exactly two digits.");i=n,n=t,t="0"}if("."==e[s])if(s++,3==(r=o(/\d/)).length)if(parseInt(n,10)>59)a("You cannot have more than 59 minutes.");else{if(!(parseInt(i,10)>59))return 60*parseInt(t,10)*60+60*parseInt(n,10)+parseInt(i,10)+parseInt(r,10)/1e3;a("You cannot have more than 59 seconds.")}else a("Milliseconds must be given in three digits.");else a('No decimal separator (".") found.')}else a("Must be exactly two digits.");else a("No time unit separator found.");else a("Timestamp must start with a character in the range 0-9.");else a("No timestamp found.")}this.parse=function(t,o){if(r(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime<o&&a("Start timestamp is not greater than or equal to start timestamp of previous cue."),i.test(e[s])&&a("Timestamp not separated from '--\x3e' by whitespace."),r(n),"-"==e[s])if(s++,"-"==e[s])if(s++,">"==e[s]){if(s++,i.test(e[s])&&a("'--\x3e' not separated from timestamp by whitespace."),r(n),t.endTime=l(),void 0!=t.endTime)return t.endTime<=t.startTime&&a("End timestamp is not greater than start timestamp."),i.test(e[s]),r(n),function(e,t){for(var i=e.split(n),s=[],r=0;r<i.length;r++)if(""!=i[r]){var o=i[r].indexOf(":"),l=i[r].slice(0,o),c=i[r].slice(o+1);if(-1!=s.indexOf(l)&&a("Duplicate setting."),s.push(l),""==c)return void a("No value for setting defined.");if("vertical"==l){if("rl"!=c&&"lr"!=c){a("Writing direction can only be set to 'rl' or 'rl'.");continue}t.direction=c}else if("line"==l){if(/,/.test(c)){var f=c.split(",");c=f[0];var u=f[1]}if(!/^[-\d](\d*)(\.\d+)?%?$/.test(c)){a("Line position takes a number or percentage.");continue}if(-1!=c.indexOf("-",1)){a("Line position can only have '-' at the start.");continue}if(-1!=c.indexOf("%")&&c.indexOf("%")!=c.Line truncated
@@ -55,4 +55,4 @@
*
* d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
*/
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.defineProperties,n=Object.getOwnPropertyDescriptors,i=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,a=(t,n,i)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[n]=i,o=(e,t)=>{for(var n in t||(t={}))r.call(t,n)&&a(e,n,t[n]);if(i)for(var n of i(t))s.call(t,n)&&a(e,n,t[n]);return e},l=(e,i)=>t(e,n(i)),c=(e,t,n)=>new Promise((i,r)=>{var s=e=>{try{o(n.next(e))}catch(t){r(t)}},a=e=>{try{o(n.throw(e))}catch(t){r(t)}},o=e=>e.done?i(e.value):Promise.resolve(e.value).then(s,a);o((n=n.apply(e,t)).next())}),f={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},u=function(e){e||(e={"&amp":"&","&lt":"<","&gt":">","&lrm":"\u200e","&rlm":"\u200f","&nbsp":"\xa0"}),this.entities=e,this.parse=function(t,n){t=t.replace(/\0/g,"\ufffd");var i=Date.now(),r=0,s=t.split(/\r\n|\r|\n/),a=!1,o=[],l=[],c=[];function u(e,t){c.push({message:e,line:r+1,col:t})}var m=s[r],h=m.length,g="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(h<b||m.indexOf(g)!==0+v||h>b&&" "!==m[b]&&"\t"!==m[b])&&u('No valid signature. (File needs to start with "WEBVTT".)'),r++;""!=s[r]&&void 0!=s[r];){if(u("No blank line after the signature."),-1!=s[r].indexOf("--\x3e")){a=!0;break}r++}for(;void 0!=s[r];){for(var y;!a&&""==s[r];)r++;if(!a&&void 0==s[r])break;y=Object.assign({},f,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var x=!0;if(-1==s[r].indexOf("--\x3e")){if(y.id=s[r],/^NOTE($|[ \t])/.test(y.id)){for(r++;""!=s[r]&&void 0!=s[r];)-1!=s[r].indexOf("--\x3e")&&u("Cannot have timestamp in a comment."),r++;continue}if(/^STYLE($|[ \t])/.test(y.id)){var T=[],w=!1;for(r++;""!=s[r]&&void 0!=s[r];)-1!=s[r].indexOf("--\x3e")&&(u("Cannot have timestamp in a style block."),w=!0),T.push(s[r]),r++;if(l.length){u("Style blocks cannot appear after the first cue.");continue}w||o.push(T.join("\n"));continue}if(""==s[++r]||void 0==s[r]){u("Cue identifier cannot be standalone.");continue}if(-1==s[r].indexOf("--\x3e")){x=!1,u("Cue identifier needs to be followed by timestamp.");continue}}a=!1;var O=new d(s[r],u),P=0;if(l.length>0&&(P=l[l.length-1].startTime),!x||O.parse(y,P)){for(r++;""!=s[r]&&void 0!=s[r];){if(-1!=s[r].indexOf("--\x3e")){u("Blank line missing before cue."),a=!0;break}""!=y.text&&(y.text+="\n"),y.text+=s[r],r++}var A=new p(y.text,u,n,e);y.tree=A.parse(y.startTime,y.endTime),l.push(y)}else for(y=null,r++;""!=s[r]&&void 0!=s[r];){if(-1!=s[r].indexOf("--\x3e")){a=!0;break}r++}}return l.sort(function(e,t){return e.startTime<t.startTime?-1:e.startTime>t.startTime?1:e.endTime>t.endTime?-1:e.endTime<t.endTime?1:0}),{cues:l,errors:c,time:Date.now()-i,styles:o}}},d=function(e,t){var n=/[\u0020\t\f]/,i=/[^\u0020\t\f]/,r=(e=e,0),s=function(e){t(e,r+1)};function a(t){for(;void 0!=e[r]&&t.test(e[r]);)r++}function o(t){for(var n="";void 0!=e[r]&&t.test(e[r]);)n+=e[r],r++;return n}function l(){var t,n,i,a,l="minutes";if(void 0!=e[r])if(/\d/.test(e[r]))if(((t=o(/\d/)).length>2||parseInt(t,10)>59)&&(l="hours"),":"==e[r])if(r++,2==(n=o(/\d/)).length){if("hours"==l||":"==e[r]){if(":"!=e[r])return void s("No seconds found or minutes is greater than 59.");if(r++,2!=(i=o(/\d/)).length)return void s("Must be exactly two digits.")}else{if(2!=t.length)return void s("Must be exactly two digits.");i=n,n=t,t="0"}if("."==e[r])if(r++,3==(a=o(/\d/)).length)if(parseInt(n,10)>59)s("You cannot have more than 59 minutes.");else{if(!(parseInt(i,10)>59))return 60*parseInt(t,10)*60+60*parseInt(n,10)+parseInt(i,10)+parseInt(a,10)/1e3;s("You cannot have more than 59 seconds.")}else s("Milliseconds must be given in three digits.");else s('No decimal separator (".") found.')}else s("Must be exactly two digits.");else s("No time unit separator found.");else s("Timestamp must start with a character in the range 0-9.");else s("No timestamp found.")}this.parse=function(t,o){if(a(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime<o&&s("Start timestamp is not greater than or equal to start timestamp of previous cue."),i.test(e[r])&&s("Timestamp not separated from '--\x3e' by whitespace."),a(n),"-"==e[r])if(r++,"-"==e[r])if(r++,">"==e[r]){if(r++,i.test(e[r])&&s("'--\x3e' not separated from timestamp by whitespace."),a(n),t.endTime=l(),void 0!=t.endTime)return t.endTime<=t.startTime&&s("End timestamp is not greater than start timestamp."),i.test(e[r]),a(n),function(e,t){for(var i=e.split(n),r=[],a=Line truncated
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.defineProperties,n=Object.getOwnPropertyDescriptors,i=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,a=(t,n,i)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[n]=i,o=(e,t)=>{for(var n in t||(t={}))r.call(t,n)&&a(e,n,t[n]);if(i)for(var n of i(t))s.call(t,n)&&a(e,n,t[n]);return e},l=(e,i)=>t(e,n(i)),c=(e,t,n)=>new Promise((i,r)=>{var s=e=>{try{o(n.next(e))}catch(t){r(t)}},a=e=>{try{o(n.throw(e))}catch(t){r(t)}},o=e=>e.done?i(e.value):Promise.resolve(e.value).then(s,a);o((n=n.apply(e,t)).next())}),f={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},u=function(e){e||(e={"&amp":"&","&lt":"<","&gt":">","&lrm":"\u200e","&rlm":"\u200f","&nbsp":"\xa0"}),this.entities=e,this.parse=function(t,n){t=t.replace(/\0/g,"\ufffd");var i=Date.now(),r=0,s=t.split(/\r\n|\r|\n/),a=!1,o=[],l=[],c=[];function u(e,t){c.push({message:e,line:r+1,col:t})}var m=s[r],h=m.length,g="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(h<b||m.indexOf(g)!==0+v||h>b&&" "!==m[b]&&"\t"!==m[b])&&u('No valid signature. (File needs to start with "WEBVTT".)'),r++;""!=s[r]&&void 0!=s[r];){if(u("No blank line after the signature."),-1!=s[r].indexOf("--\x3e")){a=!0;break}r++}for(;void 0!=s[r];){for(var y;!a&&""==s[r];)r++;if(!a&&void 0==s[r])break;y=Object.assign({},f,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var x=!0;if(-1==s[r].indexOf("--\x3e")){if(y.id=s[r],/^NOTE($|[ \t])/.test(y.id)){for(r++;""!=s[r]&&void 0!=s[r];)-1!=s[r].indexOf("--\x3e")&&u("Cannot have timestamp in a comment."),r++;continue}if(/^STYLE($|[ \t])/.test(y.id)){var T=[],w=!1;for(r++;""!=s[r]&&void 0!=s[r];)-1!=s[r].indexOf("--\x3e")&&(u("Cannot have timestamp in a style block."),w=!0),T.push(s[r]),r++;if(l.length){u("Style blocks cannot appear after the first cue.");continue}w||o.push(T.join("\n"));continue}if(""==s[++r]||void 0==s[r]){u("Cue identifier cannot be standalone.");continue}if(-1==s[r].indexOf("--\x3e")){x=!1,u("Cue identifier needs to be followed by timestamp.");continue}}a=!1;var O=new d(s[r],u),P=0;if(l.length>0&&(P=l[l.length-1].startTime),!x||O.parse(y,P)){for(r++;""!=s[r]&&void 0!=s[r];){if(-1!=s[r].indexOf("--\x3e")){u("Blank line missing before cue."),a=!0;break}""!=y.text&&(y.text+="\n"),y.text+=s[r],r++}var A=new p(y.text,u,n,e);y.tree=A.parse(y.startTime,y.endTime),l.push(y)}else for(y=null,r++;""!=s[r]&&void 0!=s[r];){if(-1!=s[r].indexOf("--\x3e")){a=!0;break}r++}}return l.sort(function(e,t){return e.startTime<t.startTime?-1:e.startTime>t.startTime?1:e.endTime>t.endTime?-1:e.endTime<t.endTime?1:0}),{cues:l,errors:c,time:Date.now()-i,styles:o}}},d=function(e,t){var n=/[\u0020\t\f]/,i=/[^\u0020\t\f]/,r=(e=e,0),s=function(e){t(e,r+1)};function a(t){for(;void 0!=e[r]&&t.test(e[r]);)r++}function o(t){for(var n="";void 0!=e[r]&&t.test(e[r]);)n+=e[r],r++;return n}function l(){var t,n,i,a,l="minutes";if(void 0!=e[r])if(/\d/.test(e[r]))if(((t=o(/\d/)).length>2||parseInt(t,10)>59)&&(l="hours"),":"==e[r])if(r++,2==(n=o(/\d/)).length){if("hours"==l||":"==e[r]){if(":"!=e[r])return void s("No seconds found or minutes is greater than 59.");if(r++,2!=(i=o(/\d/)).length)return void s("Must be exactly two digits.")}else{if(2!=t.length)return void s("Must be exactly two digits.");i=n,n=t,t="0"}if("."==e[r])if(r++,3==(a=o(/\d/)).length)if(parseInt(n,10)>59)s("You cannot have more than 59 minutes.");else{if(!(parseInt(i,10)>59))return 60*parseInt(t,10)*60+60*parseInt(n,10)+parseInt(i,10)+parseInt(a,10)/1e3;s("You cannot have more than 59 seconds.")}else s("Milliseconds must be given in three digits.");else s('No decimal separator (".") found.')}else s("Must be exactly two digits.");else s("No time unit separator found.");else s("Timestamp must start with a character in the range 0-9.");else s("No timestamp found.")}this.parse=function(t,o){if(a(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime<o&&s("Start timestamp is not greater than or equal to start timestamp of previous cue."),i.test(e[r])&&s("Timestamp not separated from '--\x3e' by whitespace."),a(n),"-"==e[r])if(r++,"-"==e[r])if(r++,">"==e[r]){if(r++,i.test(e[r])&&s("'--\x3e' not separated from timestamp by whitespace."),a(n),t.endTime=l(),void 0!=t.endTime)return t.endTime<=t.startTime&&s("End timestamp is not greater than start timestamp."),i.test(e[r]),a(n),function(e,t){for(var i=e.split(n),r=[],a=Line truncated
@@ -293,7 +293,7 @@ var WebVTTCueTimingsAndSettingsParser = function(line, errorHandler) {
var settings = input.split(SPACE), seen = [];
for (var i = 0; i < settings.length; i++) {
if (settings[i] == "") continue;
var index = settings[i].indexOf(":"), setting = settings[i].slice(0, index), value = settings[i].slice(index + 1);
var index2 = settings[i].indexOf(":"), setting = settings[i].slice(0, index2), value = settings[i].slice(index2 + 1);
if (seen.indexOf(setting) != -1) {
err("Duplicate setting.");
}
@@ -812,9 +812,9 @@ function markNestedTimestamps(node) {
}
function parseTracks(vtts, subtitles) {
const parser = new WebVTTParser();
return vtts.map((vtt, index) => {
return vtts.map((vtt, index2) => {
const tree = parser.parse(vtt, "metadata");
return { ...tree, url: subtitles[index].url, name: subtitles[index].name };
return { ...tree, url: subtitles[index2].url, name: subtitles[index2].name };
});
}
function serializeTracks(trees) {
@@ -1040,6 +1040,7 @@ function artplayerPluginMultipleSubtitles({ subtitles = [] }) {
}
};
}
const index = Object.assign(artplayerPluginMultipleSubtitles, { default: artplayerPluginMultipleSubtitles });
export {
artplayerPluginMultipleSubtitles as default
index as default
};
+2 -1
View File
@@ -88,7 +88,8 @@
"test:auto-thumbnail": "node --test test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js refactor/scripts/auto-thumbnail-contract.test.mjs",
"test:vtt-thumbnail": "node --test test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js refactor/scripts/vtt-thumbnail-contract.test.mjs refactor/scripts/vtt-thumbnail-types.test.mjs",
"test:vtt-thumbnail-types-package": "node refactor/scripts/vtt-thumbnail-package-types.mjs",
"test:multiple-subtitles": "node --test test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js refactor/scripts/multiple-subtitles-contract.test.mjs refactor/scripts/multiple-subtitles-runtime-types.test.mjs"
"test:multiple-subtitles": "node --test test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js refactor/scripts/multiple-subtitles-contract.test.mjs refactor/scripts/multiple-subtitles-runtime-types.test.mjs refactor/scripts/multiple-subtitles-types.test.mjs",
"test:multiple-subtitles-types-package": "node refactor/scripts/multiple-subtitles-package-types.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
@@ -3,8 +3,11 @@
The public factory is `artplayerPluginMultipleSubtitles({ subtitles })`. Registration is
asynchronous and resolves to `{ name: 'multipleSubtitles', tracks, reset }`. `tracks(names)`
selects names in caller order, `tracks()` clears, and `reset()` restores the original order.
Both methods return undefined. Current declarations incorrectly omit the Promise and methods;
task PKG-MULTI-SUB-04 owns that compatibility work. Do not infer runtime behavior from them.
Both methods return undefined. Root and legacy declarations retain the historical synchronous
`LegacyResult` for existing extraction and replacement-function consumers. The additive
`artplayer-plugin-multiple-subtitles/runtime` entry describes the actual `Promise<Result>`
and selection methods. It shares the root implementation, not another plugin instance.
The factory has a writable `.default` self alias for historical CommonJS access.
## Current module boundaries
@@ -19,8 +22,8 @@ task PKG-MULTI-SUB-04 owns that compatibility work. Do not infer runtime behavio
Reentrant selections and stale host rejections cannot release the latest selected resource.
- `src/caption.ts` owns a subtitleAfterUpdate listener. It unwraps internal timestamp markers
in the custom HTML caption layer, preserving subsequent nodes and literal user text.
- `src/types.ts` describes the internal Promise result, selected tracks and narrow host/resource
boundaries; option fields reuse the existing public declaration. It is not a published API.
- `src/types.ts` describes selected tracks and narrow host/resource boundaries and reuses public
runtime option/result types. The private host/resource types are not a published API.
- `src/parser.d.ts` describes the vendored parse result and discriminated cue nodes. Parsed
timestamp values must remain numeric in both parsed and serialized trees.
- `src/parser.js` is vendored WebVTT parser/serializer code with a CC0 dedication. It is not
@@ -28,7 +31,11 @@ task PKG-MULTI-SUB-04 owns that compatibility work. Do not infer runtime behavio
The pinned upstream reference and full adaptation check are in
`../../refactor/baselines/multiple-subtitles-vendor/` and
`../../refactor/scripts/multiple-subtitles-vendor.mjs`.
- `types/artplayer-plugin-multiple-subtitles.d.ts` is the historical public declaration.
- `types/artplayer-plugin-multiple-subtitles.d.ts` owns public data types and the historical
callable signature. Root/legacy `.d.mts` and `.d.cts` forward that signature; `runtime.d.*`
forwards the precise asynchronous factory. Classic resolution uses typesVersions.
- `scripts/build-ts.js` generates the editor's global factory and named type namespace from
this declaration through the semantic generator. Never edit the generated editor file.
- `../../docs/assets/example/multiple.subtitles.js` demonstrates the real plugin name,
`tracks/reset`, name order and `.art-subtitle-chinese/.art-subtitle-japanese` CSS hooks.
@@ -77,6 +84,8 @@ Use the pinned Node and Yarn 1.22.22:
```sh
yarn test:multiple-subtitles
yarn test:multiple-subtitles-types-package
yarn build:ts artplayer-plugin-multiple-subtitles
yarn test:browser test/browser/multiple-subtitles-history.spec.js
yarn test:browser test/browser/multiple-subtitles-lifecycle.spec.js
yarn build artplayer-plugin-multiple-subtitles
@@ -98,6 +107,11 @@ environment variable above before running it; source builds are used when it is
The package tsconfig checks only authored TS and declarations, with strict/noUncheckedIndexedAccess
and allowJs=false. TS 5.9.3/5.1.6 positive and negative fixtures cover internal runtime types.
Do not remove cancellation/index boundary assertions by changing historical optional inputs.
The public synchronous declaration mismatch remains an explicit task 04 migration boundary.
Public declaration tests check exact old extraction/replacement signatures, accurate runtime
types and negative uses with TS 5.9.3 and 4.3.5. The package test packs core and plugin, installs
outside the workspace and checks Node10/NodeNext/Bundler modes plus real CJS/ESM identities.
Historical 1.0.0/1.1.0 export-assignment declarations and 1.2.0 default-module declarations
disagree on raw `import = require` extraction. Task 04 retains that unresolved boundary;
default-import compatibility must not be presented as proof of every historical module form.
Tasks 05/06 still own complete core/device combinations and demo acceptance.
See `../../refactor/baselines/multiple-subtitles-contract.md` for exact historical differences.
@@ -6,6 +6,29 @@ Multiple subtitles plugin for ArtPlayer
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-multiple-subtitles/index.js&example=multiple.subtitles)
## TypeScript
The root and `/legacy` type entries preserve historical parameter and return-type extraction.
Registration has always been asynchronous. For accurate Promise and selection-method types,
use the additive runtime entry, which loads the same implementation:
```ts
import multipleSubtitles from 'artplayer-plugin-multiple-subtitles/runtime'
const register = multipleSubtitles({
subtitles: [{ url: '/subtitles/en.vtt', name: 'en' }],
})
// With an existing ArtPlayer instance:
const result = await register(art)
result.tracks(['en'])
result.reset()
```
The option object is required; `{}` means no tracks. `tracks()` clears the selection;
`reset()` restores all original tracks. Both methods return `void`. The existing `onParser`
field remains declared for compatibility but is not invoked by the implementation.
See [maintenance notes](ARCHITECTURE.md) for module responsibilities and validation commands.
## License
MIT © Harvey Zhao
@@ -55,4 +55,4 @@
*
* d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
*/
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},t=function(t){t||(t={"&amp":"&","&lt":"<","&gt":">","&lrm":"\u200e","&rlm":"\u200f","&nbsp":"\xa0"}),this.entities=t,this.parse=function(s,a){s=s.replace(/\0/g,"\ufffd");var r=Date.now(),o=0,l=s.split(/\r\n|\r|\n/),c=!1,f=[],u=[],d=[];function p(e,t){d.push({message:e,line:o+1,col:t})}var m=l[o],h=m.length,g="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(h<b||m.indexOf(g)!==0+v||h>b&&" "!==m[b]&&"\t"!==m[b])&&p('No valid signature. (File needs to start with "WEBVTT".)'),o++;""!=l[o]&&void 0!=l[o];){if(p("No blank line after the signature."),-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}for(;void 0!=l[o];){for(var y;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;y=Object.assign({},e,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var T=!0;if(-1==l[o].indexOf("--\x3e")){if(y.id=l[o],/^NOTE($|[ \t])/.test(y.id)){for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&p("Cannot have timestamp in a comment."),o++;continue}if(/^STYLE($|[ \t])/.test(y.id)){var x=[],w=!1;for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&(p("Cannot have timestamp in a style block."),w=!0),x.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(x.join("\n"));continue}if(""==l[++o]||void 0==l[o]){p("Cue identifier cannot be standalone.");continue}if(-1==l[o].indexOf("--\x3e")){T=!1,p("Cue identifier needs to be followed by timestamp.");continue}}c=!1;var A=new n(l[o],p),O=0;if(u.length>0&&(O=u[u.length-1].startTime),!T||A.parse(y,O)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=y.text&&(y.text+="\n"),y.text+=l[o],o++}var N=new i(y.text,p,a,t);y.tree=N.parse(y.startTime,y.endTime),u.push(y)}else for(y=null,o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}}return u.sort(function(e,t){return e.startTime<t.startTime?-1:e.startTime>t.startTime?1:e.endTime>t.endTime?-1:e.endTime<t.endTime?1:0}),{cues:u,errors:d,time:Date.now()-r,styles:f}}},n=function(e,t){var n=/[\u0020\t\f]/,i=/[^\u0020\t\f]/,s=(e=e,0),a=function(e){t(e,s+1)};function r(t){for(;void 0!=e[s]&&t.test(e[s]);)s++}function o(t){for(var n="";void 0!=e[s]&&t.test(e[s]);)n+=e[s],s++;return n}function l(){var t,n,i,r,l="minutes";if(void 0!=e[s])if(/\d/.test(e[s]))if(((t=o(/\d/)).length>2||parseInt(t,10)>59)&&(l="hours"),":"==e[s])if(s++,2==(n=o(/\d/)).length){if("hours"==l||":"==e[s]){if(":"!=e[s])return void a("No seconds found or minutes is greater than 59.");if(s++,2!=(i=o(/\d/)).length)return void a("Must be exactly two digits.")}else{if(2!=t.length)return void a("Must be exactly two digits.");i=n,n=t,t="0"}if("."==e[s])if(s++,3==(r=o(/\d/)).length)if(parseInt(n,10)>59)a("You cannot have more than 59 minutes.");else{if(!(parseInt(i,10)>59))return 60*parseInt(t,10)*60+60*parseInt(n,10)+parseInt(i,10)+parseInt(r,10)/1e3;a("You cannot have more than 59 seconds.")}else a("Milliseconds must be given in three digits.");else a('No decimal separator (".") found.')}else a("Must be exactly two digits.");else a("No time unit separator found.");else a("Timestamp must start with a character in the range 0-9.");else a("No timestamp found.")}this.parse=function(t,o){if(r(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime<o&&a("Start timestamp is not greater than or equal to start timestamp of previous cue."),i.test(e[s])&&a("Timestamp not separated from '--\x3e' by whitespace."),r(n),"-"==e[s])if(s++,"-"==e[s])if(s++,">"==e[s]){if(s++,i.test(e[s])&&a("'--\x3e' not separated from timestamp by whitespace."),r(n),t.endTime=l(),void 0!=t.endTime)return t.endTime<=t.startTime&&a("End timestamp is not greater than start timestamp."),i.test(e[s]),r(n),function(e,t){for(var i=e.split(n),s=[],r=0;r<i.length;r++)if(""!=i[r]){var o=i[r].indexOf(":"),l=i[r].slice(0,o),c=i[r].slice(o+1);if(-1!=s.indexOf(l)&&a("Duplicate setting."),s.push(l),""==c)return void a("No value for setting defined.");if("vertical"==l){if("rl"!=c&&"lr"!=c){a("Writing direction can only be set to 'rl' or 'rl'.");continue}t.direction=c}else if("line"==l){if(/,/.test(c)){var f=c.split(",");c=f[0];var u=f[1]}if(!/^[-\d](\d*)(\.\d+)?%?$/.test(c)){a("Line position takes a number or percentage.");continue}if(-1!=c.indexOf("-",1)){a("Line position can only have '-' at the start.");continue}if(-1!=c.indexOf("%")&&c.indexOf("%")!=c.Line truncated
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},t=function(t){t||(t={"&amp":"&","&lt":"<","&gt":">","&lrm":"\u200e","&rlm":"\u200f","&nbsp":"\xa0"}),this.entities=t,this.parse=function(s,a){s=s.replace(/\0/g,"\ufffd");var r=Date.now(),o=0,l=s.split(/\r\n|\r|\n/),c=!1,f=[],u=[],d=[];function p(e,t){d.push({message:e,line:o+1,col:t})}var m=l[o],h=m.length,g="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(h<b||m.indexOf(g)!==0+v||h>b&&" "!==m[b]&&"\t"!==m[b])&&p('No valid signature. (File needs to start with "WEBVTT".)'),o++;""!=l[o]&&void 0!=l[o];){if(p("No blank line after the signature."),-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}for(;void 0!=l[o];){for(var y;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;y=Object.assign({},e,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var T=!0;if(-1==l[o].indexOf("--\x3e")){if(y.id=l[o],/^NOTE($|[ \t])/.test(y.id)){for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&p("Cannot have timestamp in a comment."),o++;continue}if(/^STYLE($|[ \t])/.test(y.id)){var x=[],w=!1;for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&(p("Cannot have timestamp in a style block."),w=!0),x.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(x.join("\n"));continue}if(""==l[++o]||void 0==l[o]){p("Cue identifier cannot be standalone.");continue}if(-1==l[o].indexOf("--\x3e")){T=!1,p("Cue identifier needs to be followed by timestamp.");continue}}c=!1;var A=new n(l[o],p),O=0;if(u.length>0&&(O=u[u.length-1].startTime),!T||A.parse(y,O)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=y.text&&(y.text+="\n"),y.text+=l[o],o++}var N=new i(y.text,p,a,t);y.tree=N.parse(y.startTime,y.endTime),u.push(y)}else for(y=null,o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}}return u.sort(function(e,t){return e.startTime<t.startTime?-1:e.startTime>t.startTime?1:e.endTime>t.endTime?-1:e.endTime<t.endTime?1:0}),{cues:u,errors:d,time:Date.now()-r,styles:f}}},n=function(e,t){var n=/[\u0020\t\f]/,i=/[^\u0020\t\f]/,s=(e=e,0),a=function(e){t(e,s+1)};function r(t){for(;void 0!=e[s]&&t.test(e[s]);)s++}function o(t){for(var n="";void 0!=e[s]&&t.test(e[s]);)n+=e[s],s++;return n}function l(){var t,n,i,r,l="minutes";if(void 0!=e[s])if(/\d/.test(e[s]))if(((t=o(/\d/)).length>2||parseInt(t,10)>59)&&(l="hours"),":"==e[s])if(s++,2==(n=o(/\d/)).length){if("hours"==l||":"==e[s]){if(":"!=e[s])return void a("No seconds found or minutes is greater than 59.");if(s++,2!=(i=o(/\d/)).length)return void a("Must be exactly two digits.")}else{if(2!=t.length)return void a("Must be exactly two digits.");i=n,n=t,t="0"}if("."==e[s])if(s++,3==(r=o(/\d/)).length)if(parseInt(n,10)>59)a("You cannot have more than 59 minutes.");else{if(!(parseInt(i,10)>59))return 60*parseInt(t,10)*60+60*parseInt(n,10)+parseInt(i,10)+parseInt(r,10)/1e3;a("You cannot have more than 59 seconds.")}else a("Milliseconds must be given in three digits.");else a('No decimal separator (".") found.')}else a("Must be exactly two digits.");else a("No time unit separator found.");else a("Timestamp must start with a character in the range 0-9.");else a("No timestamp found.")}this.parse=function(t,o){if(r(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime<o&&a("Start timestamp is not greater than or equal to start timestamp of previous cue."),i.test(e[s])&&a("Timestamp not separated from '--\x3e' by whitespace."),r(n),"-"==e[s])if(s++,"-"==e[s])if(s++,">"==e[s]){if(s++,i.test(e[s])&&a("'--\x3e' not separated from timestamp by whitespace."),r(n),t.endTime=l(),void 0!=t.endTime)return t.endTime<=t.startTime&&a("End timestamp is not greater than start timestamp."),i.test(e[s]),r(n),function(e,t){for(var i=e.split(n),s=[],r=0;r<i.length;r++)if(""!=i[r]){var o=i[r].indexOf(":"),l=i[r].slice(0,o),c=i[r].slice(o+1);if(-1!=s.indexOf(l)&&a("Duplicate setting."),s.push(l),""==c)return void a("No value for setting defined.");if("vertical"==l){if("rl"!=c&&"lr"!=c){a("Writing direction can only be set to 'rl' or 'rl'.");continue}t.direction=c}else if("line"==l){if(/,/.test(c)){var f=c.split(",");c=f[0];var u=f[1]}if(!/^[-\d](\d*)(\.\d+)?%?$/.test(c)){a("Line position takes a number or percentage.");continue}if(-1!=c.indexOf("-",1)){a("Line position can only have '-' at the start.");continue}if(-1!=c.indexOf("%")&&c.indexOf("%")!=c.Line truncated
@@ -55,4 +55,4 @@
*
* d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
*/
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.defineProperties,n=Object.getOwnPropertyDescriptors,i=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,a=(t,n,i)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[n]=i,o=(e,t)=>{for(var n in t||(t={}))r.call(t,n)&&a(e,n,t[n]);if(i)for(var n of i(t))s.call(t,n)&&a(e,n,t[n]);return e},l=(e,i)=>t(e,n(i)),c=(e,t,n)=>new Promise((i,r)=>{var s=e=>{try{o(n.next(e))}catch(t){r(t)}},a=e=>{try{o(n.throw(e))}catch(t){r(t)}},o=e=>e.done?i(e.value):Promise.resolve(e.value).then(s,a);o((n=n.apply(e,t)).next())}),f={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},u=function(e){e||(e={"&amp":"&","&lt":"<","&gt":">","&lrm":"\u200e","&rlm":"\u200f","&nbsp":"\xa0"}),this.entities=e,this.parse=function(t,n){t=t.replace(/\0/g,"\ufffd");var i=Date.now(),r=0,s=t.split(/\r\n|\r|\n/),a=!1,o=[],l=[],c=[];function u(e,t){c.push({message:e,line:r+1,col:t})}var m=s[r],h=m.length,g="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(h<b||m.indexOf(g)!==0+v||h>b&&" "!==m[b]&&"\t"!==m[b])&&u('No valid signature. (File needs to start with "WEBVTT".)'),r++;""!=s[r]&&void 0!=s[r];){if(u("No blank line after the signature."),-1!=s[r].indexOf("--\x3e")){a=!0;break}r++}for(;void 0!=s[r];){for(var y;!a&&""==s[r];)r++;if(!a&&void 0==s[r])break;y=Object.assign({},f,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var x=!0;if(-1==s[r].indexOf("--\x3e")){if(y.id=s[r],/^NOTE($|[ \t])/.test(y.id)){for(r++;""!=s[r]&&void 0!=s[r];)-1!=s[r].indexOf("--\x3e")&&u("Cannot have timestamp in a comment."),r++;continue}if(/^STYLE($|[ \t])/.test(y.id)){var T=[],w=!1;for(r++;""!=s[r]&&void 0!=s[r];)-1!=s[r].indexOf("--\x3e")&&(u("Cannot have timestamp in a style block."),w=!0),T.push(s[r]),r++;if(l.length){u("Style blocks cannot appear after the first cue.");continue}w||o.push(T.join("\n"));continue}if(""==s[++r]||void 0==s[r]){u("Cue identifier cannot be standalone.");continue}if(-1==s[r].indexOf("--\x3e")){x=!1,u("Cue identifier needs to be followed by timestamp.");continue}}a=!1;var O=new d(s[r],u),P=0;if(l.length>0&&(P=l[l.length-1].startTime),!x||O.parse(y,P)){for(r++;""!=s[r]&&void 0!=s[r];){if(-1!=s[r].indexOf("--\x3e")){u("Blank line missing before cue."),a=!0;break}""!=y.text&&(y.text+="\n"),y.text+=s[r],r++}var A=new p(y.text,u,n,e);y.tree=A.parse(y.startTime,y.endTime),l.push(y)}else for(y=null,r++;""!=s[r]&&void 0!=s[r];){if(-1!=s[r].indexOf("--\x3e")){a=!0;break}r++}}return l.sort(function(e,t){return e.startTime<t.startTime?-1:e.startTime>t.startTime?1:e.endTime>t.endTime?-1:e.endTime<t.endTime?1:0}),{cues:l,errors:c,time:Date.now()-i,styles:o}}},d=function(e,t){var n=/[\u0020\t\f]/,i=/[^\u0020\t\f]/,r=(e=e,0),s=function(e){t(e,r+1)};function a(t){for(;void 0!=e[r]&&t.test(e[r]);)r++}function o(t){for(var n="";void 0!=e[r]&&t.test(e[r]);)n+=e[r],r++;return n}function l(){var t,n,i,a,l="minutes";if(void 0!=e[r])if(/\d/.test(e[r]))if(((t=o(/\d/)).length>2||parseInt(t,10)>59)&&(l="hours"),":"==e[r])if(r++,2==(n=o(/\d/)).length){if("hours"==l||":"==e[r]){if(":"!=e[r])return void s("No seconds found or minutes is greater than 59.");if(r++,2!=(i=o(/\d/)).length)return void s("Must be exactly two digits.")}else{if(2!=t.length)return void s("Must be exactly two digits.");i=n,n=t,t="0"}if("."==e[r])if(r++,3==(a=o(/\d/)).length)if(parseInt(n,10)>59)s("You cannot have more than 59 minutes.");else{if(!(parseInt(i,10)>59))return 60*parseInt(t,10)*60+60*parseInt(n,10)+parseInt(i,10)+parseInt(a,10)/1e3;s("You cannot have more than 59 seconds.")}else s("Milliseconds must be given in three digits.");else s('No decimal separator (".") found.')}else s("Must be exactly two digits.");else s("No time unit separator found.");else s("Timestamp must start with a character in the range 0-9.");else s("No timestamp found.")}this.parse=function(t,o){if(a(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime<o&&s("Start timestamp is not greater than or equal to start timestamp of previous cue."),i.test(e[r])&&s("Timestamp not separated from '--\x3e' by whitespace."),a(n),"-"==e[r])if(r++,"-"==e[r])if(r++,">"==e[r]){if(r++,i.test(e[r])&&s("'--\x3e' not separated from timestamp by whitespace."),a(n),t.endTime=l(),void 0!=t.endTime)return t.endTime<=t.startTime&&s("End timestamp is not greater than start timestamp."),i.test(e[r]),a(n),function(e,t){for(var i=e.split(n),r=[],a=Line truncated
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.defineProperties,n=Object.getOwnPropertyDescriptors,i=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,a=(t,n,i)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[n]=i,o=(e,t)=>{for(var n in t||(t={}))r.call(t,n)&&a(e,n,t[n]);if(i)for(var n of i(t))s.call(t,n)&&a(e,n,t[n]);return e},l=(e,i)=>t(e,n(i)),c=(e,t,n)=>new Promise((i,r)=>{var s=e=>{try{o(n.next(e))}catch(t){r(t)}},a=e=>{try{o(n.throw(e))}catch(t){r(t)}},o=e=>e.done?i(e.value):Promise.resolve(e.value).then(s,a);o((n=n.apply(e,t)).next())}),f={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},u=function(e){e||(e={"&amp":"&","&lt":"<","&gt":">","&lrm":"\u200e","&rlm":"\u200f","&nbsp":"\xa0"}),this.entities=e,this.parse=function(t,n){t=t.replace(/\0/g,"\ufffd");var i=Date.now(),r=0,s=t.split(/\r\n|\r|\n/),a=!1,o=[],l=[],c=[];function u(e,t){c.push({message:e,line:r+1,col:t})}var m=s[r],h=m.length,g="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(h<b||m.indexOf(g)!==0+v||h>b&&" "!==m[b]&&"\t"!==m[b])&&u('No valid signature. (File needs to start with "WEBVTT".)'),r++;""!=s[r]&&void 0!=s[r];){if(u("No blank line after the signature."),-1!=s[r].indexOf("--\x3e")){a=!0;break}r++}for(;void 0!=s[r];){for(var y;!a&&""==s[r];)r++;if(!a&&void 0==s[r])break;y=Object.assign({},f,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var x=!0;if(-1==s[r].indexOf("--\x3e")){if(y.id=s[r],/^NOTE($|[ \t])/.test(y.id)){for(r++;""!=s[r]&&void 0!=s[r];)-1!=s[r].indexOf("--\x3e")&&u("Cannot have timestamp in a comment."),r++;continue}if(/^STYLE($|[ \t])/.test(y.id)){var T=[],w=!1;for(r++;""!=s[r]&&void 0!=s[r];)-1!=s[r].indexOf("--\x3e")&&(u("Cannot have timestamp in a style block."),w=!0),T.push(s[r]),r++;if(l.length){u("Style blocks cannot appear after the first cue.");continue}w||o.push(T.join("\n"));continue}if(""==s[++r]||void 0==s[r]){u("Cue identifier cannot be standalone.");continue}if(-1==s[r].indexOf("--\x3e")){x=!1,u("Cue identifier needs to be followed by timestamp.");continue}}a=!1;var O=new d(s[r],u),P=0;if(l.length>0&&(P=l[l.length-1].startTime),!x||O.parse(y,P)){for(r++;""!=s[r]&&void 0!=s[r];){if(-1!=s[r].indexOf("--\x3e")){u("Blank line missing before cue."),a=!0;break}""!=y.text&&(y.text+="\n"),y.text+=s[r],r++}var A=new p(y.text,u,n,e);y.tree=A.parse(y.startTime,y.endTime),l.push(y)}else for(y=null,r++;""!=s[r]&&void 0!=s[r];){if(-1!=s[r].indexOf("--\x3e")){a=!0;break}r++}}return l.sort(function(e,t){return e.startTime<t.startTime?-1:e.startTime>t.startTime?1:e.endTime>t.endTime?-1:e.endTime<t.endTime?1:0}),{cues:l,errors:c,time:Date.now()-i,styles:o}}},d=function(e,t){var n=/[\u0020\t\f]/,i=/[^\u0020\t\f]/,r=(e=e,0),s=function(e){t(e,r+1)};function a(t){for(;void 0!=e[r]&&t.test(e[r]);)r++}function o(t){for(var n="";void 0!=e[r]&&t.test(e[r]);)n+=e[r],r++;return n}function l(){var t,n,i,a,l="minutes";if(void 0!=e[r])if(/\d/.test(e[r]))if(((t=o(/\d/)).length>2||parseInt(t,10)>59)&&(l="hours"),":"==e[r])if(r++,2==(n=o(/\d/)).length){if("hours"==l||":"==e[r]){if(":"!=e[r])return void s("No seconds found or minutes is greater than 59.");if(r++,2!=(i=o(/\d/)).length)return void s("Must be exactly two digits.")}else{if(2!=t.length)return void s("Must be exactly two digits.");i=n,n=t,t="0"}if("."==e[r])if(r++,3==(a=o(/\d/)).length)if(parseInt(n,10)>59)s("You cannot have more than 59 minutes.");else{if(!(parseInt(i,10)>59))return 60*parseInt(t,10)*60+60*parseInt(n,10)+parseInt(i,10)+parseInt(a,10)/1e3;s("You cannot have more than 59 seconds.")}else s("Milliseconds must be given in three digits.");else s('No decimal separator (".") found.')}else s("Must be exactly two digits.");else s("No time unit separator found.");else s("Timestamp must start with a character in the range 0-9.");else s("No timestamp found.")}this.parse=function(t,o){if(a(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime<o&&s("Start timestamp is not greater than or equal to start timestamp of previous cue."),i.test(e[r])&&s("Timestamp not separated from '--\x3e' by whitespace."),a(n),"-"==e[r])if(r++,"-"==e[r])if(r++,">"==e[r]){if(r++,i.test(e[r])&&s("'--\x3e' not separated from timestamp by whitespace."),a(n),t.endTime=l(),void 0!=t.endTime)return t.endTime<=t.startTime&&s("End timestamp is not greater than start timestamp."),i.test(e[r]),a(n),function(e,t){for(var i=e.split(n),r=[],a=Line truncated
@@ -293,7 +293,7 @@ var WebVTTCueTimingsAndSettingsParser = function(line, errorHandler) {
var settings = input.split(SPACE), seen = [];
for (var i = 0; i < settings.length; i++) {
if (settings[i] == "") continue;
var index = settings[i].indexOf(":"), setting = settings[i].slice(0, index), value = settings[i].slice(index + 1);
var index2 = settings[i].indexOf(":"), setting = settings[i].slice(0, index2), value = settings[i].slice(index2 + 1);
if (seen.indexOf(setting) != -1) {
err("Duplicate setting.");
}
@@ -812,9 +812,9 @@ function markNestedTimestamps(node) {
}
function parseTracks(vtts, subtitles) {
const parser = new WebVTTParser();
return vtts.map((vtt, index) => {
return vtts.map((vtt, index2) => {
const tree = parser.parse(vtt, "metadata");
return { ...tree, url: subtitles[index].url, name: subtitles[index].name };
return { ...tree, url: subtitles[index2].url, name: subtitles[index2].name };
});
}
function serializeTracks(trees) {
@@ -1040,6 +1040,7 @@ function artplayerPluginMultipleSubtitles({ subtitles = [] }) {
}
};
}
const index = Object.assign(artplayerPluginMultipleSubtitles, { default: artplayerPluginMultipleSubtitles });
export {
artplayerPluginMultipleSubtitles as default
index as default
};
@@ -19,19 +19,49 @@
],
"exports": {
".": {
"types": "./types/artplayer-plugin-multiple-subtitles.d.ts",
"import": "./dist/artplayer-plugin-multiple-subtitles.mjs",
"require": "./dist/artplayer-plugin-multiple-subtitles.js"
"import": {
"types": "./types/artplayer-plugin-multiple-subtitles.d.mts",
"default": "./dist/artplayer-plugin-multiple-subtitles.mjs"
},
"require": {
"types": "./types/artplayer-plugin-multiple-subtitles.d.cts",
"default": "./dist/artplayer-plugin-multiple-subtitles.js"
}
},
"./legacy": {
"types": "./types/artplayer-plugin-multiple-subtitles.d.ts",
"import": "./dist/artplayer-plugin-multiple-subtitles.legacy.js",
"require": "./dist/artplayer-plugin-multiple-subtitles.legacy.js"
"import": {
"types": "./types/artplayer-plugin-multiple-subtitles.d.mts",
"default": "./dist/artplayer-plugin-multiple-subtitles.legacy.js"
},
"require": {
"types": "./types/artplayer-plugin-multiple-subtitles.d.cts",
"default": "./dist/artplayer-plugin-multiple-subtitles.legacy.js"
}
},
"./runtime": {
"import": {
"types": "./types/runtime.d.mts",
"default": "./dist/artplayer-plugin-multiple-subtitles.mjs"
},
"require": {
"types": "./types/runtime.d.cts",
"default": "./dist/artplayer-plugin-multiple-subtitles.js"
}
}
},
"main": "./dist/artplayer-plugin-multiple-subtitles.js",
"module": "./dist/artplayer-plugin-multiple-subtitles.mjs",
"types": "./types/artplayer-plugin-multiple-subtitles.d.ts",
"legacy": "./dist/artplayer-plugin-multiple-subtitles.legacy.js",
"browserslist": "last 1 Chrome version"
"browserslist": "last 1 Chrome version",
"typesVersions": {
"*": {
"legacy": [
"types/artplayer-plugin-multiple-subtitles.d.ts"
],
"runtime": [
"types/runtime.d.ts"
]
}
}
}
@@ -1,4 +1,5 @@
import type Artplayer from 'artplayer'
import type { RuntimeFactory } from '../types/artplayer-plugin-multiple-subtitles'
import type { Option, Result, Track } from './types'
import installCaptionView from './caption'
import createLifetime from './lifetime'
@@ -6,7 +7,7 @@ import { parseTracks, serializeTracks } from './merge'
import createRenderer from './render'
import loadVtt from './request'
export default function artplayerPluginMultipleSubtitles({ subtitles = [] }: Option) {
function artplayerPluginMultipleSubtitles({ subtitles = [] }: Option) {
return async (art: Artplayer): Promise<Result> => {
const { unescape, getExt, srtToVtt, assToVtt } = (art.constructor as typeof Artplayer).utils
const lifetime = createLifetime(art)
@@ -44,3 +45,5 @@ export default function artplayerPluginMultipleSubtitles({ subtitles = [] }: Opt
}
}
}
export default Object.assign(artplayerPluginMultipleSubtitles, { default: artplayerPluginMultipleSubtitles }) as RuntimeFactory
@@ -4,15 +4,7 @@ import type legacyFactory from '../types/artplayer-plugin-multiple-subtitles'
import type { ParseResult } from './parser'
export type LegacyOption = Parameters<typeof legacyFactory>[0]
export type TrackOption = LegacyOption['subtitles'][number]
export interface Option {
subtitles?: TrackOption[]
}
export interface Result {
name: 'multipleSubtitles'
tracks: (names?: string[]) => void
reset: () => void
}
export type { RuntimeOption as Option, Result, TrackOption } from '../types/artplayer-plugin-multiple-subtitles'
export interface Track extends ParseResult {
url: string | undefined
name: string | undefined
@@ -6,6 +6,8 @@
"include": [
"src/**/*.ts",
"types/**/*.d.ts",
"types/**/*.d.mts",
"types/**/*.d.cts",
"../../types/assets.d.ts",
"../../test/types/multiple-subtitles-runtime.ts"
]
@@ -0,0 +1,5 @@
import type { Factory } from './artplayer-plugin-multiple-subtitles.js'
declare const artplayerPluginMultipleSubtitles: Factory
export default artplayerPluginMultipleSubtitles
export type { Factory, LegacyResult, Option, Result, RuntimeFactory, RuntimeOption, TrackOption } from './artplayer-plugin-multiple-subtitles.js'
@@ -0,0 +1,5 @@
import type { Factory } from './artplayer-plugin-multiple-subtitles.js'
declare const artplayerPluginMultipleSubtitles: Factory
export default artplayerPluginMultipleSubtitles
export type { Factory, LegacyResult, Option, Result, RuntimeFactory, RuntimeOption, TrackOption } from './artplayer-plugin-multiple-subtitles.js'
@@ -1,15 +1,40 @@
import type Artplayer from 'artplayer'
declare const artplayerPluginMultipleSubtitles: (option: {
subtitles: {
url?: string
name?: string
type?: 'vtt' | 'srt' | 'ass'
encoding?: string
onParser?: (...args: object[]) => object
}[]
}) => (art: Artplayer) => {
export interface TrackOption {
url?: string
name?: string
type?: 'vtt' | 'srt' | 'ass'
encoding?: string
onParser?: (...args: object[]) => object
}
export interface Option {
subtitles: TrackOption[]
}
export interface RuntimeOption {
subtitles?: TrackOption[]
}
export interface LegacyResult {
name: 'multipleSubtitles'
}
export interface Result extends LegacyResult {
tracks: (names?: string[]) => void
reset: () => void
}
/** Historical synchronous extraction; actual registration is asynchronous. */
export type Factory = (option: Option) => (art: Artplayer) => LegacyResult
/** Accurate runtime view available through the /runtime entry. */
export interface RuntimeFactory {
(option: RuntimeOption): (art: Artplayer) => Promise<Result>
default: RuntimeFactory
}
/** Preserve existing parameter extraction and replacement-function compatibility. */
declare function artplayerPluginMultipleSubtitles(option: Option): (art: Artplayer) => LegacyResult
export default artplayerPluginMultipleSubtitles
@@ -0,0 +1,4 @@
import type { RuntimeFactory } from './artplayer-plugin-multiple-subtitles.js'
declare const artplayerPluginMultipleSubtitles: RuntimeFactory
export = artplayerPluginMultipleSubtitles
@@ -0,0 +1,5 @@
import type { RuntimeFactory } from './artplayer-plugin-multiple-subtitles.js'
declare const artplayerPluginMultipleSubtitles: RuntimeFactory
export default artplayerPluginMultipleSubtitles
export type { Result, RuntimeFactory, RuntimeOption, TrackOption } from './artplayer-plugin-multiple-subtitles.js'
@@ -0,0 +1,5 @@
import type { RuntimeFactory } from './artplayer-plugin-multiple-subtitles.js'
declare const artplayerPluginMultipleSubtitles: RuntimeFactory
export default artplayerPluginMultipleSubtitles
export type { Result, RuntimeFactory, RuntimeOption, TrackOption } from './artplayer-plugin-multiple-subtitles.js'
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,67 @@
# PKG-MULTI-SUB-04 公开类型与实际安装检查点
本次处理 API-06/09/11。根入口和 legacy 保留旧的必填 subtitles 数组、可选轨道字段、
同步 LegacyResult 推导以及可被普通函数替换的工厂类型;新增 `/runtime` 入口提供实际的
Promise<Result>、tracks(names?) 和 reset()。Result/RuntimeOption 由公开定义统一维护,
内部实现复用,parser 与资源宿主边界仍为私有。没有把 Promise 伪装成同步结果。
## 最终入口与旧用法
- `import subtitles from 'artplayer-plugin-multiple-subtitles'` 和 `/legacy` 的旧默认导入、
Parameters、ReturnType 和替换函数仍通过;旧返回声明只保留 name,运行时始终异步。
- 新代码可 `import subtitles from 'artplayer-plugin-multiple-subtitles/runtime'`,通过
`await subtitles({ subtitles: [...] })(art)` 无断言取得 tracks/reset;options 必传,{} 合法。
- root/runtime 复用同一产物及函数。现代和 legacy 函数增加可写、可枚举、可配置的
`.default` 自引用,兼容旧 CommonJS 对象的有效 default 调用;现代直接调用继续有效。
- root/legacy 使用独立 .d.mts/.d.cts 转发默认声明;runtime 的 .d.cts 用 export= 提供
直接及 default 调用;typesVersions 支持 classic 解析。包内新增五个声明文件。
- 编辑器通过正常 build:ts 语义生成旧全局工厂及命名类型空间。RuntimeFactory 是类型,
没有新增同名全局函数。onParser 保留原字段类型及不调用的运行时行为。
## 实际兼容冲突仍待决
真实 1.0.0/1.1.0 包的 CommonJS 是对象.default,但声明 export= 将模块描述成直接函数;
1.2.0 运行时是直接函数,声明却是默认导出模块。安装消费者已分别复现:
| 类型写法 | 1.0.0 / 1.1.0 | 1.2.0 | 本次候选 |
| --- | --- | --- | --- |
| default import,Parameters/ReturnType,普通函数替换 | 通过 classic | 通过 classic | 五种模式通过 |
| import=require 的原始模块直接提取/替换 | 类型通过;实际直接调用失败 | 类型失败 | 类型失败 |
| import=require 后取 .default 再提取/替换 | 类型失败;实际调用有效 | 类型通过 | 类型通过;实际也可调用 |
| NodeNext ESM 默认导入 | 通过 | 原声明 8 项诊断 | 候选通过 |
不能将原始模块同时改成“可调用且必须有 default”的类型来静默结束任务:这样普通替换
函数会缺少 default;若 default 可选,则已有无断言 default 调用失败。两个失败分别有
2322 和 2722 的编译复现,TS 5.9.3/4.3.5 覆盖。当前候选保留 1.2 的模块声明,旧 JS 两种
有效入口均可运行,但未声称兼容所有旧类型提取。需要明确接受这项类型迁移差异才能关闭
对应边界;此前 Ads 的参数推导修正授权不自动覆盖此项。04 和 TYPE/EXPORT 风险保持 open。
## 已执行验证
- 本包 274 项通过;源码/main/legacy 正常、生命周期、合并各 39 项通过。新增两项验证
script global/CJS 自别名、属性描述符、真实 async 注册、void 方法及销毁清理。
- 公开类型五模式正例通过,移除 expect-error 各准确拒绝 12 个非法用法;实际旧三个
tarball 声明与默认导入/替换消费者分别通过 TS 5.9.3/4.3.5 classic 检查。
- strict 生产源码 TS 5.9.3/5.1.6 及每编译器 9 个负例通过;编辑器每编译器 2 个负例。
- 新增 `yarn test:multiple-subtitles-types-package`:三个已发布 tarball × 五模式,
候选实际 pack × 七模式,共 22 格;旧 1.2 NodeNext ESM 的失败单独登记,不计为兼容成功。
使用仓库外消费者、packed core、offline install/frozen reinstall、锁字节一致和全部
已安装成员字节核对,断言无 workspace 符号链接或类型逃逸。实际 Node 验证 root/runtime
CJS/ESM 的身份以及 legacy default/require 的身份,不只检查 manifest 路径存在。
- 初次矩阵复现旧 1.2 NodeNext 无上下文 callback 的 7019 诊断;另一次核对发现旧 TS
诊断顺序不同。核实具体旧包错误后固定各编译器预期,没有把候选失败列为允许项。
- Chromium 153.0.8010.12 / Firefox 155.0 / Windows WebKit 26.6,发布/候选核心,
三入口每个 36 项、共 108 项通过。覆盖 SRT 显示/选择、取消、Blob、HTTP、视频切源、
内嵌时间戳。无 skip/flaky;不是物理 Safari、移动设备或全部旧核心验收。
- 完整 CI 2344 项通过(1989 单元、14 工程、341 基线),严格生产 TS 353 文件;显式
lint 通过。正常 build 生成三个 dist 及 docs 副本,逐字一致;正常 build:ts 更新编辑器。
完整检查及内容指纹见[证据](../baselines/multiple-subtitles-public-types.json)。新增脚本已接入
本包测试和主 baseline glob;实际安装脚本是独立较慢命令,最终分发集成仍归 06。
没有新依赖、锁或版本变更。包内 README/ARCHITECTURE 同步入口和模块维护说明。
## 后续与回退
04 保持 doing,等待上述历史类型取舍结论;实体解码、完整核心/设备/编辑器验证仍归 05/06。
本次独立本地检查点提交,不推送、不发布。回退恢复旧声明及 manifest,移除 runtime 入口和
default 别名;此前请求、时间戳与资源修复保持。不能将本检查点视为整个包或项目已可发布。
@@ -0,0 +1,11 @@
import type Artplayer from 'artplayer'
// eslint-disable-next-line ts/no-require-imports -- Exercise historical export-assignment consumers.
import plugin = require('artplayer-plugin-multiple-subtitles')
const vtt = plugin
declare const art: Artplayer
const options: Parameters<typeof vtt>[0] = { subtitles: [] }
const result: ReturnType<ReturnType<typeof vtt>> = { name: 'multipleSubtitles' }
const replacement: typeof vtt = (_option: Parameters<typeof vtt>[0]) => (_art: Artplayer) => result
void vtt(options)(art).name
void replacement
@@ -0,0 +1,7 @@
import type { Factory } from 'artplayer-plugin-multiple-subtitles'
declare const replacement: Factory
const required: Factory & { default: Factory } = replacement
const optional: Factory & { default?: Factory } = replacement
optional.default({ subtitles: [] })
void required
@@ -0,0 +1,9 @@
import Artplayer from 'artplayer'
import subtitles from 'artplayer-plugin-multiple-subtitles'
const option: Parameters<typeof subtitles>[0] = { subtitles: [{ url: '/en.vtt', name: 'en', type: 'vtt', encoding: 'utf-8', onParser: (...args) => args }, {}] }
const art = new Artplayer({ container: '#player', url: '/video.mp4', plugins: [subtitles(option)] })
const result: ReturnType<ReturnType<typeof subtitles>> = { name: 'multipleSubtitles' }
const replacement: typeof subtitles = (_option: Parameters<typeof subtitles>[0]) => (_art: Artplayer) => result
void subtitles(option)(art).name
void replacement
+1 -1
View File
@@ -515,7 +515,7 @@
- PKG-MULTI-SUB-01: [记录](baselines/multiple-subtitles-release.json) [记录](baselines/multiple-subtitles-contract.md) [记录](baselines/multiple-subtitles-contract-validation.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-01-contract.md)
- PKG-MULTI-SUB-02: [记录](baselines/multiple-subtitles-failures.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-02-failures.md)
- PKG-MULTI-SUB-03: [记录](baselines/multiple-subtitles-resources.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-03-resources.md)
- PKG-MULTI-SUB-04: [记录](baselines/multiple-subtitles-runtime-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-runtime-types.md)
- PKG-MULTI-SUB-04: [记录](baselines/multiple-subtitles-runtime-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-runtime-types.md) [记录](baselines/multiple-subtitles-public-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-public-types.md)
- PKG-MULTI-SUB-07: [记录](baselines/multiple-subtitles-timestamps.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md)
- PKG-ADS-01: [记录](baselines/ads-release.json) [记录](baselines/ads-contract.md) [记录](baselines/ads-contract-validation.json) [记录](changes/2026-09-12-PKG-ADS-01-contract.md) [记录](scripts/ads-contract.test.mjs)
- PKG-ADS-02: [记录](changes/2026-09-12-PKG-ADS-02-tests.md) [记录](ads-validation.md) [记录](baselines/ads-validation.json)
+19
View File
@@ -1,5 +1,24 @@
# 进度与证据
## PKG-MULTI-SUB-04 公开类型与仓库外安装检查点(doing)
保留 root/legacy 的旧必填 subtitles、同步 LegacyResult 提取与可替换工厂;新增 runtime
入口提供真实 Promise<Result> 和 tracks/reset,共享同一实现。新增可写 default 自别名、
分开的 ESM/CJS 声明、classic typesVersions 和语义生成的编辑器类型,包内文档同步。
实际旧三个 tarball 各五模式、候选 pack 七模式,共 22 格安装检查;offline/frozen 重装、
全成员字节、类型未逃逸、真实 Node 入口身份通过。旧 1.2 NodeNext ESM 失败独立登记。
公开声明每模式 12 个反例、编辑器各 2 个反例、严格运行时各 9 个反例通过;本包 274 项,
源码/main/legacy 各 39 项,三浏览器两核心三入口共 108 项通过。完整 CI 2344 项通过
(1989 单元、14 工程、341 基线),严格生产 TS 353 文件;实际 build 与 docs 副本一致。
见[变更与待决表](changes/2026-09-13-PKG-MULTI-SUB-04-public-types.md)和
[证据](baselines/multiple-subtitles-public-types.json)。没有新依赖、锁或版本修改。
旧 1.0/1.1 原始 CommonJS 模块类型提取与 1.2 default 模块声明矛盾;必填/可选 default
两种替代均有编译失败证据。JS 调用已兼容,纯旧类型差异尚未获接受,04 与 TYPE/EXPORT
风险保持 open。VTT 的相同历史声明问题仍待决;不能扩用此前 Ads 的参数类型授权。
223 项仍为 121 done、14 doing、88 todo;198 项风险。独立本地检查点,不推送、不发布。
下一步处理类型取舍及其迁移说明;实体解码、完整设备/核心/编辑器仍待 05/06。
## PKG-MULTI-SUB-07 内嵌时间戳与显示修复完成
修复数字时间戳被文本包装成 NaN;合法 VTT class 标记保留原生 processing instruction,
+12 -4
View File
@@ -4256,9 +4256,13 @@
"refactor/baselines/multiple-subtitles-release.json",
"refactor/baselines/multiple-subtitles-contract.md",
"test/multiple-subtitles.test.js",
"refactor/scripts/multiple-subtitles-contract.test.mjs"
"refactor/scripts/multiple-subtitles-contract.test.mjs",
"refactor/baselines/multiple-subtitles-public-types.json",
"refactor/changes/2026-09-13-PKG-MULTI-SUB-04-public-types.md",
"refactor/scripts/multiple-subtitles-types.test.mjs",
"refactor/scripts/multiple-subtitles-package-types.mjs"
],
"compatibleResolution": "Preserve valid old consumers while exposing accurate methods and async types; review onParser variance separately.",
"compatibleResolution": "Root/legacy retain 1.2.0 default-module extraction and replaceable synchronous factory types; additive runtime entry exposes accurate Promise and methods; writable default self alias preserves historical valid JS forms. Actual installed old 1.0.0/1.1.0 raw export-assignment extraction conflicts with 1.2.0 declarations and remains awaiting explicit resolution. Do not close on default-import success alone.",
"closureCriteria": "Strict old/new consumers and installed type views cover parameters, result methods and factory replacement."
},
{
@@ -4332,9 +4336,13 @@
"refactor/baselines/multiple-subtitles-release.json",
"refactor/baselines/multiple-subtitles-contract.md",
"test/multiple-subtitles.test.js",
"refactor/scripts/multiple-subtitles-contract.test.mjs"
"refactor/scripts/multiple-subtitles-contract.test.mjs",
"refactor/baselines/multiple-subtitles-public-types.json",
"refactor/changes/2026-09-13-PKG-MULTI-SUB-04-public-types.md",
"refactor/scripts/multiple-subtitles-types.test.mjs",
"refactor/scripts/multiple-subtitles-package-types.mjs"
],
"compatibleResolution": "Preserve valid historical runtime import forms and explicitly assess type-only extraction conflicts.",
"compatibleResolution": "Root/legacy retain 1.2.0 default-module extraction and replaceable synchronous factory types; additive runtime entry exposes accurate Promise and methods; writable default self alias preserves historical valid JS forms. Actual installed old 1.0.0/1.1.0 raw export-assignment extraction conflicts with 1.2.0 declarations and remains awaiting explicit resolution. Do not close on default-import success alone.",
"closureCriteria": "Actual installed CJS/ESM/global/legacy consumers and their declarations are verified."
},
{
@@ -0,0 +1,122 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import ts from 'typescript'
import compat from 'typescript-compat'
import { checkFiles, packedFiles } from '../../scripts/package-check.mjs'
import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '../../scripts/package-consumer.mjs'
import { verifyMultipleSubtitlesContract } from './multiple-subtitles-contract.mjs'
import { hash, readMember } from './releases.mjs'
async function main() {
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:multiple-subtitles-types-package')
const yarn = process.env.npm_execpath
assert(yarn && fs.existsSync(yarn))
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/multiple-subtitles-package-types-'))
console.log(`Multiple subtitles isolated package evidence: ${output}`)
const contract = await verifyMultipleSubtitlesContract()
const packages = []
for (const name of ['artplayer', 'artplayer-plugin-multiple-subtitles']) {
const archive = path.join(output, `${name}.tgz`)
fs.writeFileSync(path.join(output, `${name}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', name)))
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
const files = packedFiles(archive)
checkFiles(manifest, files, name === contract.baseline.release.name ? Object.keys(contract.baseline.release.files) : [])
packages.push({ name, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
}
const matrix = []
const core = packages[0]
const candidates = [
...[...contract.baseline.previous].reverse().concat(contract.baseline.release).map(release => ({ ...release, archive: contract.archives.get(release.version), label: `published-${release.version}` })),
{ ...packages[1], label: 'candidate' },
]
for (const plugin of candidates) {
const consumer = consumerDirectory()
try {
writeJson(path.join(consumer, 'package.json'), { name: 'multiple-subtitles-isolated-consumer', private: true, dependencies: Object.fromEntries([core, plugin].map(pkg => [pkg.name, `file:${pkg.archive.replaceAll('\\', '/')}`])) })
fs.copyFileSync(path.join(workspace, 'yarn.lock'), path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, `${plugin.label}-install.log`), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
const lock = fs.readFileSync(path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, `${plugin.label}-frozen-install.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--ignore-scripts', '--non-interactive'], consumer))
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
fs.writeFileSync(path.join(output, `${plugin.label}-yarn.lock`), lock)
for (const pkg of [core, plugin]) {
const root = path.join(consumer, 'node_modules', pkg.name)
assert.equal(fs.realpathSync(root), root, 'Installed package must not be a workspace link')
for (const [member, expected] of Object.entries(pkg.files))
assert.equal(hash(fs.readFileSync(path.join(root, member.slice(8)))), expected, `Installed bytes differ: ${member}`)
}
const runtimeFile = path.join(consumer, 'exports.cjs')
fs.writeFileSync(runtimeFile, plugin.label === 'candidate'
? `const assert = require('node:assert/strict');
for (const name of ['artplayer-plugin-multiple-subtitles', 'artplayer-plugin-multiple-subtitles/legacy', 'artplayer-plugin-multiple-subtitles/runtime']) { const factory = require(name); assert.equal(typeof factory, 'function'); assert.equal(factory.default, factory); assert.equal(typeof factory({}), 'function'); }
assert.equal(require('artplayer-plugin-multiple-subtitles/runtime'), require('artplayer-plugin-multiple-subtitles'));
Promise.all(['artplayer-plugin-multiple-subtitles', 'artplayer-plugin-multiple-subtitles/runtime', 'artplayer-plugin-multiple-subtitles/legacy'].map(name => import(name))).then(([root, runtime, legacy]) => { assert.deepEqual(Object.keys(root), ['default']); assert.deepEqual(Object.keys(runtime), ['default']); assert.equal(runtime.default, root.default); assert.equal(root.default.default, root.default); assert.equal(legacy.default, require('artplayer-plugin-multiple-subtitles/legacy')); assert.equal(legacy.default.default, legacy.default); }).catch(error => { console.error(error); process.exitCode = 1; });`
: `const assert = require('node:assert/strict'); const value = require('artplayer-plugin-multiple-subtitles'); assert.equal(typeof value, '${plugin.version === '1.2.0' ? 'function' : 'object'}'); assert.equal(typeof (value.default || value)({}), 'function'); ${plugin.version === '1.2.0' ? '' : 'assert.throws(() => value({}), { name: \'TypeError\', message: \'value is not a function\' });'} assert.equal(typeof value.default, '${plugin.version === '1.2.0' ? 'undefined' : 'function'}');`)
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtimeFile], consumer))
const candidateModes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
if (plugin.label === 'candidate')
candidateModes.push([ts, 'node10-commonjs-no-interop'], [compat, 'node10-commonjs-no-interop'])
const historicalModes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
for (const [compiler, mode] of (plugin.label === 'candidate' ? candidateModes : historicalModes)) {
const next = mode.startsWith('nodenext')
const extension = next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'
const source = fs.readFileSync(path.join(workspace, plugin.label === 'candidate' ? 'test/types/multiple-subtitles-public.ts' : 'refactor/fixtures/consumers/multiple-subtitles-published.ts'), 'utf8')
const filename = path.join(consumer, `consumer.${extension}`)
const historical = path.join(consumer, `historical.${extension}`)
fs.copyFileSync(path.join(workspace, 'refactor/fixtures/consumers/multiple-subtitles-published.ts'), historical)
const files = plugin.label === 'candidate' ? [filename, historical] : [filename]
if (mode === 'nodenext-cjs' && plugin.label === 'candidate') {
const commonjs = path.join(consumer, 'commonjs.cts')
fs.writeFileSync(commonjs, `import vtt = require('artplayer-plugin-multiple-subtitles'); import legacy = require('artplayer-plugin-multiple-subtitles/legacy'); import runtime = require('artplayer-plugin-multiple-subtitles/runtime'); vtt.default({subtitles: []}); legacy.default({subtitles: []}); runtime({}); runtime.default({});`)
files.push(commonjs)
}
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: !mode.endsWith('-no-interop'), target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS, moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs }
function compile(code) {
fs.writeFileSync(filename, code)
const program = compiler.createProgram(files, options)
for (const file of program.getSourceFiles()) {
const actual = fs.realpathSync(file.fileName)
assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(file) && path.dirname(actual) === fs.realpathSync(path.dirname(compiler.sys.getExecutingFilePath()))), `Types escaped installed consumer: ${actual}`)
}
return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
}
const diagnostics = compile(source)
const historicalFailure = plugin.version === '1.2.0' && mode === 'nodenext-esm'
const expected = historicalFailure ? [2322, 2344, 7019, 2349, 2344, 2322, 2344, 2349] : []
writeJson(path.join(output, `${plugin.label}-${compiler.version}-${mode}-diagnostics.json`), { diagnostics, historicalFailure })
assert.deepEqual(diagnostics.map(item => item.code), expected, `${plugin.label} ${compiler.version} ${mode}`)
const invalid = plugin.label === 'candidate' ? compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')) : []
if (plugin.label === 'candidate')
assert.equal(invalid.length, 12, 'Installed declarations must reject all invalid uses')
const commonjs = {}
if (mode === 'node10-commonjs' || mode === 'nodenext-cjs') {
const fixture = fs.readFileSync(path.join(workspace, 'refactor/fixtures/consumers/multiple-subtitles-commonjs.ts'), 'utf8')
const oldAssignment = plugin.label !== 'candidate' && plugin.version !== '1.2.0'
for (const form of ['direct', 'default']) {
const consumer = form === 'direct' ? fixture : fixture.replace('const vtt = plugin', 'const vtt = plugin.default')
const diagnostics = compile(consumer)
const expected = form === 'default'
? oldAssignment ? [2339] : []
: oldAssignment ? [] : compiler === compat ? [2344, 2322, 2344, 2741, 2344, 2349] : [2322, 2344, 2344, 2322, 2344, 2349]
writeJson(path.join(output, `${plugin.label}-${compiler.version}-${mode}-require-${form}.json`), { diagnostics, expected })
assert.deepEqual(diagnostics.map(item => item.code), expected, `${plugin.label} ${compiler.version} ${mode} require ${form}`)
commonjs[form] = { diagnostics, typeChecks: diagnostics.length === 0 }
}
}
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, historicalFailure, diagnostics, invalid, commonjs })
}
}
finally {
removeConsumer(consumer)
}
}
writeJson(path.join(output, 'report.json'), { suite: 'multiple-subtitles-isolated-package-types', introducedBy: 'PKG-MULTI-SUB-04', scope: 'Three actual published packages in five compiler modes and packed candidate in seven modes, installed outside the workspace with packed core. Offline frozen reinstalls and byte identity verified. Expected old NodeNext ESM and CommonJS module extraction failures remain explicit; these do not count as candidate compatibility success. Raw historical export-assignment type compatibility remains unresolved. Full device and distribution acceptance remains separate.', packages, published: candidates.slice(0, 3).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: hash(fs.readFileSync(pkg.archive)), missingEntrypoints: pkg.missingEntrypoints })), matrix })
console.log(`Multiple subtitles installed matrix verified: ${matrix.length} cases, including ${matrix.filter(item => item.historicalFailure).length} expected historical declaration failure; ${output}`)
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
@@ -0,0 +1,89 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
// eslint-disable-next-line test/no-import-node-test -- Public declaration compatibility runner.
import test from 'node:test'
import { ESLint } from 'eslint'
import ts from 'typescript'
import compat from 'typescript-compat'
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../../scripts/plugin-editor-types.mjs'
import { checkConsumer } from '../../scripts/typecheck.mjs'
import { verifyMultipleSubtitlesContract } from './multiple-subtitles-contract.mjs'
import { readMember } from './releases.mjs'
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
test('Multiple subtitles editor globals keep old extraction and expose explicit asynchronous types', async () => {
const source = fs.readFileSync('packages/artplayer-plugin-multiple-subtitles/types/artplayer-plugin-multiple-subtitles.d.ts', 'utf8')
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginMultipleSubtitles')
const file = 'docs/assets/ts/artplayer-plugin-multiple-subtitles.d.ts'
const [formatted] = await new ESLint({ fix: true, fixTypes: ['layout'] }).lintText(generated, { filePath: file })
assert.equal(formatted.errorCount, 0)
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), formatted.output || generated)
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
const consumer = `declare const art: Artplayer;
const option: artplayerPluginMultipleSubtitles.Option = { subtitles: [{name: 'en'}] };
const old: artplayerPluginMultipleSubtitles.LegacyResult = artplayerPluginMultipleSubtitles(option)(art);
declare const runtime: artplayerPluginMultipleSubtitles.RuntimeFactory;
const pending: Promise<artplayerPluginMultipleSubtitles.Result> = runtime({})(art);
pending.then(result => { result.tracks(['en']); result.reset(); });
// @ts-expect-error Historical extraction remains synchronous.
const changed: Promise<artplayerPluginMultipleSubtitles.Result> = old;
// @ts-expect-error URL is text.
artplayerPluginMultipleSubtitles({ subtitles: [{ url: 1 }] });`
for (const compiler of [ts, compat]) {
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 2)
}
})
test('Multiple subtitles public extraction stays exact in five modes while runtime methods need no cast', () => {
const source = fs.readFileSync('test/types/multiple-subtitles-public.ts', 'utf8')
for (const [compiler, mode] of modes) {
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
assert.equal(checkConsumer(compiler, mode, source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')).length, 12)
}
})
test('Multiple subtitles runtime CommonJS supports direct and default calls', () => {
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import runtime = require('artplayer-plugin-multiple-subtitles/runtime');
import legacy = require('artplayer-plugin-multiple-subtitles');
import type Artplayer from 'artplayer'; declare const art: Artplayer;
const first = runtime({})(art); const second = runtime.default({})(art);
const promise: Promise<{name: 'multipleSubtitles', tracks(names?: string[]): void, reset(): void}> = first;
legacy.default({subtitles: []})(art).name; void [promise, second];`), [])
})
test('Multiple subtitles raw callable module alternatives retain the required-default versus replacement conflict', () => {
const source = fs.readFileSync('refactor/fixtures/consumers/multiple-subtitles-export-alternatives.ts', 'utf8')
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [compat, 'node10-commonjs']]) {
assert.deepEqual(checkConsumer(compiler, mode, source).map(item => ({ line: item.line, code: item.code })), [{ line: 4, code: 2322 }, { line: 6, code: 2722 }])
}
})
test('Multiple subtitles actual three historical declarations retain default-import and replacement consumers', async () => {
const contract = await verifyMultipleSubtitlesContract()
const consumer = fs.readFileSync('refactor/fixtures/consumers/multiple-subtitles-published.ts', 'utf8')
for (const release of [contract.baseline.release, ...contract.baseline.previous]) {
const old = readMember(contract.archives.get(release.version), `package/${release.manifest.types.replace(/^\.\//, '')}`).toString()
for (const compiler of [ts, compat]) {
const filename = path.resolve('test/types/multiple-subtitles-historical-consumer.ts')
const declaration = path.resolve('test/types/multiple-subtitles-historical.d.ts')
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'] }
function historical(source) {
const host = compiler.createCompilerHost(options)
const read = host.getSourceFile.bind(host)
host.getSourceFile = (file, language, ...rest) => path.resolve(file) === filename
? compiler.createSourceFile(file, source.replace('from \'artplayer-plugin-multiple-subtitles\'', 'from \'./multiple-subtitles-historical\''), language, true)
: path.resolve(file) === declaration ? compiler.createSourceFile(file, old, language, true) : read(file, language, ...rest)
const exists = host.fileExists.bind(host)
host.fileExists = file => path.resolve(file) === declaration || exists(file)
return compiler.getPreEmitDiagnostics(compiler.createProgram([filename, declaration], options, host)).map(error => error.code)
}
assert.deepEqual(historical(consumer), [])
assert.deepEqual(historical(`${consumer}\nsubtitles();`), [2554])
assert.deepEqual(historical(`${consumer}\nconst absent: Parameters<typeof subtitles>[0] = undefined;`), [2322])
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', consumer), [])
}
}
})
+3 -1
View File
@@ -2325,7 +2325,9 @@
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
"evidence": [
"baselines/multiple-subtitles-runtime-types.json",
"changes/2026-09-13-PKG-MULTI-SUB-04-runtime-types.md"
"changes/2026-09-13-PKG-MULTI-SUB-04-runtime-types.md",
"baselines/multiple-subtitles-public-types.json",
"changes/2026-09-13-PKG-MULTI-SUB-04-public-types.md"
]
},
{
+1 -1
View File
@@ -53,7 +53,7 @@ console.log(`✨ Built ${artplayerTSoutput}`);
const type = pluginsTS[index]
const { name, file } = parsePluginInfo(type)
const source = String(fs.readFileSync(type))
const semanticPlugin = ['artplayerPluginVttThumbnail', 'artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerProxyMediabunny', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe', 'ArtplayerToolThumbnail'].includes(name)
const semanticPlugin = ['artplayerPluginMultipleSubtitles', 'artplayerPluginVttThumbnail', 'artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerProxyMediabunny', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe', 'ArtplayerToolThumbnail'].includes(name)
const localTypes = name === 'artplayerProxyMediabunny' ? { './media': fs.readFileSync(path.join(path.dirname(type), 'media.d.ts'), 'utf8') } : {}
const code = semanticPlugin
? generatePluginEditorDeclaration(source, name, localTypes)
+23
View File
@@ -24,6 +24,29 @@ function empty(env) {
for (const listeners of env.listeners.values()) assert.equal(listeners.size, 0)
}
for (const script of [false, true]) {
test(`Multiple subtitles candidate: ${script ? 'script global' : 'CommonJS'} self alias preserves identity, registration and writable metadata`, async () => {
const env = create({ script })
assert.equal(typeof env.exported, 'function')
assert.equal(env.exported.default, env.exported)
assert.equal(env.factory.default.default, env.factory)
const descriptor = Object.getOwnPropertyDescriptor(env.factory, 'default')
assert.equal(descriptor.writable, true)
assert.equal(descriptor.configurable, true)
assert.equal(descriptor.enumerable, true)
const pending = env.factory.default({})(env.art)
assert.equal(typeof pending.then, 'function')
const result = await pending
assert.equal(result.name, 'multipleSubtitles')
assert.equal(result.tracks(), undefined)
assert.equal(result.reset(), undefined)
env.factory.default = () => 'replacement'
assert.equal(env.factory.default(), 'replacement')
env.emit('destroy')
empty(env)
})
}
for (const abortController of [true, false]) {
for (const phase of ['fetch', 'body']) {
test(`Multiple subtitles candidate: destroy cancels ${phase}, AbortController=${abortController}`, async () => {
+55
View File
@@ -0,0 +1,55 @@
import type { Factory, LegacyResult, Option, Result, RuntimeFactory, TrackOption } from 'artplayer-plugin-multiple-subtitles'
import Artplayer from 'artplayer'
import legacy from 'artplayer-plugin-multiple-subtitles'
import legacyEntry from 'artplayer-plugin-multiple-subtitles/legacy'
import runtime from 'artplayer-plugin-multiple-subtitles/runtime'
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false
type Assert<T extends true> = T
type ExactOptions = Assert<Equal<Parameters<typeof legacy>, [option: Option]>>
type ExactFactory = Assert<Equal<typeof legacy, Factory>>
type ExactResult = Assert<Equal<ReturnType<ReturnType<typeof legacy>>, LegacyResult>>
type ActualResult = Assert<Equal<ReturnType<ReturnType<typeof runtime>>, Promise<Result>>>
type OldTrack = Assert<Equal<TrackOption, { url?: string, name?: string, type?: 'vtt' | 'srt' | 'ass', encoding?: string, onParser?: (...args: object[]) => object }>>
const option: Option = { subtitles: [{ url: '/en.vtt', name: 'en', onParser: (...args) => args }] }
const art = new Artplayer({ container: '#player', url: '/video.mp4', plugins: [legacy(option), runtime(option)] })
const old: LegacyResult = legacy(option)(art)
const replacement: typeof legacy = (_option: Option) => (_art: Artplayer) => ({ name: 'multipleSubtitles' })
legacyEntry(option)
const factory: RuntimeFactory = runtime
const pending: Promise<Result> = factory({})(art)
const alias: Promise<Result> = factory.default.default(option)(art)
factory.default = runtime
pending.then((result) => {
const returned: void = result.tracks(['en'])
result.tracks()
result.reset()
// @ts-expect-error Selection names are strings.
result.tracks([1])
void returned
})
// @ts-expect-error Historical subtitles field remains required.
legacy({})
// @ts-expect-error Published extraction keeps the option object required.
const absent: Parameters<typeof legacy>[0] = undefined
// @ts-expect-error Root declarations preserve historical synchronous extraction.
const changed: Promise<Result> = old
// @ts-expect-error Registration has no synchronous name.
void pending.name
// @ts-expect-error Factory options remain required.
runtime()
// @ts-expect-error Factory options cannot be undefined.
runtime(undefined)
// @ts-expect-error URL remains a string.
runtime({ subtitles: [{ url: 1 }] })
// @ts-expect-error Invalid subtitle format remains rejected.
runtime({ subtitles: [{ type: 'xml' }] })
// @ts-expect-error Unknown options remain rejected.
runtime({ unknown: true })
// @ts-expect-error A host is required.
runtime({})()
// @ts-expect-error Accurate runtime types cannot accept a synchronous registrar.
const wrong: typeof runtime = replacement
void [alias, absent, changed, wrong]
export type { ActualResult, ExactFactory, ExactOptions, ExactResult, OldTrack }