mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
fix(multiple-subtitles): [PKG-MULTI-SUB-07] preserve inline timestamps and caption text
This commit is contained in:
1 parent
da64aae267
commit
7f226ff822
24 files changed
+8660
-132
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.
|
||||
*/
|
||||
!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={"&":"&","<":"<",">":">","&lrm":"\u200e","&rlm":"\u200f"," ":"\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={"&":"&","<":"<",">":">","&lrm":"\u200e","&rlm":"\u200f"," ":"\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())});var f={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},u=function(e){e||(e={"&":"&","<":"<",">":">","&lrm":"\u200e","&rlm":"\u200f"," ":"\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())}),f={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},u=function(e){e||(e={"&":"&","<":"<",">":">","&lrm":"\u200e","&rlm":"\u200f"," ":"\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
|
||||
@@ -55,56 +55,6 @@
|
||||
*
|
||||
* 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 createLifetime(art) {
|
||||
let closed = Boolean(art.isDestroy);
|
||||
const cleanups = /* @__PURE__ */ new Set();
|
||||
let cancel;
|
||||
const cancelled = new Promise((resolve) => {
|
||||
cancel = resolve;
|
||||
});
|
||||
function run(cleanup) {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (error) {
|
||||
console.warn("Failed to clean up multiple subtitles:", error);
|
||||
}
|
||||
}
|
||||
const lifetime = {
|
||||
get closed() {
|
||||
return closed;
|
||||
},
|
||||
own(cleanup) {
|
||||
if (closed)
|
||||
run(cleanup);
|
||||
else
|
||||
cleanups.add(cleanup);
|
||||
return () => cleanups.delete(cleanup);
|
||||
},
|
||||
wait(value) {
|
||||
return Promise.race([value, cancelled]);
|
||||
},
|
||||
dispose() {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
cancel();
|
||||
const pending = [...cleanups].reverse();
|
||||
cleanups.clear();
|
||||
for (const cleanup of pending)
|
||||
run(cleanup);
|
||||
}
|
||||
};
|
||||
if (!closed) {
|
||||
lifetime.own(() => art.off("destroy", lifetime.dispose));
|
||||
try {
|
||||
art.on("destroy", lifetime.dispose);
|
||||
} catch (error) {
|
||||
lifetime.dispose();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return lifetime;
|
||||
}
|
||||
var defaultCueSettings = {
|
||||
direction: "horizontal",
|
||||
snapToLines: true,
|
||||
@@ -849,6 +799,17 @@ var WebVTTSerializer = function() {
|
||||
return result;
|
||||
};
|
||||
};
|
||||
const TIMESTAMP_CLASS = "artplayer-multiple-subtitles-timestamp";
|
||||
function markTimestamp(node) {
|
||||
return { type: "object", name: "c", classes: [TIMESTAMP_CLASS], children: [node] };
|
||||
}
|
||||
function markNestedTimestamps(node) {
|
||||
if (node.type === "timestamp")
|
||||
return markTimestamp(node);
|
||||
if (node.type === "object")
|
||||
return { ...node, children: node.children.map(markNestedTimestamps) };
|
||||
return node;
|
||||
}
|
||||
function parseTracks(vtts, subtitles) {
|
||||
const parser = new WebVTTParser();
|
||||
return vtts.map((vtt, index) => {
|
||||
@@ -865,16 +826,84 @@ function serializeTracks(trees) {
|
||||
...cue,
|
||||
tree: {
|
||||
...cue.tree,
|
||||
children: cue.tree.children.map((child) => ({
|
||||
...child,
|
||||
value: `<div class="art-subtitle-${tree.name}">${child.value}</div>`
|
||||
}))
|
||||
children: cue.tree.children.map((child) => {
|
||||
if (child.type === "timestamp")
|
||||
return markTimestamp(child);
|
||||
const value = `<div class="art-subtitle-${tree.name}">${child.value}</div>`;
|
||||
return child.type === "object" ? { ...child, value, children: child.children.map(markNestedTimestamps) } : { ...child, value };
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return new WebVTTSerializer().serialize(cues);
|
||||
}
|
||||
function installCaptionView(art, lifetime) {
|
||||
function update() {
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
for (const marker of Array.from(art.template.$subtitle.getElementsByTagName(`c.${TIMESTAMP_CLASS}`))) {
|
||||
const first = marker.firstChild;
|
||||
if (first?.nodeType === 3)
|
||||
first.nodeValue = (first.nodeValue || "").replace(/^<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/, "");
|
||||
marker.replaceWith(...Array.from(marker.childNodes));
|
||||
}
|
||||
}
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
lifetime.own(() => art.off("subtitleAfterUpdate", update));
|
||||
art.on("subtitleAfterUpdate", update);
|
||||
}
|
||||
function createLifetime(art) {
|
||||
let closed = Boolean(art.isDestroy);
|
||||
const cleanups = /* @__PURE__ */ new Set();
|
||||
let cancel;
|
||||
const cancelled = new Promise((resolve) => {
|
||||
cancel = resolve;
|
||||
});
|
||||
function run(cleanup) {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (error) {
|
||||
console.warn("Failed to clean up multiple subtitles:", error);
|
||||
}
|
||||
}
|
||||
const lifetime = {
|
||||
get closed() {
|
||||
return closed;
|
||||
},
|
||||
own(cleanup) {
|
||||
if (closed)
|
||||
run(cleanup);
|
||||
else
|
||||
cleanups.add(cleanup);
|
||||
return () => cleanups.delete(cleanup);
|
||||
},
|
||||
wait(value) {
|
||||
return Promise.race([value, cancelled]);
|
||||
},
|
||||
dispose() {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
cancel();
|
||||
const pending = [...cleanups].reverse();
|
||||
cleanups.clear();
|
||||
for (const cleanup of pending)
|
||||
run(cleanup);
|
||||
}
|
||||
};
|
||||
if (!closed) {
|
||||
lifetime.own(() => art.off("destroy", lifetime.dispose));
|
||||
try {
|
||||
art.on("destroy", lifetime.dispose);
|
||||
} catch (error) {
|
||||
lifetime.dispose();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return lifetime;
|
||||
}
|
||||
function createRenderer(art, lifetime, unescape) {
|
||||
let current = null;
|
||||
function own(url) {
|
||||
@@ -1002,6 +1031,7 @@ function artplayerPluginMultipleSubtitles({ subtitles = [] }) {
|
||||
if (lifetime.closed)
|
||||
return result;
|
||||
trees = parseTracks(vtts, subtitles);
|
||||
installCaptionView(art, lifetime);
|
||||
setTracks(trees);
|
||||
return result;
|
||||
} catch (error) {
|
||||
|
||||
@@ -17,11 +17,12 @@ task PKG-MULTI-SUB-04 owns that compatibility work. Do not infer runtime behavio
|
||||
Its cancellation Promise also settles pending operations without AbortController support.
|
||||
- `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.
|
||||
- `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/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.
|
||||
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
|
||||
owned plugin code to mechanically migrate to TypeScript. Keep it distinct from task 04.
|
||||
The pinned upstream reference and full adaptation check are in
|
||||
@@ -52,6 +53,18 @@ The historical failure suite deliberately retains the old outcomes; the candidat
|
||||
suite verifies their fixes. Preserve parser best-effort behavior independently: invalid-header
|
||||
diagnostics can coexist with usable cues, and metadata is intentionally read after download.
|
||||
|
||||
Inline timestamp nodes keep their numeric values. The merge layer puts them in valid VTT
|
||||
class spans (`c.artplayer-multiple-subtitles-timestamp`), including nested timestamps. Native
|
||||
getCueAsHTML retains the processing instructions. ArtPlayer's HTML layer does not interpret
|
||||
those tags, so caption.ts removes only the leading instruction within the generated marker
|
||||
and unwraps its remaining children. HTML does not pair `<c.class>` with `</c>`; removing the
|
||||
whole element would incorrectly discard following caption text. The original cue stays intact.
|
||||
This keeps the existing whole-cue display behavior; it does not add karaoke highlighting.
|
||||
|
||||
The vendor entity decoder still leaves extra semicolons after standard named entities. Nine
|
||||
historical implementations reproduce it; MULTI-SUB-ENTITY-01 remains open for task 05. Do not
|
||||
silently alter vendor source or declare entity rendering correct because timestamp tests pass.
|
||||
|
||||
## Vendor boundary and validation
|
||||
|
||||
The comparison revision is w3c/webvtt.js `380cfcce34ba8b472d3a31474874eb72a0e5f460`.
|
||||
|
||||
+1
-1
@@ -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={"&":"&","<":"<",">":">","&lrm":"\u200e","&rlm":"\u200f"," ":"\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={"&":"&","<":"<",">":">","&lrm":"\u200e","&rlm":"\u200f"," ":"\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
|
||||
Vendored
+1
-1
@@ -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())});var f={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},u=function(e){e||(e={"&":"&","<":"<",">":">","&lrm":"\u200e","&rlm":"\u200f"," ":"\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())}),f={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},u=function(e){e||(e={"&":"&","<":"<",">":">","&lrm":"\u200e","&rlm":"\u200f"," ":"\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
|
||||
+84
-54
@@ -55,56 +55,6 @@
|
||||
*
|
||||
* 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 createLifetime(art) {
|
||||
let closed = Boolean(art.isDestroy);
|
||||
const cleanups = /* @__PURE__ */ new Set();
|
||||
let cancel;
|
||||
const cancelled = new Promise((resolve) => {
|
||||
cancel = resolve;
|
||||
});
|
||||
function run(cleanup) {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (error) {
|
||||
console.warn("Failed to clean up multiple subtitles:", error);
|
||||
}
|
||||
}
|
||||
const lifetime = {
|
||||
get closed() {
|
||||
return closed;
|
||||
},
|
||||
own(cleanup) {
|
||||
if (closed)
|
||||
run(cleanup);
|
||||
else
|
||||
cleanups.add(cleanup);
|
||||
return () => cleanups.delete(cleanup);
|
||||
},
|
||||
wait(value) {
|
||||
return Promise.race([value, cancelled]);
|
||||
},
|
||||
dispose() {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
cancel();
|
||||
const pending = [...cleanups].reverse();
|
||||
cleanups.clear();
|
||||
for (const cleanup of pending)
|
||||
run(cleanup);
|
||||
}
|
||||
};
|
||||
if (!closed) {
|
||||
lifetime.own(() => art.off("destroy", lifetime.dispose));
|
||||
try {
|
||||
art.on("destroy", lifetime.dispose);
|
||||
} catch (error) {
|
||||
lifetime.dispose();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return lifetime;
|
||||
}
|
||||
var defaultCueSettings = {
|
||||
direction: "horizontal",
|
||||
snapToLines: true,
|
||||
@@ -849,6 +799,17 @@ var WebVTTSerializer = function() {
|
||||
return result;
|
||||
};
|
||||
};
|
||||
const TIMESTAMP_CLASS = "artplayer-multiple-subtitles-timestamp";
|
||||
function markTimestamp(node) {
|
||||
return { type: "object", name: "c", classes: [TIMESTAMP_CLASS], children: [node] };
|
||||
}
|
||||
function markNestedTimestamps(node) {
|
||||
if (node.type === "timestamp")
|
||||
return markTimestamp(node);
|
||||
if (node.type === "object")
|
||||
return { ...node, children: node.children.map(markNestedTimestamps) };
|
||||
return node;
|
||||
}
|
||||
function parseTracks(vtts, subtitles) {
|
||||
const parser = new WebVTTParser();
|
||||
return vtts.map((vtt, index) => {
|
||||
@@ -865,16 +826,84 @@ function serializeTracks(trees) {
|
||||
...cue,
|
||||
tree: {
|
||||
...cue.tree,
|
||||
children: cue.tree.children.map((child) => ({
|
||||
...child,
|
||||
value: `<div class="art-subtitle-${tree.name}">${child.value}</div>`
|
||||
}))
|
||||
children: cue.tree.children.map((child) => {
|
||||
if (child.type === "timestamp")
|
||||
return markTimestamp(child);
|
||||
const value = `<div class="art-subtitle-${tree.name}">${child.value}</div>`;
|
||||
return child.type === "object" ? { ...child, value, children: child.children.map(markNestedTimestamps) } : { ...child, value };
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return new WebVTTSerializer().serialize(cues);
|
||||
}
|
||||
function installCaptionView(art, lifetime) {
|
||||
function update() {
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
for (const marker of Array.from(art.template.$subtitle.getElementsByTagName(`c.${TIMESTAMP_CLASS}`))) {
|
||||
const first = marker.firstChild;
|
||||
if (first?.nodeType === 3)
|
||||
first.nodeValue = (first.nodeValue || "").replace(/^<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/, "");
|
||||
marker.replaceWith(...Array.from(marker.childNodes));
|
||||
}
|
||||
}
|
||||
if (lifetime.closed)
|
||||
return;
|
||||
lifetime.own(() => art.off("subtitleAfterUpdate", update));
|
||||
art.on("subtitleAfterUpdate", update);
|
||||
}
|
||||
function createLifetime(art) {
|
||||
let closed = Boolean(art.isDestroy);
|
||||
const cleanups = /* @__PURE__ */ new Set();
|
||||
let cancel;
|
||||
const cancelled = new Promise((resolve) => {
|
||||
cancel = resolve;
|
||||
});
|
||||
function run(cleanup) {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (error) {
|
||||
console.warn("Failed to clean up multiple subtitles:", error);
|
||||
}
|
||||
}
|
||||
const lifetime = {
|
||||
get closed() {
|
||||
return closed;
|
||||
},
|
||||
own(cleanup) {
|
||||
if (closed)
|
||||
run(cleanup);
|
||||
else
|
||||
cleanups.add(cleanup);
|
||||
return () => cleanups.delete(cleanup);
|
||||
},
|
||||
wait(value) {
|
||||
return Promise.race([value, cancelled]);
|
||||
},
|
||||
dispose() {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
cancel();
|
||||
const pending = [...cleanups].reverse();
|
||||
cleanups.clear();
|
||||
for (const cleanup of pending)
|
||||
run(cleanup);
|
||||
}
|
||||
};
|
||||
if (!closed) {
|
||||
lifetime.own(() => art.off("destroy", lifetime.dispose));
|
||||
try {
|
||||
art.on("destroy", lifetime.dispose);
|
||||
} catch (error) {
|
||||
lifetime.dispose();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return lifetime;
|
||||
}
|
||||
function createRenderer(art, lifetime, unescape) {
|
||||
let current = null;
|
||||
function own(url) {
|
||||
@@ -1002,6 +1031,7 @@ function artplayerPluginMultipleSubtitles({ subtitles = [] }) {
|
||||
if (lifetime.closed)
|
||||
return result;
|
||||
trees = parseTracks(vtts, subtitles);
|
||||
installCaptionView(art, lifetime);
|
||||
setTracks(trees);
|
||||
return result;
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Lifetime } from './types'
|
||||
import { TIMESTAMP_CLASS } from './merge'
|
||||
|
||||
export default function installCaptionView(art: Pick<Artplayer, 'on' | 'off' | 'template'>, lifetime: Lifetime): void {
|
||||
function update(): void {
|
||||
if (lifetime.closed)
|
||||
return
|
||||
// HTML does not pair <c.class> with </c>; unwrap while preserving following caption nodes.
|
||||
for (const marker of Array.from(art.template.$subtitle.getElementsByTagName(`c.${TIMESTAMP_CLASS}`))) {
|
||||
const first = marker.firstChild
|
||||
if (first?.nodeType === 3)
|
||||
first.nodeValue = (first.nodeValue || '').replace(/^<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/, '')
|
||||
marker.replaceWith(...Array.from(marker.childNodes))
|
||||
}
|
||||
}
|
||||
if (lifetime.closed)
|
||||
return
|
||||
lifetime.own(() => art.off('subtitleAfterUpdate', update))
|
||||
art.on('subtitleAfterUpdate', update)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Option, Result, Track } from './types'
|
||||
import installCaptionView from './caption'
|
||||
import createLifetime from './lifetime'
|
||||
import { parseTracks, serializeTracks } from './merge'
|
||||
import createRenderer from './render'
|
||||
@@ -33,6 +34,7 @@ export default function artplayerPluginMultipleSubtitles({ subtitles = [] }: Opt
|
||||
return result
|
||||
// A live completed request yields text; cancellation was checked above.
|
||||
trees = parseTracks(vtts as string[], subtitles)
|
||||
installCaptionView(art, lifetime)
|
||||
setTracks(trees)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
import type { Cue, WrappedNode } from './parser'
|
||||
import type { Cue, CueNode, TimestampNode, WrappedNode } from './parser'
|
||||
import type { Track, TrackOption } from './types'
|
||||
import { WebVTTParser, WebVTTSerializer } from './parser'
|
||||
|
||||
export const TIMESTAMP_CLASS = 'artplayer-multiple-subtitles-timestamp'
|
||||
|
||||
function markTimestamp(node: TimestampNode): CueNode {
|
||||
return { type: 'object', name: 'c', classes: [TIMESTAMP_CLASS], children: [node] }
|
||||
}
|
||||
|
||||
function markNestedTimestamps(node: CueNode): CueNode {
|
||||
if (node.type === 'timestamp')
|
||||
return markTimestamp(node)
|
||||
if (node.type === 'object')
|
||||
return { ...node, children: node.children.map(markNestedTimestamps) }
|
||||
return node
|
||||
}
|
||||
|
||||
export function parseTracks(vtts: readonly string[], subtitles: readonly TrackOption[]): Track[] {
|
||||
const parser = new WebVTTParser()
|
||||
return vtts.map((vtt, index) => {
|
||||
@@ -21,10 +35,14 @@ export function serializeTracks(trees: readonly (Track | undefined)[]): string {
|
||||
...cue,
|
||||
tree: {
|
||||
...cue.tree,
|
||||
children: cue.tree.children.map(child => ({
|
||||
...child,
|
||||
value: `<div class="art-subtitle-${tree.name}">${child.value}</div>`,
|
||||
})),
|
||||
children: cue.tree.children.map((child) => {
|
||||
if (child.type === 'timestamp')
|
||||
return markTimestamp(child)
|
||||
const value = `<div class="art-subtitle-${tree.name}">${child.value}</div>`
|
||||
return child.type === 'object'
|
||||
? { ...child, value, children: child.children.map(markNestedTimestamps) }
|
||||
: { ...child, value }
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@ export interface TimestampNode {
|
||||
}
|
||||
|
||||
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 type WrappedNode = CueNode
|
||||
|
||||
export interface Cue<Node = CueNode> {
|
||||
id: string
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,49 @@
|
||||
# PKG-MULTI-SUB-07 内嵌时间戳与显示层修复
|
||||
|
||||
九份历史实现已复现 `<00:02.000>` 被包装成字符串并输出 `<NaN:NaN.NaN>`。
|
||||
本次保留数字 TimestampNode,处理顶层与嵌套节点,在有效的 VTT class 节点中承载
|
||||
时间戳。解析树仍不可变,重复选择/reset 不积累包装;文本、标签和轨序的原回归保留。
|
||||
私有 WrappedNode 现在与 CueNode 一样要求数字时间戳,编译负例从 8 项增为 9 项。
|
||||
|
||||
## 原生与自定义字幕层
|
||||
|
||||
直接输出有效时间戳后,Chromium 中的发布/候选核心都把 `<00:02.000>` 显示成文字。
|
||||
这不是测试超时或资源加载失败,最初两个失败断言及截图/trace 已保留。
|
||||
因此新增 caption.ts,在既有 subtitleAfterUpdate 事件后处理插件生成的专用标记。
|
||||
|
||||
- 原生 WebVTT 中的 `<c.artplayer-multiple-subtitles-timestamp>` 是合法 class 节点,
|
||||
其中的数字时间戳仍解析为 processing instruction,cue.text 不被显示层修改。
|
||||
- HTML 不会把 `<c.class>` 与 `</c>` 配对,后面的字幕可能成为该元素的子节点。
|
||||
直接删除标记曾导致后文丢失;已通过真实浏览器复现并修正为移除前导时间指令、
|
||||
将其余子节点移回父层。对应失败报告保留,最终断言检查前后文字。
|
||||
- 不对整段文字做时间戳正则删除。仅处理专用标记中的前导指令,普通用户文字和
|
||||
原有 `.art-subtitle-<name>` CSS 钩子保留。public cue.text 修改产生的字面量标签
|
||||
有真实浏览器回归,清空/reset 后正常恢复原字幕。
|
||||
- 注册的监听器由当前 lifetime 管理,失败/销毁时移除;已有资源测试改为检查所有
|
||||
监听器集合为空。没有增加新公开选项、返回值或事件,不改变逐 cue 显示策略。
|
||||
|
||||
## 验证范围
|
||||
|
||||
纯测试覆盖开始/中间/末尾、多个毫秒时间戳、Unicode、嵌套标签、冻结树、重复选择
|
||||
和重新解析有效 VTT;历史时间戳失败仍保持,不将候选修复覆盖进旧预期。
|
||||
原生场景检查开头/中间/末尾三个 processing instruction、前后文字、红色样式、
|
||||
字面量标签保留、清空/reset,以及原有 SRT、销毁/HTTP/Blob/视频切源场景。
|
||||
通过源码/main/legacy 与发布/候选核心,在 Chromium/Firefox/Windows WebKit 重跑。
|
||||
具体结果、编译器和输入指纹见[验证](../baselines/multiple-subtitles-timestamps.json)。
|
||||
|
||||
最终三入口各 37 项回归通过,本包 267 项通过;原生各 36 项,共 108 项通过,
|
||||
原生 processing instruction 精确为 00:00:01.250、00:00:02.000、00:00:03.500。
|
||||
完整 CI 2337 项通过(1987 单元、14 工程、336 基线),严格生产 TS 353 文件。
|
||||
状态更新后 9 项元数据测试通过。显式 lint 无错误/警告,全量 lint 保留既有生成声明警告。
|
||||
|
||||
## 独立发现与后续边界
|
||||
|
||||
给字面量场景提供正常 `<...>` 输入时,vendor 生成了 `<;...>;`。九份历史实现
|
||||
又复现 `<`、`>`、`&` 的多余分号,新增 MULTI-SUB-ENTITY-01,由 05 处置。
|
||||
本任务不改 vendor;最终字面量显示测试通过公开 cue.text 修改隔离显示层,明确不代表
|
||||
该实体解码缺陷已修复。额外分号的初始六个浏览器失败记录保留。
|
||||
|
||||
04 的公开类型、完整旧核心/实体设备、实体解码、全部样式/编辑器和安装消费者仍待
|
||||
后续任务。正常构建更新三个产物及 docs 副本;无新依赖、公开声明和版本变更。
|
||||
本任务独立本地提交,不推送、不发布。回退恢复原 NaN 输出并移除显示监听器;
|
||||
已发布失败基线及新发现记录仍可追溯。
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 223 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 89 / doing 14 / blocked 0 / done 120 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 88 / doing 14 / blocked 0 / done 121 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
| 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-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-07 | artplayer-plugin-multiple-subtitles<br>修复字幕内嵌时间戳序列化 | PKG-MULTI-SUB-03 | 保留数字时间戳节点、修正无效 NaN 输出、明确旧/新字幕输出差异 | 旧九份实现可复现,候选有效内嵌时间戳及文字/CSS/原生字幕通过,公开工厂与选择接口不变 | H | todo |
|
||||
| PKG-MULTI-SUB-07 | artplayer-plugin-multiple-subtitles<br>修复字幕内嵌时间戳序列化 | PKG-MULTI-SUB-03 | 保留数字时间戳节点、修正无效 NaN 输出、明确旧/新字幕输出差异 | 旧九份实现可复现,候选有效内嵌时间戳及文字/CSS/原生字幕通过,公开工厂与选择接口不变 | H | done |
|
||||
| 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 |
|
||||
|
||||
@@ -516,6 +516,7 @@
|
||||
- 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-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)
|
||||
- PKG-ADS-03: [记录](changes/2026-09-12-PKG-ADS-03-lifecycle.md) [记录](ads-validation.md) [记录](baselines/ads-lifecycle-validation.json)
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-MULTI-SUB-07 内嵌时间戳与显示修复完成
|
||||
|
||||
修复数字时间戳被文本包装成 NaN;合法 VTT class 标记保留原生 processing instruction,
|
||||
caption.ts 在既有事件后只展开专用标记,保留后续节点及用户文字,销毁移除监听。
|
||||
覆盖顶层/嵌套、开始/中间/末尾、多个毫秒时间戳、冻结树和重复选择;私有类型不再
|
||||
允许字符串时间戳。源码/main/legacy 各 37 项通过,本包 267 项通过,双 TS 编译器各
|
||||
9 个负例准确拒绝。三入口原生各 36 项共 108 项通过,原生时间精确为 1.250/2.000/3.500 秒。
|
||||
最初裸标签可见、删除标记丢后文及 vendor 实体分号问题均保留了实际失败报告。
|
||||
时间戳修复后完整 CI 2337 项通过(1987 单元、14 工程、336 基线),严格生产 TS 353 文件。
|
||||
正常构建/ESM 入口通过,dist/docs 副本一致;公开声明、依赖、版本和 vendor 源码未改。
|
||||
TIMESTAMP 风险关闭;九份历史实体分号问题新增 ENTITY 风险,归 05 独立修复。
|
||||
见[变更](changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md)和[验证](baselines/multiple-subtitles-timestamps.json)。
|
||||
223 项:121 done、14 doing、88 todo;198 项风险。独立本地提交,不推送、不发布。
|
||||
下一步继续 MULTI-SUB-04 公开声明/异步及旧模块消费者兼容;实体解码和完整设备组合仍待 05。
|
||||
|
||||
|
||||
## PKG-MULTI-SUB-04 严格运行时 TS 检查点(doing)
|
||||
|
||||
五个自有模块迁为 TS,新增明确的 Option/Promise Result/Track/host/lifetime 类型;
|
||||
|
||||
@@ -200,4 +200,5 @@
|
||||
| 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-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 |
|
||||
| MULTI-SUB-TIMESTAMP-01 | resolved / 已复现 | Text wrappers replace numeric inline cue timestamps and serialize NaN times | PKG-MULTI-SUB-07, PKG-MULTI-SUB-05 |
|
||||
| MULTI-SUB-ENTITY-01 | open / 已复现 | Vendored cue entity decoding retains semicolons after lt, gt and amp entities | PKG-MULTI-SUB-05 |
|
||||
+31
-3
@@ -4403,7 +4403,7 @@
|
||||
"id": "MULTI-SUB-TIMESTAMP-01",
|
||||
"title": "Text wrappers replace numeric inline cue timestamps and serialize NaN times",
|
||||
"confirmation": "reproduced",
|
||||
"status": "open",
|
||||
"status": "resolved",
|
||||
"owners": [
|
||||
"PKG-MULTI-SUB-07",
|
||||
"PKG-MULTI-SUB-05"
|
||||
@@ -4411,10 +4411,38 @@
|
||||
"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"
|
||||
"test/multiple-subtitles-failures.test.js",
|
||||
"refactor/baselines/multiple-subtitles-timestamps.json",
|
||||
"refactor/changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md",
|
||||
"test/multiple-subtitles-merge.test.js",
|
||||
"test/browser/multiple-subtitles-lifecycle.spec.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."
|
||||
"closureCriteria": "Valid inline timestamps and surrounding text survive serialization and native subtitle playback; frozen historical failure remains reproducible and ordinary markup regressions pass.",
|
||||
"resolutionEvidence": [
|
||||
"refactor/baselines/multiple-subtitles-timestamps.json",
|
||||
"refactor/changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md",
|
||||
"test/multiple-subtitles-merge.test.js",
|
||||
"test/browser/multiple-subtitles-lifecycle.spec.js",
|
||||
"packages/artplayer-plugin-multiple-subtitles/src/caption.ts"
|
||||
],
|
||||
"resolutionRationale": "Numeric timestamp nodes serialize inside valid VTT class markers, preserve native processing instructions, and are unwrapped only in the custom caption DOM without deleting subsequent text. Source/main/legacy tests, frozen-tree and timestamp cases, strict negative fixtures, and 108 native cases across both core fixtures pass. Public cue edits verify literal text preservation independently of the unresolved vendor entity bug."
|
||||
},
|
||||
{
|
||||
"id": "MULTI-SUB-ENTITY-01",
|
||||
"title": "Vendored cue entity decoding retains semicolons after lt, gt and amp entities",
|
||||
"confirmation": "reproduced",
|
||||
"status": "open",
|
||||
"owners": [
|
||||
"PKG-MULTI-SUB-05"
|
||||
],
|
||||
"evidence": [
|
||||
"test/multiple-subtitles-failures.test.js",
|
||||
"refactor/baselines/multiple-subtitles-timestamps.json",
|
||||
"refactor/changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md"
|
||||
],
|
||||
"compatibleResolution": "Independently fix valid named-entity decoding, documenting the vendor patch and preserving provenance checks and ordinary cue behavior.",
|
||||
"closureCriteria": "Nine historical implementations reproduce the defect; candidate entity/markup regressions and real subtitle display pass, with vendor adaptation and provenance documentation updated."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -32,6 +32,6 @@ for (const compiler of [ts, runtimeCompat]) {
|
||||
}
|
||||
assert.deepEqual(diagnostics(false), [])
|
||||
const rejected = diagnostics(true)
|
||||
assert.equal(rejected.length, 8, JSON.stringify(rejected))
|
||||
assert.equal(rejected.length, 9, JSON.stringify(rejected))
|
||||
})
|
||||
}
|
||||
+5
-2
@@ -2338,11 +2338,14 @@
|
||||
"dependsOn": [
|
||||
"PKG-MULTI-SUB-03"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "保留数字时间戳节点、修正无效 NaN 输出、明确旧/新字幕输出差异",
|
||||
"acceptance": "旧九份实现可复现,候选有效内嵌时间戳及文字/CSS/原生字幕通过,公开工厂与选择接口不变",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"baselines/multiple-subtitles-timestamps.json",
|
||||
"changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-MULTI-SUB-05",
|
||||
|
||||
@@ -166,4 +166,49 @@ for (const core of ['published', 'candidate']) {
|
||||
await page.evaluate(() => window.art.destroy(false))
|
||||
await evidence(page, testInfo, core, { requests, nativeSourceSwitch: true })
|
||||
})
|
||||
|
||||
test(`Multiple subtitles candidate ${core} core: inline timestamps remain native instructions without visible time tags`, async ({ page }, testInfo) => {
|
||||
const timed = 'WEBVTT\n\n00:00.000 --> 00:08.000\n<00:01.250>Before <00:02.000> after<00:03.500>\n'
|
||||
await page.route('**/test/multiple-timed.vtt', route => route.fulfill({ body: timed, contentType: 'text/vtt' }))
|
||||
await prepare(page, core)
|
||||
await page.addStyleTag({ content: '.art-subtitle-timed { color: rgb(255, 0, 0); }' })
|
||||
await page.evaluate(async () => {
|
||||
await window.art.plugins.add(window.artplayerPluginMultipleSubtitles({ subtitles: [{ url: '/test/multiple-timed.vtt', name: 'timed' }] }))
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.subtitle.cues.length)).toBe(1)
|
||||
const native = await page.evaluate(() => {
|
||||
const cue = window.art.subtitle.cues[0]
|
||||
const fragment = cue.getCueAsHTML()
|
||||
const walker = document.createTreeWalker(fragment, NodeFilter.SHOW_PROCESSING_INSTRUCTION)
|
||||
const timestamps = []
|
||||
while (walker.nextNode()) timestamps.push(walker.currentNode.nodeValue)
|
||||
return { text: cue.text, timestamps }
|
||||
})
|
||||
expect(native.text).toContain('<00:02.000>')
|
||||
expect(native.text).not.toContain('NaN')
|
||||
expect(native.timestamps).toHaveLength(3)
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
|
||||
await page.locator('#pause').click()
|
||||
await expect(page.locator('.art-subtitle-timed').first()).toHaveCSS('color', 'rgb(255, 0, 0)')
|
||||
const timedLine = page.locator('.art-subtitle-line').filter({ has: page.locator('.art-subtitle-timed') })
|
||||
const visible = await timedLine.textContent()
|
||||
expect(visible).toContain('Before')
|
||||
expect(visible).toContain('after')
|
||||
expect(visible).not.toMatch(/<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/)
|
||||
// Public cue edits exercise literal display without relying on the vendor's entity decoder.
|
||||
await page.evaluate(() => {
|
||||
window.art.subtitle.cues[0].text += '<div class="art-subtitle-literal">Literal <00:02.000> example</div>'
|
||||
window.art.subtitle.update()
|
||||
})
|
||||
await expect(page.locator('.art-subtitle-literal')).toHaveText('Literal <00:02.000> example')
|
||||
await page.evaluate(() => window.art.plugins.multipleSubtitles.tracks([]))
|
||||
await expect(page.locator('.art-subtitle-timed')).toHaveCount(0)
|
||||
await page.evaluate(() => window.art.plugins.multipleSubtitles.reset())
|
||||
await expect(timedLine).toContainText('Before')
|
||||
await expect(timedLine).not.toContainText(/<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/)
|
||||
await expect(page.locator('.art-subtitle-literal')).toHaveCount(0)
|
||||
await page.evaluate(() => window.art.destroy(false))
|
||||
await evidence(page, testInfo, core, { native, visible })
|
||||
})
|
||||
}
|
||||
@@ -263,4 +263,10 @@ for (const implementation of implementations) {
|
||||
await env.factory(options(subtitle()))(env.art)
|
||||
assert((await env.latestText()).includes('<NaN:NaN.NaN>'))
|
||||
})
|
||||
|
||||
test(`Multiple-subtitles ${implementation.name} failure baseline: entity decoding retains stray semicolons`, async () => {
|
||||
const env = create({ responses: { 'a.vtt': subtitleVtt('Literal <00:02.000> example &') } })
|
||||
await env.factory(options(subtitle()))(env.art)
|
||||
assert(env.utils.unescape(await env.latestText()).includes('Literal <;00:02.000>; example &;'))
|
||||
})
|
||||
}
|
||||
@@ -21,7 +21,7 @@ async function flush() {
|
||||
}
|
||||
function empty(env) {
|
||||
assert.equal(env.liveBlobs.size, 0)
|
||||
assert.equal(env.listeners.get('destroy')?.size || 0, 0)
|
||||
for (const listeners of env.listeners.values()) assert.equal(listeners.size, 0)
|
||||
}
|
||||
|
||||
for (const abortController of [true, false]) {
|
||||
|
||||
@@ -74,3 +74,48 @@ test('Multiple subtitles request: real core SRT/ASS conversion and encoded text
|
||||
for (const value of ['SRT text', 'ASS text', 'café', '日本語']) assert(text.includes(value))
|
||||
current.emit('destroy')
|
||||
})
|
||||
|
||||
for (const [text, timestamps] of [
|
||||
['Before <00:02.000> after', ['<00:02.000>']],
|
||||
['<00:01.250> one <00:03.500> two', ['<00:01.250>', '<00:03.500>']],
|
||||
['end <00:07.000>', ['<00:07.000>']],
|
||||
['甲<00:02.000>乙', ['<00:02.000>']],
|
||||
['<b>bold <00:02.000> after</b>', ['<00:02.000>']],
|
||||
]) {
|
||||
test(`Multiple subtitles timestamps: valid nodes survive selection and reset (${text})`, async () => {
|
||||
const input = `WEBVTT\n\n00:00.000 --> 00:08.000\n${text}\n`
|
||||
const env = multipleSubtitlesEnvironment(candidate, { responses: { 'a.vtt': input, 'b.vtt': subtitleVtt('Other') } })
|
||||
const result = await env.factory({ subtitles: [{ url: 'a.vtt', name: 'a' }, { url: 'b.vtt', name: 'b' }] })(env.art)
|
||||
result.tracks(['a'])
|
||||
const selected = await env.latestText()
|
||||
for (const timestamp of timestamps) assert(selected.includes(timestamp))
|
||||
assert(!selected.includes('NaN'))
|
||||
const reparsed = parseTracks([selected], [{ name: 'reparsed' }])
|
||||
assert.deepEqual(reparsed[0].errors, [])
|
||||
const numbers = []
|
||||
function collect(nodes) {
|
||||
for (const node of nodes) {
|
||||
if (node.type === 'timestamp')
|
||||
numbers.push(node.value)
|
||||
if (node.children)
|
||||
collect(node.children)
|
||||
}
|
||||
}
|
||||
collect(reparsed[0].cues[0].tree.children)
|
||||
assert.equal(numbers.length, timestamps.length)
|
||||
result.tracks(['b'])
|
||||
assert(!(await env.latestText()).includes(timestamps[0]))
|
||||
result.reset()
|
||||
for (const timestamp of timestamps) assert((await env.latestText()).includes(timestamp))
|
||||
env.emit('destroy')
|
||||
})
|
||||
}
|
||||
|
||||
test('Multiple subtitles timestamps: frozen numeric nodes stay unchanged across duplicate selection', () => {
|
||||
const trees = freeze(parseTracks(['WEBVTT\n\n00:00.000 --> 00:08.000\nBefore <00:02.000> after\n'], [{ name: 'a' }]))
|
||||
const original = JSON.stringify(trees)
|
||||
const output = serializeTracks([trees[0], trees[0]])
|
||||
assert.equal((output.match(/<00:02\.000>/g) || []).length, 2)
|
||||
assert.equal((output.match(/<c\.artplayer-multiple-subtitles-timestamp>/g) || []).length, 2)
|
||||
assert.equal(JSON.stringify(trees), original)
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { CueNode, TimestampNode } from '../../packages/artplayer-plugin-multiple-subtitles/src/parser'
|
||||
import type { CueNode, TimestampNode, WrappedNode } 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'
|
||||
@@ -52,6 +52,9 @@ void timestamp
|
||||
// @ts-expect-error Parsed timestamps are numbers, not wrapped serialized values.
|
||||
const wrongTimestamp: TimestampNode = { type: 'timestamp', value: '2' }
|
||||
void wrongTimestamp
|
||||
// @ts-expect-error Serialized timestamp nodes must also keep numeric values.
|
||||
const wrongWrapped: WrappedNode = { type: 'timestamp', value: '<div>2</div>' }
|
||||
void wrongWrapped
|
||||
const trees = parseTracks(['WEBVTT\n\n'], [{}])
|
||||
const vtt: string = serializeTracks(trees)
|
||||
void vtt
|
||||
|
||||
Reference in new issue
Block a user