refactor(multiple-subtitles): [PKG-MULTI-SUB-04] type runtime modules and vendor boundaries

This commit is contained in:
Harvey Zhao committed 2026-09-13 11:21:06 +08:00
1 parent beaa93d6cc
commit da64aae267
28 files changed
+7075 -86

No files matched your search

@@ -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. * 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],g=m.length,h="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(g<b||m.indexOf(h)!==0+v||g>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 x;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;x=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(x.id=l[o],/^NOTE($|[ \t])/.test(x.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(x.id)){var y=[],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),y.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(y.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 O=new n(l[o],p),A=0;if(u.length>0&&(A=u[u.length-1].startTime),!T||O.parse(x,A)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=x.text&&(x.text+="\n"),x.text+=l[o],o++}var L=new i(x.text,p,a,t);x.tree=L.parse(x.startTime,x.endTime),u.push(x)}else for(x=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],g=m.length,h="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(g<b||m.indexOf(h)!==0+v||g>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 x;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;x=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(x.id=l[o],/^NOTE($|[ \t])/.test(x.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(x.id)){var y=[],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),y.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(y.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 O=new n(l[o],p),A=0;if(u.length>0&&(A=u[u.length-1].startTime),!T||O.parse(x,A)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=x.text&&(x.text+="\n"),x.text+=l[o],o++}var L=new i(x.text,p,a,t);x.tree=L.parse(x.startTime,x.endTime),u.push(x)}else for(x=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. * 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())});var 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],g=m.length,h="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(g<b||m.indexOf(h)!==0+v||g>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=[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())});var 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],g=m.length,h="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(g<b||m.indexOf(h)!==0+v||g>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=[Line truncated
@@ -853,14 +853,13 @@ function parseTracks(vtts, subtitles) {
const parser = new WebVTTParser(); const parser = new WebVTTParser();
return vtts.map((vtt, index) => { return vtts.map((vtt, index) => {
const tree = parser.parse(vtt, "metadata"); const tree = parser.parse(vtt, "metadata");
tree.url = subtitles[index].url; return { ...tree, url: subtitles[index].url, name: subtitles[index].name };
tree.name = subtitles[index].name;
return tree;
}); });
} }
function serializeTracks(trees) { function serializeTracks(trees) {
const cues = []; const cues = [];
for (const tree of trees) { for (const selected of trees) {
const tree = selected;
for (const cue of tree.cues) { for (const cue of tree.cues) {
cues.push({ cues.push({
...cue, ...cue,
+1 -1
View File
@@ -88,7 +88,7 @@
"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: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": "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: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" "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"
}, },
"browserslist": "last 1 Chrome version", "browserslist": "last 1 Chrome version",
"devDependencies": { "devDependencies": {
@@ -2,4 +2,5 @@ node_modules
.DS_Store .DS_Store
yarn.lock yarn.lock
package-lock.json package-lock.json
src src
tsconfig.json
@@ -8,15 +8,20 @@ task PKG-MULTI-SUB-04 owns that compatibility work. Do not infer runtime behavio
## Current module boundaries ## Current module boundaries
- `src/index.js` composes registration, named selection and the unchanged public result. - `src/index.ts` composes registration, named selection and the unchanged public result.
- `src/request.js` owns each fetch through byte decoding and delegates SRT/ASS conversion - `src/request.ts` owns each fetch through byte decoding and delegates SRT/ASS conversion
to the existing core utils. Failure aborts the request; registration failure closes siblings. to the existing core utils. Failure aborts the request; registration failure closes siblings.
- `src/merge.js` parses metadata and serializes selected cues without mutating parsed trees. - `src/merge.ts` parses metadata and serializes selected cues without mutating parsed trees.
It depends only on the vendor parser, not the player or browser resource APIs. It depends only on the vendor parser, not the player or browser resource APIs.
- `src/lifetime.js` owns the destroy listener, request cancellation and resource callbacks. - `src/lifetime.ts` owns the destroy listener, request cancellation and resource callbacks.
Its cancellation Promise also settles pending operations without AbortController support. Its cancellation Promise also settles pending operations without AbortController support.
- `src/render.js` owns generated Blob URLs and synchronous/async host installation outcomes. - `src/render.ts` owns generated Blob URLs and synchronous/async host installation outcomes.
Reentrant selections and stale host rejections cannot release the latest selected resource. Reentrant selections and stale host rejections cannot release the latest selected resource.
- `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/parser.d.ts` describes the vendored parse result and discriminated cue nodes. Parsed
numeric timestamps are distinct from the historically wrapped string nodes accepted by the
serializer. That wrapping produces invalid inline times; PKG-MULTI-SUB-07 owns its repair.
- `src/parser.js` is vendored WebVTT parser/serializer code with a CC0 dedication. It is not - `src/parser.js` is vendored WebVTT parser/serializer code with a CC0 dedication. It is not
owned plugin code to mechanically migrate to TypeScript. Keep it distinct from task 04. owned plugin code to mechanically migrate to TypeScript. Keep it distinct from task 04.
The pinned upstream reference and full adaptation check are in The pinned upstream reference and full adaptation check are in
@@ -77,5 +82,9 @@ Candidate lifecycle and merge suites cover cancellation, failures, resource owne
reentrancy and immutable serialization. The candidate browser suite adds real video source reentrancy and immutable serialization. The candidate browser suite adds real video source
switching, HTTP rejection and Blob revocation. For main/legacy validation, set the artifact switching, HTTP rejection and Blob revocation. For main/legacy validation, set the artifact
environment variable above before running it; source builds are used when it is absent. environment variable above before running it; source builds are used when it is absent.
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.
Tasks 05/06 still own complete core/device combinations and demo acceptance. Tasks 05/06 still own complete core/device combinations and demo acceptance.
See `../../refactor/baselines/multiple-subtitles-contract.md` for exact historical differences. See `../../refactor/baselines/multiple-subtitles-contract.md` for exact historical differences.
@@ -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. * 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],g=m.length,h="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(g<b||m.indexOf(h)!==0+v||g>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 x;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;x=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(x.id=l[o],/^NOTE($|[ \t])/.test(x.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(x.id)){var y=[],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),y.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(y.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 O=new n(l[o],p),A=0;if(u.length>0&&(A=u[u.length-1].startTime),!T||O.parse(x,A)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=x.text&&(x.text+="\n"),x.text+=l[o],o++}var L=new i(x.text,p,a,t);x.tree=L.parse(x.startTime,x.endTime),u.push(x)}else for(x=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],g=m.length,h="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(g<b||m.indexOf(h)!==0+v||g>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 x;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;x=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(x.id=l[o],/^NOTE($|[ \t])/.test(x.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(x.id)){var y=[],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),y.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(y.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 O=new n(l[o],p),A=0;if(u.length>0&&(A=u[u.length-1].startTime),!T||O.parse(x,A)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=x.text&&(x.text+="\n"),x.text+=l[o],o++}var L=new i(x.text,p,a,t);x.tree=L.parse(x.startTime,x.endTime),u.push(x)}else for(x=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. * 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())});var 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],g=m.length,h="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(g<b||m.indexOf(h)!==0+v||g>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=[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())});var 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],g=m.length,h="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(g<b||m.indexOf(h)!==0+v||g>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=[Line truncated
@@ -853,14 +853,13 @@ function parseTracks(vtts, subtitles) {
const parser = new WebVTTParser(); const parser = new WebVTTParser();
return vtts.map((vtt, index) => { return vtts.map((vtt, index) => {
const tree = parser.parse(vtt, "metadata"); const tree = parser.parse(vtt, "metadata");
tree.url = subtitles[index].url; return { ...tree, url: subtitles[index].url, name: subtitles[index].name };
tree.name = subtitles[index].name;
return tree;
}); });
} }
function serializeTracks(trees) { function serializeTracks(trees) {
const cues = []; const cues = [];
for (const tree of trees) { for (const selected of trees) {
const tree = selected;
for (const cue of tree.cues) { for (const cue of tree.cues) {
cues.push({ cues.push({
...cue, ...cue,
@@ -1,19 +1,21 @@
import type Artplayer from 'artplayer'
import type { Option, Result, Track } from './types'
import createLifetime from './lifetime' import createLifetime from './lifetime'
import { parseTracks, serializeTracks } from './merge' import { parseTracks, serializeTracks } from './merge'
import createRenderer from './render' import createRenderer from './render'
import loadVtt from './request' import loadVtt from './request'
export default function artplayerPluginMultipleSubtitles({ subtitles = [] }) { export default function artplayerPluginMultipleSubtitles({ subtitles = [] }: Option) {
return async (art) => { return async (art: Artplayer): Promise<Result> => {
const { unescape, getExt, srtToVtt, assToVtt } = art.constructor.utils const { unescape, getExt, srtToVtt, assToVtt } = (art.constructor as typeof Artplayer).utils
const lifetime = createLifetime(art) const lifetime = createLifetime(art)
const render = createRenderer(art, lifetime, unescape) const render = createRenderer(art, lifetime, unescape)
let trees = [] let trees: Track[] = []
function setTracks(selected) { function setTracks(selected: readonly (Track | undefined)[]): void {
if (!lifetime.closed) if (!lifetime.closed)
render(serializeTracks(selected)) render(serializeTracks(selected))
} }
const result = { const result: Result = {
name: 'multipleSubtitles', name: 'multipleSubtitles',
tracks(names = []) { tracks(names = []) {
if (!lifetime.closed) if (!lifetime.closed)
@@ -29,7 +31,8 @@ export default function artplayerPluginMultipleSubtitles({ subtitles = [] }) {
const vtts = await Promise.all(subtitles.map(option => loadVtt(option, { getExt, srtToVtt, assToVtt }, lifetime))) const vtts = await Promise.all(subtitles.map(option => loadVtt(option, { getExt, srtToVtt, assToVtt }, lifetime)))
if (lifetime.closed) if (lifetime.closed)
return result return result
trees = parseTracks(vtts, subtitles) // A live completed request yields text; cancellation was checked above.
trees = parseTracks(vtts as string[], subtitles)
setTracks(trees) setTracks(trees)
return result return result
} }
@@ -1,11 +1,13 @@
export default function createLifetime(art) { import type { Cleanup, Lifetime, LifetimeHost } from './types'
export default function createLifetime(art: LifetimeHost): Lifetime {
let closed = Boolean(art.isDestroy) let closed = Boolean(art.isDestroy)
const cleanups = new Set() const cleanups = new Set<Cleanup>()
let cancel let cancel!: Cleanup
const cancelled = new Promise((resolve) => { const cancelled = new Promise<void>((resolve) => {
cancel = resolve cancel = resolve
}) })
function run(cleanup) { function run(cleanup: Cleanup): void {
try { try {
cleanup() cleanup()
} }
@@ -13,7 +15,7 @@ export default function createLifetime(art) {
console.warn('Failed to clean up multiple subtitles:', error) console.warn('Failed to clean up multiple subtitles:', error)
} }
} }
const lifetime = { const lifetime: Lifetime = {
get closed() { return closed }, get closed() { return closed },
own(cleanup) { own(cleanup) {
if (closed) if (closed)
@@ -1,30 +0,0 @@
import { WebVTTParser, WebVTTSerializer } from './parser'
export function parseTracks(vtts, subtitles) {
const parser = new WebVTTParser()
return vtts.map((vtt, index) => {
const tree = parser.parse(vtt, 'metadata')
tree.url = subtitles[index].url
tree.name = subtitles[index].name
return tree
})
}
export function serializeTracks(trees) {
const cues = []
for (const tree of trees) {
for (const cue of tree.cues) {
cues.push({
...cue,
tree: {
...cue.tree,
children: cue.tree.children.map(child => ({
...child,
value: `<div class="art-subtitle-${tree.name}">${child.value}</div>`,
})),
},
})
}
}
return new WebVTTSerializer().serialize(cues)
}
@@ -0,0 +1,33 @@
import type { Cue, WrappedNode } from './parser'
import type { Track, TrackOption } from './types'
import { WebVTTParser, WebVTTSerializer } from './parser'
export function parseTracks(vtts: readonly string[], subtitles: readonly TrackOption[]): Track[] {
const parser = new WebVTTParser()
return vtts.map((vtt, index) => {
const tree = parser.parse(vtt, 'metadata')
// Options and completed downloads share the same Promise.all index.
return { ...tree, url: subtitles[index]!.url, name: subtitles[index]!.name }
})
}
export function serializeTracks(trees: readonly (Track | undefined)[]): string {
const cues: Cue<WrappedNode>[] = []
for (const selected of trees) {
// Unknown names retain the historical TypeError at the property access below.
const tree = selected!
for (const cue of tree.cues) {
cues.push({
...cue,
tree: {
...cue.tree,
children: cue.tree.children.map(child => ({
...child,
value: `<div class="art-subtitle-${tree.name}">${child.value}</div>`,
})),
},
})
}
}
return new WebVTTSerializer().serialize(cues)
}
@@ -0,0 +1,56 @@
export interface TextNode {
type: 'text'
value: string
}
export interface ObjectNode {
type: 'object'
name: string
classes: string[]
value?: string
children: CueNode[]
}
export interface TimestampNode {
type: 'timestamp'
value: number
}
export type CueNode = TextNode | ObjectNode | TimestampNode
// The historical plugin wraps every top-level value, including timestamp nodes.
export type WrappedNode = TextNode | (Omit<ObjectNode, 'value'> & { value: string }) | { type: 'timestamp', value: string }
export interface Cue<Node = CueNode> {
id: string
startTime: number
endTime: number
pauseOnExit: boolean
direction: 'horizontal' | 'rl' | 'lr'
snapToLines: boolean
linePosition: number | 'auto'
lineAlign: 'start' | 'center' | 'end'
textPosition: number | 'auto'
positionAlign: 'auto' | 'line-left' | 'center' | 'line-right'
size: number
alignment: 'start' | 'center' | 'end' | 'left' | 'right'
text: string
tree: { children: Node[] }
nonSerializable?: boolean
}
export interface ParseResult {
cues: Cue[]
errors: { message: string, line: number, col: number | undefined }[]
styles: string[]
time: number
}
// Private declaration for the vendored exports used by this package, not a new public API.
export declare class WebVTTParser {
constructor(entities?: Record<string, string>)
parse(input: string, mode: 'metadata'): ParseResult
}
export declare class WebVTTSerializer {
serialize(cues: readonly Cue<CueNode | WrappedNode>[], styles?: readonly string[]): string
}
@@ -1,9 +1,12 @@
export default function createRenderer(art, lifetime, unescape) { import type { Subtitle, Utils } from 'artplayer'
let current = null import type { Lifetime, OwnedURL, RenderHost } from './types'
function own(url) {
export default function createRenderer(art: RenderHost, lifetime: Lifetime, unescape: Utils['unescape']): (vtt: string) => void {
let current: OwnedURL | null = null
function own(url: string): OwnedURL {
let freed = false let freed = false
let release = () => {} let release: () => unknown = () => {}
const entry = { const entry: OwnedURL = {
free() { free() {
if (freed) if (freed)
return return
@@ -24,12 +27,13 @@ export default function createRenderer(art, lifetime, unescape) {
if (lifetime.closed) if (lifetime.closed)
return return
const previous = current const previous = current
let option let option: Subtitle | undefined
let escape let escape: boolean | undefined
let configured = false let configured = false
current = entry current = entry
try { try {
option = art.option.subtitle // ArtPlayer merges subtitle defaults; preserve errors for invalid custom hosts.
option = art.option.subtitle!
escape = option.escape escape = option.escape
if (lifetime.closed || current !== entry) if (lifetime.closed || current !== entry)
return return
@@ -52,8 +56,8 @@ export default function createRenderer(art, lifetime, unescape) {
catch (error) { catch (error) {
if (current === entry) { if (current === entry) {
current = previous current = previous
if (configured && option.escape === false) if (configured && option!.escape === false)
option.escape = escape option!.escape = escape
} }
entry.free() entry.free()
throw error throw error
@@ -1,4 +1,6 @@
export default async function loadVtt(option, { getExt, srtToVtt, assToVtt }, lifetime) { import type { Converters, Lifetime, TrackOption } from './types'
export default async function loadVtt(option: TrackOption, { getExt, srtToVtt, assToVtt }: Converters, lifetime: Lifetime): Promise<string | void> {
if (lifetime.closed) if (lifetime.closed)
return return
const controller = typeof AbortController === 'function' ? new AbortController() : null const controller = typeof AbortController === 'function' ? new AbortController() : null
@@ -7,16 +9,17 @@ export default async function loadVtt(option, { getExt, srtToVtt, assToVtt }, li
const url = option.url const url = option.url
if (lifetime.closed) if (lifetime.closed)
return return
const response = await lifetime.wait(controller ? fetch(url, { signal: controller.signal }) : fetch(url)) // Keep native fetch coercion for historically optional URLs; do not add a default URL.
const response = await lifetime.wait(controller ? fetch(url!, { signal: controller.signal }) : fetch(url!))
if (lifetime.closed) if (lifetime.closed)
return return
if (response.ok === false) if (response!.ok === false)
throw new Error(`Failed to fetch multiple subtitles: HTTP ${response.status}`) throw new Error(`Failed to fetch multiple subtitles: HTTP ${response!.status}`)
const buffer = await lifetime.wait(response.arrayBuffer()) const buffer = await lifetime.wait(response!.arrayBuffer())
if (lifetime.closed) if (lifetime.closed)
return return
const text = new TextDecoder(option.encoding || 'utf-8').decode(buffer) const text = new TextDecoder(option.encoding || 'utf-8').decode(buffer!)
switch (option.type || getExt(option.url)) { switch (option.type || getExt(option.url!)) {
case 'srt': return srtToVtt(text) case 'srt': return srtToVtt(text)
case 'ass': return assToVtt(text) case 'ass': return assToVtt(text)
case 'vtt': return text case 'vtt': return text
@@ -0,0 +1,35 @@
import type Artplayer from 'artplayer'
import type { Subtitle, Utils } from 'artplayer'
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 interface Track extends ParseResult {
url: string | undefined
name: string | undefined
}
export type Converters = Pick<Utils, 'getExt' | 'srtToVtt' | 'assToVtt'>
export type Cleanup = () => void
export interface Lifetime {
readonly closed: boolean
own: (cleanup: Cleanup) => () => boolean
wait: <Value>(value: Value | PromiseLike<Value>) => Promise<Value | void>
dispose: Cleanup
}
export type LifetimeHost = Pick<Artplayer, 'isDestroy' | 'on' | 'off'>
export interface RenderHost {
option: { subtitle?: Subtitle }
subtitle: { init: (option: Subtitle) => unknown }
}
export interface OwnedURL {
free: Cleanup
}
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"allowJs": false
},
"include": [
"src/**/*.ts",
"types/**/*.d.ts",
"../../types/assets.d.ts",
"../../test/types/multiple-subtitles-runtime.ts"
]
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,45 @@
# PKG-MULTI-SUB-04 严格运行时 TS 检查点
五个自有 JS 模块迁为 TS,新增 types.ts 描述真实 Promise<Result>、轨道、生命周期、
converter 和宿主安装边界。parser.js 原始 vendor 执行体保持;parser.d.ts 区分文本、
标签和数字时间戳节点,描述 cue settings、错误/样式结果及序列化输入。
分包 tsconfig 使用 strict/noUncheckedIndexedAccess/allowJs=false,既有浏览器目标不变。
## 兼容与断言
- 内部 TrackOption 复用旧公开配置字段,内部 Option 允许运行时已有的 `{}`;工厂参数
本身仍必需。真实返回 Promise,tracks/reset 明确 void。
- 公开 types、导出路径和版本尚未改动。旧同步声明与真实异步结果的冲突保留为编译负例;
04 未完成,后续必须验证旧提取/替换、模块形式及安装消费者再处理公开入口。
- `(art.constructor as typeof Artplayer)` 只恢复构造器静态类型;可选 URL 的 `!` 保留
原生 fetch 的旧值转换,不偷偷加默认 URL;关闭状态检查后的 `!`/string[] 对应取消边界。
- Promise.all 索引与选项索引配对。未知名称仍在 cue 属性读取时抛 TypeError,不能因
类型严格化过滤掉无效项。宿主默认 subtitle 的断言也不新增运行时默认。
- parseTracks 返回拥有 url/name 的明确新对象,其 cues/errors/styles 保持相同引用;
没有改变 cue 输出、选择顺序和生命周期策略。
## 类型检查发现的独立缺陷
旧包装逻辑把所有顶层节点的 value 都改成 div 字符串,包括应该保留数字的时间戳。
实际已发布 1.2.0 和本次候选对 `Before <00:02.000> after` 都输出 `<NaN:NaN.NaN>`。
九份历史输入新增复现,全部确认同一缺陷;原有 02 验证是当时 162 项,本次历史失败
文件扩为 171 项,不改写旧报告数字。实际探针输入/输出写入本次验证记录。
新增 PKG-MULTI-SUB-07 专门修复并验证内嵌时间戳的序列化,作为 05 的前置;风险保持
open。本次 WrappedNode 明确表达这个历史错误,不把类型通过解释为字幕内容已正确。
下一步先处理 07,再继续 04 的公开声明;修复需同时更新旧/新差异和原生显示证据。
## 验证与发布边界
TS 5.9.3/5.1.6 严格正例通过,移除 expect-error 后各 8 个错误;注册返回、选择名称、
格式、只读关闭状态、数字时间戳、序列化返回和旧同步替换均有明确约束。
源码/main/legacy 的既有 31 项正常/资源/转换测试分别重跑;三入口原生五场景各 30 项。
vendor 没有机械 TS 改写,不计为已迁移生产 TS。实际结果与限制见
[验证记录](../baselines/multiple-subtitles-runtime-types.json)。
正常构建更新三份 dist 及 docs/compiled。新增 tsconfig 加入 .npmignore,并用实际
yarn pack 验证自有源码、私有 parser 声明和 tsconfig 未泄漏;公开声明/入口原样保留。
最终本包 252 项、完整 CI 2322 项通过(1972 单元、14 工程、336 基线),严格生产
TS 增至 352 文件。三入口各 31 项及原生共 90 项通过,状态更新后另有 9 项元数据
检查通过。新增源文件/声明/fixture/runner 显式 lint 通过;全量 lint 保留既有生成声明警告。
无新依赖,未推送或发布。本次为 04 的独立本地检查点提交,任务继续 doing。
+7 -5
View File
@@ -2,9 +2,9 @@
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。 > 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 223 项,范围 22 个包及工作区/示例。
状态:todo 89 / doing 13 / blocked 0 / done 120 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 状态:todo 89 / doing 14 / blocked 0 / done 120 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -28,7 +28,7 @@
| artplayer-plugin-document-pip | 1.1.0 | PKG-DPIP-01, PKG-DPIP-02, PKG-DPIP-03, PKG-DPIP-04, PKG-DPIP-05, PKG-DPIP-06 | | artplayer-plugin-document-pip | 1.1.0 | PKG-DPIP-01, PKG-DPIP-02, PKG-DPIP-03, PKG-DPIP-04, PKG-DPIP-05, PKG-DPIP-06 |
| artplayer-plugin-hls-control | 1.1.0 | PKG-HLS-01, PKG-HLS-02, PKG-HLS-03, PKG-HLS-04, PKG-HLS-SDK-01, PKG-HLS-05, PKG-HLS-06 | | artplayer-plugin-hls-control | 1.1.0 | PKG-HLS-01, PKG-HLS-02, PKG-HLS-03, PKG-HLS-04, PKG-HLS-SDK-01, PKG-HLS-05, PKG-HLS-06 |
| artplayer-plugin-jassub | 1.1.0 | PKG-JASSUB-01, PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-04, PKG-JASSUB-05, PKG-JASSUB-06 | | artplayer-plugin-jassub | 1.1.0 | PKG-JASSUB-01, PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-04, PKG-JASSUB-05, PKG-JASSUB-06 |
| artplayer-plugin-multiple-subtitles | 1.2.0 | PKG-MULTI-SUB-01, PKG-MULTI-SUB-02, PKG-MULTI-SUB-03, PKG-MULTI-SUB-04, PKG-MULTI-SUB-05, PKG-MULTI-SUB-06 | | artplayer-plugin-multiple-subtitles | 1.2.0 | PKG-MULTI-SUB-01, PKG-MULTI-SUB-02, PKG-MULTI-SUB-03, PKG-MULTI-SUB-04, PKG-MULTI-SUB-07, PKG-MULTI-SUB-05, PKG-MULTI-SUB-06 |
| artplayer-plugin-vast | 1.2.0 | PKG-VAST-01, PKG-VAST-02, PKG-VAST-03, PKG-VAST-04, PKG-VAST-05, PKG-VAST-06 | | artplayer-plugin-vast | 1.2.0 | PKG-VAST-01, PKG-VAST-02, PKG-VAST-03, PKG-VAST-04, PKG-VAST-05, PKG-VAST-06 |
| artplayer-plugin-vtt-thumbnail | 1.1.0 | PKG-VTT-THUMB-01, PKG-VTT-THUMB-02, PKG-VTT-THUMB-03, PKG-VTT-THUMB-04, PKG-VTT-THUMB-05, PKG-VTT-THUMB-06 | | artplayer-plugin-vtt-thumbnail | 1.1.0 | PKG-VTT-THUMB-01, PKG-VTT-THUMB-02, PKG-VTT-THUMB-03, PKG-VTT-THUMB-04, PKG-VTT-THUMB-05, PKG-VTT-THUMB-06 |
| artplayer-proxy-canvas | 1.1.0 | PKG-AMBILIGHT-PROXY-01, PKG-CANVAS-01, PKG-CANVAS-02, PKG-CANVAS-03, PKG-CANVAS-04, PKG-CANVAS-05, PKG-CANVAS-06, PKG-FACTORY-01 | | artplayer-proxy-canvas | 1.1.0 | PKG-AMBILIGHT-PROXY-01, PKG-CANVAS-01, PKG-CANVAS-02, PKG-CANVAS-03, PKG-CANVAS-04, PKG-CANVAS-05, PKG-CANVAS-06, PKG-FACTORY-01 |
@@ -219,8 +219,9 @@
| PKG-MULTI-SUB-01 | artplayer-plugin-multiple-subtitles<br>核对包契约与历史用法 | BASE-05 | subtitles/onParser、multipleSubtitles 名称及实际返回方法 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 VENDOR-03,定位 WebVTT parser 来源/修订及 CC0 头,保留解析/序列化行为 | H | done | | PKG-MULTI-SUB-01 | artplayer-plugin-multiple-subtitles<br>核对包契约与历史用法 | BASE-05 | subtitles/onParser、multipleSubtitles 名称及实际返回方法 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 VENDOR-03,定位 WebVTT parser 来源/修订及 CC0 头,保留解析/序列化行为 | H | done |
| PKG-MULTI-SUB-02 | artplayer-plugin-multiple-subtitles<br>建立特有行为与错误测试 | PKG-MULTI-SUB-01, ENG-03, ENG-05 | VTT/SRT/ASS、编码/重叠 cue/排序/空轨/失败与切换 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done | | PKG-MULTI-SUB-02 | artplayer-plugin-multiple-subtitles<br>建立特有行为与错误测试 | PKG-MULTI-SUB-01, ENG-03, ENG-05 | VTT/SRT/ASS、编码/重叠 cue/排序/空轨/失败与切换 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-MULTI-SUB-03 | artplayer-plugin-multiple-subtitles<br>整理内部职责与资源 | PKG-MULTI-SUB-02, CORE-15 | 解析与合并/获取/渲染分离,过期请求及对象 URL 清理 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done | | PKG-MULTI-SUB-03 | artplayer-plugin-multiple-subtitles<br>整理内部职责与资源 | PKG-MULTI-SUB-02, CORE-15 | 解析与合并/获取/渲染分离,过期请求及对象 URL 清理 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
| PKG-MULTI-SUB-04 | artplayer-plugin-multiple-subtitles<br>迁移自有源码和公开类型 | PKG-MULTI-SUB-03, ENG-04, ENG-06, CORE-07 | 准确 parser/cue/tree/result 类型与真实异步形状 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo | | PKG-MULTI-SUB-04 | artplayer-plugin-multiple-subtitles<br>迁移自有源码和公开类型 | PKG-MULTI-SUB-03, ENG-04, ENG-06, CORE-07 | 准确 parser/cue/tree/result 类型与真实异步形状 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | doing |
| PKG-MULTI-SUB-05 | artplayer-plugin-multiple-subtitles<br>验证新旧核心和组合 | PKG-MULTI-SUB-04, CORE-22 | 与核心字幕/偏移/全屏组合,旧名称调用不变 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | | PKG-MULTI-SUB-07 | artplayer-plugin-multiple-subtitles<br>修复字幕内嵌时间戳序列化 | PKG-MULTI-SUB-03 | 保留数字时间戳节点、修正无效 NaN 输出、明确旧/新字幕输出差异 | 旧九份实现可复现,候选有效内嵌时间戳及文字/CSS/原生字幕通过,公开工厂与选择接口不变 | H | todo |
| PKG-MULTI-SUB-05 | artplayer-plugin-multiple-subtitles<br>验证新旧核心和组合 | PKG-MULTI-SUB-04, CORE-22, PKG-MULTI-SUB-07 | 与核心字幕/偏移/全屏组合,旧名称调用不变 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
| PKG-MULTI-SUB-06 | artplayer-plugin-multiple-subtitles<br>验证分发并同步文档 | PKG-MULTI-SUB-05, ENG-07 | multiple.subtitles.js、parser 来源许可及打包边界 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo | | PKG-MULTI-SUB-06 | artplayer-plugin-multiple-subtitles<br>验证分发并同步文档 | PKG-MULTI-SUB-05, ENG-07 | multiple.subtitles.js、parser 来源许可及打包边界 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
## 5 包迁移:artplayer-plugin-jassub ## 5 包迁移:artplayer-plugin-jassub
@@ -514,6 +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-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-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-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-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-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) - PKG-ADS-02: [记录](changes/2026-09-12-PKG-ADS-02-tests.md) [记录](ads-validation.md) [记录](baselines/ads-validation.json)
- PKG-ADS-03: [记录](changes/2026-09-12-PKG-ADS-03-lifecycle.md) [记录](ads-validation.md) [记录](baselines/ads-lifecycle-validation.json) - PKG-ADS-03: [记录](changes/2026-09-12-PKG-ADS-03-lifecycle.md) [记录](ads-validation.md) [记录](baselines/ads-lifecycle-validation.json)
+15
View File
@@ -1,5 +1,20 @@
# 进度与证据 # 进度与证据
## PKG-MULTI-SUB-04 严格运行时 TS 检查点(doing)
五个自有模块迁为 TS,新增明确的 Option/Promise Result/Track/host/lifetime 类型;
parser.js 保持原 vendor,私有 parser.d.ts 区分 cue 与节点,分包 allowJs=false。
TS 5.9.3/5.1.6 严格正例通过,移除标记后各 8 个负例准确报错;公开旧声明未改。
源码/main/legacy 各 31 项通过,三入口原生各 30 项共 90 项通过;实际构建/ESM 导入通过。
实际 yarn pack 检查入口、成员及产物字节,私有源码、parser 声明和 tsconfig 未泄漏。
本包 252 项通过,完整 CI 2322 项通过(1972 单元、14 工程、336 基线),
严格生产 TS 增至 352 文件。见[检查点](changes/2026-09-13-PKG-MULTI-SUB-04-runtime-types.md)
和[验证](baselines/multiple-subtitles-runtime-types.json)。公开异步/旧类型及安装消费者仍待 04。
类型梳理复现旧/候选内嵌时间戳输出 NaN;九份历史实现均复现,新建 TIMESTAMP 风险与
PKG-MULTI-SUB-07,并将 07 加入 05 前置。此问题未修复,下一步先做 07,再继续公开类型。
223 项:120 done、14 doing、89 todo;197 项风险。独立本地检查点提交,不推送、不发布。
## PKG-MULTI-SUB-03 模块与资源修复完成 ## PKG-MULTI-SUB-03 模块与资源修复完成
生产源码拆为入口、请求、纯解析合并、生命周期和宿主安装五个模块,vendor parser 不变。 生产源码拆为入口、请求、纯解析合并、生命周期和宿主安装五个模块,vendor parser 不变。
+1
View File
@@ -200,3 +200,4 @@
| MULTI-SUB-EXPORT-01 | open / 已复现 | Older CommonJS default objects conflict with export-assignment declarations; latest exports a direct factory | PKG-MULTI-SUB-04, PKG-MULTI-SUB-06 | | MULTI-SUB-EXPORT-01 | open / 已复现 | Older CommonJS default objects conflict with export-assignment declarations; latest exports a direct factory | PKG-MULTI-SUB-04, PKG-MULTI-SUB-06 |
| MULTI-SUB-HTTP-01 | resolved / 已复现 | HTTP error responses with parseable bodies are installed as subtitles | PKG-MULTI-SUB-03, PKG-MULTI-SUB-05 | | MULTI-SUB-HTTP-01 | resolved / 已复现 | HTTP error responses with parseable bodies are installed as subtitles | PKG-MULTI-SUB-03, PKG-MULTI-SUB-05 |
| MULTI-SUB-HOST-01 | resolved / 已复现 | Subtitle init rejections are ignored and failed installation or URL replacement leaks or invalidates resources | PKG-MULTI-SUB-03, PKG-MULTI-SUB-05 | | MULTI-SUB-HOST-01 | resolved / 已复现 | Subtitle init rejections are ignored and failed installation or URL replacement leaks or invalidates resources | PKG-MULTI-SUB-03, PKG-MULTI-SUB-05 |
| MULTI-SUB-TIMESTAMP-01 | open / 已复现 | Text wrappers replace numeric inline cue timestamps and serialize NaN times | PKG-MULTI-SUB-07, PKG-MULTI-SUB-05 |
+17
View File
@@ -4398,6 +4398,23 @@
"test/browser/multiple-subtitles-lifecycle.spec.js" "test/browser/multiple-subtitles-lifecycle.spec.js"
], ],
"resolutionRationale": "Owned installations retain prior URLs on synchronous failure, observe async rejections, release the failed active URL without affecting newer installs, and clean up on destroy. Controlled sync/async/reentrant/getter failures and native Blob revocation pass; void methods and registration timing are preserved." "resolutionRationale": "Owned installations retain prior URLs on synchronous failure, observe async rejections, release the failed active URL without affecting newer installs, and clean up on destroy. Controlled sync/async/reentrant/getter failures and native Blob revocation pass; void methods and registration timing are preserved."
},
{
"id": "MULTI-SUB-TIMESTAMP-01",
"title": "Text wrappers replace numeric inline cue timestamps and serialize NaN times",
"confirmation": "reproduced",
"status": "open",
"owners": [
"PKG-MULTI-SUB-07",
"PKG-MULTI-SUB-05"
],
"evidence": [
"refactor/baselines/multiple-subtitles-runtime-types.json",
"refactor/changes/2026-09-13-PKG-MULTI-SUB-04-runtime-types.md",
"test/multiple-subtitles-failures.test.js"
],
"compatibleResolution": "Preserve numeric timestamp nodes while retaining subtitle text, track wrappers and selection contracts; record the intentional repair of invalid legacy output.",
"closureCriteria": "Valid inline timestamps and surrounding text survive serialization and native subtitle playback; frozen historical failure remains reproducible and ordinary markup regressions pass."
} }
] ]
} }
@@ -0,0 +1,37 @@
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 -- Internal runtime compiler checks use the repository runner.
import test from 'node:test'
import ts from 'typescript'
import runtimeCompat from 'typescript-runtime-compat'
for (const compiler of [ts, runtimeCompat]) {
test(`Multiple subtitles strict runtime modules and negative consumers: TS ${compiler.version}`, () => {
const file = path.resolve('packages/artplayer-plugin-multiple-subtitles/tsconfig.json')
const config = compiler.readConfigFile(file, compiler.sys.readFile)
assert.equal(config.error, undefined)
const parsed = compiler.parseJsonConfigFileContent(config.config, compiler.sys, path.dirname(file))
assert.equal(parsed.options.strict, true)
assert.equal(parsed.options.allowJs, false)
assert.equal(parsed.options.skipLibCheck, false)
assert.equal(parsed.options.noEmit, true)
assert.deepEqual(parsed.options.types, [])
const fixture = path.resolve('test/types/multiple-subtitles-runtime.ts')
assert(parsed.fileNames.some(file => path.resolve(file) === fixture))
function diagnostics(negative) {
const host = compiler.createCompilerHost(parsed.options)
const read = host.readFile.bind(host)
host.readFile = file => path.resolve(file) === fixture && negative
? fs.readFileSync(fixture, 'utf8').replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')
: read(file)
const program = compiler.createProgram(parsed.fileNames, parsed.options, host)
assert(program.getSourceFile(fixture))
assert(!program.getSourceFiles().some(file => /multiple-subtitles\/src\/.*\.js$/.test(file.fileName.replaceAll('\\', '/'))), 'Vendored JS must be represented by its explicit declaration')
return compiler.getPreEmitDiagnostics(program).map(error => ({ code: error.code, text: compiler.flattenDiagnosticMessageText(error.messageText, '\n') }))
}
assert.deepEqual(diagnostics(false), [])
const rejected = diagnostics(true)
assert.equal(rejected.length, 8, JSON.stringify(rejected))
})
}
+22 -2
View File
@@ -2319,10 +2319,29 @@
"ENG-06", "ENG-06",
"CORE-07" "CORE-07"
], ],
"status": "todo", "status": "doing",
"risk": "H", "risk": "H",
"deliverable": "准确 parser/cue/tree/result 类型与真实异步形状", "deliverable": "准确 parser/cue/tree/result 类型与真实异步形状",
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容", "acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
"evidence": [
"baselines/multiple-subtitles-runtime-types.json",
"changes/2026-09-13-PKG-MULTI-SUB-04-runtime-types.md"
]
},
{
"id": "PKG-MULTI-SUB-07",
"phase": "5 包迁移:artplayer-plugin-multiple-subtitles",
"title": "修复字幕内嵌时间戳序列化",
"scope": [
"artplayer-plugin-multiple-subtitles"
],
"dependsOn": [
"PKG-MULTI-SUB-03"
],
"status": "todo",
"risk": "H",
"deliverable": "保留数字时间戳节点、修正无效 NaN 输出、明确旧/新字幕输出差异",
"acceptance": "旧九份实现可复现,候选有效内嵌时间戳及文字/CSS/原生字幕通过,公开工厂与选择接口不变",
"evidence": [] "evidence": []
}, },
{ {
@@ -2334,7 +2353,8 @@
], ],
"dependsOn": [ "dependsOn": [
"PKG-MULTI-SUB-04", "PKG-MULTI-SUB-04",
"CORE-22" "CORE-22",
"PKG-MULTI-SUB-07"
], ],
"status": "todo", "status": "todo",
"risk": "H", "risk": "H",
+6
View File
@@ -257,4 +257,10 @@ for (const implementation of implementations) {
assert(env.utils.unescape(await env.latestText()).includes('art-subtitle-renamed')) assert(env.utils.unescape(await env.latestText()).includes('art-subtitle-renamed'))
assert.deepEqual(env.requests, ['a.vtt']) assert.deepEqual(env.requests, ['a.vtt'])
}) })
test(`Multiple-subtitles ${implementation.name} failure baseline: inline timestamp nodes become invalid serialized times`, async () => {
const env = create({ responses: { 'a.vtt': 'WEBVTT\n\n00:00.000 --> 00:08.000\nBefore <00:02.000> after\n' } })
await env.factory(options(subtitle()))(env.art)
assert((await env.latestText()).includes('<NaN:NaN.NaN>'))
})
} }
+65
View File
@@ -0,0 +1,65 @@
import type Artplayer from 'artplayer'
import type { CueNode, TimestampNode } from '../../packages/artplayer-plugin-multiple-subtitles/src/parser'
import type { Option, Result } from '../../packages/artplayer-plugin-multiple-subtitles/src/types'
import factory from '../../packages/artplayer-plugin-multiple-subtitles/src/index'
import createLifetime from '../../packages/artplayer-plugin-multiple-subtitles/src/lifetime'
import { parseTracks, serializeTracks } from '../../packages/artplayer-plugin-multiple-subtitles/src/merge'
import { WebVTTParser } from '../../packages/artplayer-plugin-multiple-subtitles/src/parser'
import loadVtt from '../../packages/artplayer-plugin-multiple-subtitles/src/request'
import legacyFactory from '../../packages/artplayer-plugin-multiple-subtitles/types/artplayer-plugin-multiple-subtitles'
declare const art: Artplayer
const option: Option = { subtitles: [{ url: '/a.vtt', name: 'a', type: 'vtt' }] }
const register = factory(option)
const pending: Promise<Result> = register(art)
pending.then((result) => {
result.name satisfies 'multipleSubtitles'
const returned: void = result.tracks(['a'])
result.reset()
void returned
// @ts-expect-error Selection names are strings.
result.tracks([1])
})
factory({})(art)
factory({ subtitles: [{}] })(art)
// @ts-expect-error Factory options remain required.
factory()
// @ts-expect-error Registration is asynchronous, with no synchronous name.
void pending.name
// @ts-expect-error Invalid subtitle format is rejected.
factory({ subtitles: [{ type: 'xml' }] })
const lifetime = createLifetime(art)
const waited: Promise<number | void> = lifetime.wait(Promise.resolve(3))
const text: Promise<string | void> = loadVtt({}, (art.constructor as typeof Artplayer).utils, lifetime)
void waited
void text
// @ts-expect-error Lifetime state cannot be assigned.
lifetime.closed = false
const parser = new WebVTTParser()
const parsed = parser.parse('WEBVTT\n\n', 'metadata')
parsed.errors.forEach(error => error.message.toUpperCase())
const nodes: CueNode[] = parsed.cues[0]!.tree.children
for (const node of nodes) {
if (node.type === 'timestamp')
node.value.toFixed()
if (node.type === 'text')
node.value.toUpperCase()
}
const timestamp: TimestampNode = { type: 'timestamp', value: 2 }
void timestamp
// @ts-expect-error Parsed timestamps are numbers, not wrapped serialized values.
const wrongTimestamp: TimestampNode = { type: 'timestamp', value: '2' }
void wrongTimestamp
const trees = parseTracks(['WEBVTT\n\n'], [{}])
const vtt: string = serializeTracks(trees)
void vtt
// @ts-expect-error The pure serializer returns text.
const number: number = serializeTracks(trees)
void number
legacyFactory({ subtitles: [] })(art).name satisfies 'multipleSubtitles'
// @ts-expect-error Historical synchronous return extraction cannot be silently replaced.
const oldRegistration: ReturnType<typeof legacyFactory> = register
void oldRegistration