mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-11 22:06:17 -08:00
fix(multiple-subtitles): [PKG-MULTI-SUB-08] preserve entity text and semantic cue wrappers
This commit is contained in:
1 parent
4acf85ea69
commit
675b095b27
22 files changed
+744
-59
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],h=m.length,g="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(h<b||m.indexOf(g)!==0+v||h>b&&" "!==m[b]&&"\t"!==m[b])&&p('No valid signature. (File needs to start with "WEBVTT".)'),o++;""!=l[o]&&void 0!=l[o];){if(p("No blank line after the signature."),-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}for(;void 0!=l[o];){for(var y;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;y=Object.assign({},e,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var T=!0;if(-1==l[o].indexOf("--\x3e")){if(y.id=l[o],/^NOTE($|[ \t])/.test(y.id)){for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&p("Cannot have timestamp in a comment."),o++;continue}if(/^STYLE($|[ \t])/.test(y.id)){var x=[],w=!1;for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&(p("Cannot have timestamp in a style block."),w=!0),x.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(x.join("\n"));continue}if(""==l[++o]||void 0==l[o]){p("Cue identifier cannot be standalone.");continue}if(-1==l[o].indexOf("--\x3e")){T=!1,p("Cue identifier needs to be followed by timestamp.");continue}}c=!1;var A=new n(l[o],p),O=0;if(u.length>0&&(O=u[u.length-1].startTime),!T||A.parse(y,O)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=y.text&&(y.text+="\n"),y.text+=l[o],o++}var N=new i(y.text,p,a,t);y.tree=N.parse(y.startTime,y.endTime),u.push(y)}else for(y=null,o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}}return u.sort(function(e,t){return e.startTime<t.startTime?-1:e.startTime>t.startTime?1:e.endTime>t.endTime?-1:e.endTime<t.endTime?1:0}),{cues:u,errors:d,time:Date.now()-r,styles:f}}},n=function(e,t){var n=/[\u0020\t\f]/,i=/[^\u0020\t\f]/,s=(e=e,0),a=function(e){t(e,s+1)};function r(t){for(;void 0!=e[s]&&t.test(e[s]);)s++}function o(t){for(var n="";void 0!=e[s]&&t.test(e[s]);)n+=e[s],s++;return n}function l(){var t,n,i,r,l="minutes";if(void 0!=e[s])if(/\d/.test(e[s]))if(((t=o(/\d/)).length>2||parseInt(t,10)>59)&&(l="hours"),":"==e[s])if(s++,2==(n=o(/\d/)).length){if("hours"==l||":"==e[s]){if(":"!=e[s])return void a("No seconds found or minutes is greater than 59.");if(s++,2!=(i=o(/\d/)).length)return void a("Must be exactly two digits.")}else{if(2!=t.length)return void a("Must be exactly two digits.");i=n,n=t,t="0"}if("."==e[s])if(s++,3==(r=o(/\d/)).length)if(parseInt(n,10)>59)a("You cannot have more than 59 minutes.");else{if(!(parseInt(i,10)>59))return 60*parseInt(t,10)*60+60*parseInt(n,10)+parseInt(i,10)+parseInt(r,10)/1e3;a("You cannot have more than 59 seconds.")}else a("Milliseconds must be given in three digits.");else a('No decimal separator (".") found.')}else a("Must be exactly two digits.");else a("No time unit separator found.");else a("Timestamp must start with a character in the range 0-9.");else a("No timestamp found.")}this.parse=function(t,o){if(r(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime<o&&a("Start timestamp is not greater than or equal to start timestamp of previous cue."),i.test(e[s])&&a("Timestamp not separated from '--\x3e' by whitespace."),r(n),"-"==e[s])if(s++,"-"==e[s])if(s++,">"==e[s]){if(s++,i.test(e[s])&&a("'--\x3e' not separated from timestamp by whitespace."),r(n),t.endTime=l(),void 0!=t.endTime)return t.endTime<=t.startTime&&a("End timestamp is not greater than start timestamp."),i.test(e[s]),r(n),function(e,t){for(var i=e.split(n),s=[],r=0;r<i.length;r++)if(""!=i[r]){var o=i[r].indexOf(":"),l=i[r].slice(0,o),c=i[r].slice(o+1);if(-1!=s.indexOf(l)&&a("Duplicate setting."),s.push(l),""==c)return void a("No value for setting defined.");if("vertical"==l){if("rl"!=c&&"lr"!=c){a("Writing direction can only be set to 'rl' or 'rl'.");continue}t.direction=c}else if("line"==l){if(/,/.test(c)){var f=c.split(",");c=f[0];var u=f[1]}if(!/^[-\d](\d*)(\.\d+)?%?$/.test(c)){a("Line position takes a number or percentage.");continue}if(-1!=c.indexOf("-",1)){a("Line position can only have '-' at the start.");continue}if(-1!=c.indexOf("%")&&c.indexOf("%")!=c.Line truncated
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},t=function(t){t||(t={"&":"&","<":"<",">":">","&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 y=!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 T=[],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),T.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(T.join("\n"));continue}if(""==l[++o]||void 0==l[o]){p("Cue identifier cannot be standalone.");continue}if(-1==l[o].indexOf("--\x3e")){y=!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),!y||A.parse(x,O)){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 N=new i(x.text,p,a,t);x.tree=N.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.
|
||||
*/
|
||||
!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
|
||||
!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],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=[],a=Line truncated
|
||||
@@ -799,19 +799,36 @@ var WebVTTSerializer = function() {
|
||||
return result;
|
||||
};
|
||||
};
|
||||
const cueEntities = {
|
||||
"&": "&",
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
">": ">",
|
||||
"&lrm": "",
|
||||
"‎": "",
|
||||
"&rlm": "",
|
||||
"‏": "",
|
||||
" ": " ",
|
||||
" ": " "
|
||||
};
|
||||
function escapeCueText(value) {
|
||||
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
const TIMESTAMP_CLASS = "artplayer-multiple-subtitles-timestamp";
|
||||
function markTimestamp(node) {
|
||||
return { type: "object", name: "c", classes: [TIMESTAMP_CLASS], children: [node] };
|
||||
}
|
||||
function markNestedTimestamps(node) {
|
||||
function prepareCueNode(node) {
|
||||
if (node.type === "timestamp")
|
||||
return markTimestamp(node);
|
||||
if (node.type === "object")
|
||||
return { ...node, children: node.children.map(markNestedTimestamps) };
|
||||
return node;
|
||||
return { ...node, children: node.children.map(prepareCueNode) };
|
||||
return { ...node, value: escapeCueText(node.value) };
|
||||
}
|
||||
function parseTracks(vtts, subtitles) {
|
||||
const parser = new WebVTTParser();
|
||||
const parser = new WebVTTParser(cueEntities);
|
||||
return vtts.map((vtt, index2) => {
|
||||
const tree = parser.parse(vtt, "metadata");
|
||||
return { ...tree, url: subtitles[index2].url, name: subtitles[index2].name };
|
||||
@@ -822,16 +839,21 @@ function serializeTracks(trees) {
|
||||
for (const selected of trees) {
|
||||
const tree = selected;
|
||||
for (const cue of tree.cues) {
|
||||
const children = [];
|
||||
for (const child of cue.tree.children) {
|
||||
if (child.type === "timestamp")
|
||||
children.push(markTimestamp(child));
|
||||
else children.push(
|
||||
{ type: "text", value: `<div class="art-subtitle-${tree.name}">` },
|
||||
prepareCueNode(child),
|
||||
{ type: "text", value: "</div>" }
|
||||
);
|
||||
}
|
||||
cues.push({
|
||||
...cue,
|
||||
tree: {
|
||||
...cue.tree,
|
||||
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 };
|
||||
})
|
||||
children
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -51,7 +51,7 @@
|
||||
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:llm && yarn build:test && yarn build:docs && yarn test:imports",
|
||||
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js",
|
||||
"typecheck": "node scripts/typecheck.mjs",
|
||||
"test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js 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 test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
|
||||
"test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.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 test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
|
||||
"test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
|
||||
"test": "yarn test:node && yarn test:baseline",
|
||||
"test:browser": "playwright test",
|
||||
@@ -88,7 +88,7 @@
|
||||
"test:auto-thumbnail": "node --test test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js refactor/scripts/auto-thumbnail-contract.test.mjs refactor/scripts/auto-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:multiple-subtitles": "node --test test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js refactor/scripts/multiple-subtitles-contract.test.mjs refactor/scripts/multiple-subtitles-runtime-types.test.mjs refactor/scripts/multiple-subtitles-types.test.mjs",
|
||||
"test:multiple-subtitles": "node --test test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js refactor/scripts/multiple-subtitles-contract.test.mjs refactor/scripts/multiple-subtitles-runtime-types.test.mjs refactor/scripts/multiple-subtitles-types.test.mjs",
|
||||
"test:multiple-subtitles-types-package": "node refactor/scripts/multiple-subtitles-package-types.mjs",
|
||||
"test:jassub": "node --test test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js refactor/scripts/jassub-contract.test.mjs refactor/scripts/jassub-provenance.test.mjs refactor/scripts/jassub-failures.test.mjs refactor/scripts/jassub-types.test.mjs refactor/scripts/jassub-vendor-patch.test.mjs",
|
||||
"test:jassub-types": "node --test refactor/scripts/jassub-types.test.mjs",
|
||||
|
||||
@@ -19,6 +19,10 @@ to npm 1.2.0; the runtime entry requires an option object but allows omitted `su
|
||||
to the existing core utils. Failure aborts the request; registration failure closes siblings.
|
||||
- `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.
|
||||
- `src/text.ts` supplies the vendor's existing entity-table option with both exact
|
||||
semicolon-terminated references and historical lenient aliases. It also protects
|
||||
literal cue text through the later onVttLoad unescape and HTML caption parsing.
|
||||
No vendor implementation patch is needed; parser.js and its provenance remain intact.
|
||||
- `src/lifetime.ts` owns the destroy listener, request cancellation and resource callbacks.
|
||||
Its cancellation Promise also settles pending operations without AbortController support.
|
||||
- `src/render.ts` owns generated Blob URLs and synchronous/async host installation outcomes.
|
||||
@@ -47,7 +51,7 @@ to npm 1.2.0; the runtime entry requires an option object but allows omitted `su
|
||||
All tracks download concurrently and decode through TextDecoder. Explicit type wins over
|
||||
the URL extension; SRT/ASS conversion delegates to existing core utils. `onParser` appears
|
||||
in declarations but is not called. Parsing runs in metadata mode. Each track's top-level
|
||||
cue text receives a name-based div in a copied cue; merging concatenates cue arrays by track
|
||||
cue node receives a name-based div in a copied cue; merging concatenates cue arrays by track
|
||||
order, whereas 1.0.0 merged by cue index and retained the first track's timestamps.
|
||||
|
||||
Each selection serializes VTT and allocates a text/vtt Blob before replacing the preceding
|
||||
@@ -73,15 +77,27 @@ and unwraps its remaining children. HTML does not pair `<c.class>` with `</c>`;
|
||||
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.
|
||||
The vendor default entity table still has its historical semicolon bug, preserved
|
||||
in the unmodified vendor file and its upstream parity tests. The plugin now supplies
|
||||
exact amp/lt/gt/lrm/rlm/nbsp references through the existing constructor option.
|
||||
Historical partial/bare-name handling remains via the old aliases; numeric and
|
||||
unknown references are left to the parser. Entity decoding runs once, not repeatedly.
|
||||
|
||||
Literal text is escaped separately from semantic cue tags before the serializer's
|
||||
outer escaping. The renderer's existing onVttLoad unescape removes only that outer
|
||||
layer, leaving escaped literal tags/timestamps for native VTT and HTML display.
|
||||
Wrappers are sibling text nodes around the prepared node; they are never assigned
|
||||
to a b/i/c/v/ruby node's annotation field. This fixes malformed tag attributes and
|
||||
visible undefined while preserving CSS hooks, actual markup and numeric timestamps.
|
||||
Native/custom display, nested entities and selection/reset regressions belong to
|
||||
PKG-MULTI-SUB-08; it does not complete all historical core/device combinations.
|
||||
|
||||
## Vendor boundary and validation
|
||||
|
||||
The comparison revision is w3c/webvtt.js `380cfcce34ba8b472d3a31474874eb72a0e5f460`.
|
||||
This is not proof of the original acquisition commit. Its execution body matches local
|
||||
code after replacing the IIFE/global exports with named ESM exports and normalizing format.
|
||||
The plugin-side entity table is a configuration adapter, not a change to that body.
|
||||
Older shipped sources keep the IIFE. Preserve the CC0 source header and complete
|
||||
THIRD_PARTY_NOTICES; the normal build places notices in all standalone bundle headers.
|
||||
|
||||
|
||||
+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],h=m.length,g="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(h<b||m.indexOf(g)!==0+v||h>b&&" "!==m[b]&&"\t"!==m[b])&&p('No valid signature. (File needs to start with "WEBVTT".)'),o++;""!=l[o]&&void 0!=l[o];){if(p("No blank line after the signature."),-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}for(;void 0!=l[o];){for(var y;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;y=Object.assign({},e,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var T=!0;if(-1==l[o].indexOf("--\x3e")){if(y.id=l[o],/^NOTE($|[ \t])/.test(y.id)){for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&p("Cannot have timestamp in a comment."),o++;continue}if(/^STYLE($|[ \t])/.test(y.id)){var x=[],w=!1;for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&(p("Cannot have timestamp in a style block."),w=!0),x.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(x.join("\n"));continue}if(""==l[++o]||void 0==l[o]){p("Cue identifier cannot be standalone.");continue}if(-1==l[o].indexOf("--\x3e")){T=!1,p("Cue identifier needs to be followed by timestamp.");continue}}c=!1;var A=new n(l[o],p),O=0;if(u.length>0&&(O=u[u.length-1].startTime),!T||A.parse(y,O)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=y.text&&(y.text+="\n"),y.text+=l[o],o++}var N=new i(y.text,p,a,t);y.tree=N.parse(y.startTime,y.endTime),u.push(y)}else for(y=null,o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}}return u.sort(function(e,t){return e.startTime<t.startTime?-1:e.startTime>t.startTime?1:e.endTime>t.endTime?-1:e.endTime<t.endTime?1:0}),{cues:u,errors:d,time:Date.now()-r,styles:f}}},n=function(e,t){var n=/[\u0020\t\f]/,i=/[^\u0020\t\f]/,s=(e=e,0),a=function(e){t(e,s+1)};function r(t){for(;void 0!=e[s]&&t.test(e[s]);)s++}function o(t){for(var n="";void 0!=e[s]&&t.test(e[s]);)n+=e[s],s++;return n}function l(){var t,n,i,r,l="minutes";if(void 0!=e[s])if(/\d/.test(e[s]))if(((t=o(/\d/)).length>2||parseInt(t,10)>59)&&(l="hours"),":"==e[s])if(s++,2==(n=o(/\d/)).length){if("hours"==l||":"==e[s]){if(":"!=e[s])return void a("No seconds found or minutes is greater than 59.");if(s++,2!=(i=o(/\d/)).length)return void a("Must be exactly two digits.")}else{if(2!=t.length)return void a("Must be exactly two digits.");i=n,n=t,t="0"}if("."==e[s])if(s++,3==(r=o(/\d/)).length)if(parseInt(n,10)>59)a("You cannot have more than 59 minutes.");else{if(!(parseInt(i,10)>59))return 60*parseInt(t,10)*60+60*parseInt(n,10)+parseInt(i,10)+parseInt(r,10)/1e3;a("You cannot have more than 59 seconds.")}else a("Milliseconds must be given in three digits.");else a('No decimal separator (".") found.')}else a("Must be exactly two digits.");else a("No time unit separator found.");else a("Timestamp must start with a character in the range 0-9.");else a("No timestamp found.")}this.parse=function(t,o){if(r(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime<o&&a("Start timestamp is not greater than or equal to start timestamp of previous cue."),i.test(e[s])&&a("Timestamp not separated from '--\x3e' by whitespace."),r(n),"-"==e[s])if(s++,"-"==e[s])if(s++,">"==e[s]){if(s++,i.test(e[s])&&a("'--\x3e' not separated from timestamp by whitespace."),r(n),t.endTime=l(),void 0!=t.endTime)return t.endTime<=t.startTime&&a("End timestamp is not greater than start timestamp."),i.test(e[s]),r(n),function(e,t){for(var i=e.split(n),s=[],r=0;r<i.length;r++)if(""!=i[r]){var o=i[r].indexOf(":"),l=i[r].slice(0,o),c=i[r].slice(o+1);if(-1!=s.indexOf(l)&&a("Duplicate setting."),s.push(l),""==c)return void a("No value for setting defined.");if("vertical"==l){if("rl"!=c&&"lr"!=c){a("Writing direction can only be set to 'rl' or 'rl'.");continue}t.direction=c}else if("line"==l){if(/,/.test(c)){var f=c.split(",");c=f[0];var u=f[1]}if(!/^[-\d](\d*)(\.\d+)?%?$/.test(c)){a("Line position takes a number or percentage.");continue}if(-1!=c.indexOf("-",1)){a("Line position can only have '-' at the start.");continue}if(-1!=c.indexOf("%")&&c.indexOf("%")!=c.Line truncated
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},t=function(t){t||(t={"&":"&","<":"<",">":">","&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 y=!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 T=[],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),T.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(T.join("\n"));continue}if(""==l[++o]||void 0==l[o]){p("Cue identifier cannot be standalone.");continue}if(-1==l[o].indexOf("--\x3e")){y=!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),!y||A.parse(x,O)){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 N=new i(x.text,p,a,t);x.tree=N.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
|
||||
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())}),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
|
||||
!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],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=[],a=Line truncated
|
||||
+32
-10
@@ -799,19 +799,36 @@ var WebVTTSerializer = function() {
|
||||
return result;
|
||||
};
|
||||
};
|
||||
const cueEntities = {
|
||||
"&": "&",
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
">": ">",
|
||||
"&lrm": "",
|
||||
"‎": "",
|
||||
"&rlm": "",
|
||||
"‏": "",
|
||||
" ": " ",
|
||||
" ": " "
|
||||
};
|
||||
function escapeCueText(value) {
|
||||
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
const TIMESTAMP_CLASS = "artplayer-multiple-subtitles-timestamp";
|
||||
function markTimestamp(node) {
|
||||
return { type: "object", name: "c", classes: [TIMESTAMP_CLASS], children: [node] };
|
||||
}
|
||||
function markNestedTimestamps(node) {
|
||||
function prepareCueNode(node) {
|
||||
if (node.type === "timestamp")
|
||||
return markTimestamp(node);
|
||||
if (node.type === "object")
|
||||
return { ...node, children: node.children.map(markNestedTimestamps) };
|
||||
return node;
|
||||
return { ...node, children: node.children.map(prepareCueNode) };
|
||||
return { ...node, value: escapeCueText(node.value) };
|
||||
}
|
||||
function parseTracks(vtts, subtitles) {
|
||||
const parser = new WebVTTParser();
|
||||
const parser = new WebVTTParser(cueEntities);
|
||||
return vtts.map((vtt, index2) => {
|
||||
const tree = parser.parse(vtt, "metadata");
|
||||
return { ...tree, url: subtitles[index2].url, name: subtitles[index2].name };
|
||||
@@ -822,16 +839,21 @@ function serializeTracks(trees) {
|
||||
for (const selected of trees) {
|
||||
const tree = selected;
|
||||
for (const cue of tree.cues) {
|
||||
const children = [];
|
||||
for (const child of cue.tree.children) {
|
||||
if (child.type === "timestamp")
|
||||
children.push(markTimestamp(child));
|
||||
else children.push(
|
||||
{ type: "text", value: `<div class="art-subtitle-${tree.name}">` },
|
||||
prepareCueNode(child),
|
||||
{ type: "text", value: "</div>" }
|
||||
);
|
||||
}
|
||||
cues.push({
|
||||
...cue,
|
||||
tree: {
|
||||
...cue.tree,
|
||||
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 };
|
||||
})
|
||||
children
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Cue, CueNode, TimestampNode, WrappedNode } from './parser'
|
||||
import type { Track, TrackOption } from './types'
|
||||
import { WebVTTParser, WebVTTSerializer } from './parser'
|
||||
import { cueEntities, escapeCueText } from './text'
|
||||
|
||||
export const TIMESTAMP_CLASS = 'artplayer-multiple-subtitles-timestamp'
|
||||
|
||||
@@ -8,16 +9,16 @@ function markTimestamp(node: TimestampNode): CueNode {
|
||||
return { type: 'object', name: 'c', classes: [TIMESTAMP_CLASS], children: [node] }
|
||||
}
|
||||
|
||||
function markNestedTimestamps(node: CueNode): CueNode {
|
||||
function prepareCueNode(node: CueNode): CueNode {
|
||||
if (node.type === 'timestamp')
|
||||
return markTimestamp(node)
|
||||
if (node.type === 'object')
|
||||
return { ...node, children: node.children.map(markNestedTimestamps) }
|
||||
return node
|
||||
return { ...node, children: node.children.map(prepareCueNode) }
|
||||
return { ...node, value: escapeCueText(node.value) }
|
||||
}
|
||||
|
||||
export function parseTracks(vtts: readonly string[], subtitles: readonly TrackOption[]): Track[] {
|
||||
const parser = new WebVTTParser()
|
||||
const parser = new WebVTTParser(cueEntities)
|
||||
return vtts.map((vtt, index) => {
|
||||
const tree = parser.parse(vtt, 'metadata')
|
||||
// Options and completed downloads share the same Promise.all index.
|
||||
@@ -31,18 +32,21 @@ export function serializeTracks(trees: readonly (Track | undefined)[]): string {
|
||||
// Unknown names retain the historical TypeError at the property access below.
|
||||
const tree = selected!
|
||||
for (const cue of tree.cues) {
|
||||
const children: CueNode[] = []
|
||||
for (const child of cue.tree.children) {
|
||||
if (child.type === 'timestamp')
|
||||
children.push(markTimestamp(child))
|
||||
else children.push(
|
||||
{ type: 'text', value: `<div class="art-subtitle-${tree.name}">` },
|
||||
prepareCueNode(child),
|
||||
{ type: 'text', value: '</div>' },
|
||||
)
|
||||
}
|
||||
cues.push({
|
||||
...cue,
|
||||
tree: {
|
||||
...cue.tree,
|
||||
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 }
|
||||
}),
|
||||
children,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Keep the vendor's lenient aliases while making complete named references exact.
|
||||
export const cueEntities = {
|
||||
'&': '&',
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'>': '>',
|
||||
'&lrm': '\u200E',
|
||||
'‎': '\u200E',
|
||||
'&rlm': '\u200F',
|
||||
'‏': '\u200F',
|
||||
' ': '\u00A0',
|
||||
' ': '\u00A0',
|
||||
}
|
||||
|
||||
// Serialization protects the whole VTT once; this protects cue text after onVttLoad unescape.
|
||||
export function escapeCueText(value: string): string {
|
||||
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
{
|
||||
"task": "PKG-MULTI-SUB-08",
|
||||
"date": "2026-09-14",
|
||||
"baselineCommit": "4acf85ea696a99f05fce71089711bc4a4ca0d5b6",
|
||||
"node": "v24.21.0",
|
||||
"yarn": "1.22.22",
|
||||
"platform": "win32",
|
||||
"before": {
|
||||
"artifact": {
|
||||
"path": "refactor/.cache/multiple-subtitles-entities-before.js",
|
||||
"sha256": "8ad5ddfa1ee1d79f7183b7a5fade748ca2093ceba7b467ec19b5b79ad129a249"
|
||||
},
|
||||
"initial": {
|
||||
"path": "refactor/.cache/multi-entities-before.log",
|
||||
"sha256": "cbe8d2bbb32db2682856e35ede9dd5600e9280b04efa148d2f75aea69b8eb716",
|
||||
"passed": 0,
|
||||
"failed": 5
|
||||
},
|
||||
"final": {
|
||||
"path": "refactor/.cache/multi-entities-before-final.log",
|
||||
"sha256": "0c56f858e5b0b42fb208f7505423b85a2013385bedf84aa9bb5f2749b3c71682",
|
||||
"passed": 3,
|
||||
"failed": 8
|
||||
}
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"path": "packages/artplayer-plugin-multiple-subtitles/src/merge.ts",
|
||||
"sha256": "e41514f3a62833d9e42bee20e693b5a13fb42cfb3ea2a08f0821bbcb2d450cfd"
|
||||
},
|
||||
{
|
||||
"path": "packages/artplayer-plugin-multiple-subtitles/src/text.ts",
|
||||
"sha256": "15660d74726e36831939799d72e8126ede3e3becd22ba9cc2127db4277171d0f"
|
||||
}
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"path": "test/multiple-subtitles-entities.test.js",
|
||||
"sha256": "23ff2ca0561a26347bb2602c1fb69b34bf5d664ad23c5c8877bccb383e66861e"
|
||||
},
|
||||
{
|
||||
"path": "test/multiple-subtitles-merge.test.js",
|
||||
"sha256": "c7a7ac447ad76e559d5710a590054599412806db6f4282fd2a856fa05092f533"
|
||||
},
|
||||
{
|
||||
"path": "test/multiple-subtitles-failures.test.js",
|
||||
"sha256": "d987af07a2c42ade51c6bd7b8eb2441191e748e5ddf7f3be5cbf4809a8be034f"
|
||||
},
|
||||
{
|
||||
"path": "test/browser/multiple-subtitles-entities.spec.js",
|
||||
"sha256": "cdc9dfc23900761c8110581ee81ad9f17c5796e03190c154b21cbdcb686c806e"
|
||||
}
|
||||
],
|
||||
"vendor": {
|
||||
"unchangedFromBaseline": true,
|
||||
"sha256LF": "ba582074d57fa2d72880e6e4253fe14646787916b2b68e2a1e748e685cf96ab1",
|
||||
"check": {
|
||||
"provenance": {
|
||||
"revision": "380cfcce34ba8b472d3a31474874eb72a0e5f460",
|
||||
"packageVersionAtRevision": "2.1.2",
|
||||
"scope": "Pinned comparison revision, not proof of historical acquisition commit. After removing IIFE/global exportify and adding named ESM exports, normalized executable content matches local parser.",
|
||||
"sources": [
|
||||
{
|
||||
"file": "refactor/baselines/multiple-subtitles-vendor/parser.txt",
|
||||
"upstream": "https://raw.githubusercontent.com/w3c/webvtt.js/380cfcce34ba8b472d3a31474874eb72a0e5f460/parser.js",
|
||||
"sha256": "35a112e585b7cb1b4fd30350b0e8c8f6d1efa2670a6008c4a6038e07c9f8d727"
|
||||
},
|
||||
{
|
||||
"file": "refactor/baselines/multiple-subtitles-vendor/license.txt",
|
||||
"upstream": "https://raw.githubusercontent.com/w3c/webvtt.js/380cfcce34ba8b472d3a31474874eb72a0e5f460/LICENSE.md",
|
||||
"sha256": "6cc655a5df52fefac69058311c090f4e70b7c37b8f6488f9498420a930b8275d"
|
||||
},
|
||||
{
|
||||
"file": "refactor/baselines/multiple-subtitles-vendor/package.json.txt",
|
||||
"upstream": "https://raw.githubusercontent.com/w3c/webvtt.js/380cfcce34ba8b472d3a31474874eb72a0e5f460/package.json",
|
||||
"sha256": "d0102b9276839f1136ecafd9527102a28b44643e8ff403b3ddb1e51c04777a7b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"normalizedSha256": "e71dc5073db409dcb8eef66a0d6bfe6c8d96fa7d157ff51a68decf6f05a7c80f",
|
||||
"localSha256LF": "ba582074d57fa2d72880e6e4253fe14646787916b2b68e2a1e748e685cf96ab1",
|
||||
"noticesSha256LF": "50755f38458d41151c7f0b1675c744aa365f0510dc11f53832691ef5c14ce6b6"
|
||||
}
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"path": "refactor/.cache/multi-entities-suite-final.log",
|
||||
"sha256": "2d38f2518283940e4d898823093a0959f6095c82883d97b1726874e20209beec",
|
||||
"passed": 294,
|
||||
"failed": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
{
|
||||
"path": "refactor/.cache/multi-entities-main.log",
|
||||
"sha256": "9eec0b8e567456842a4678da1ce57099c8c2faba98e0cc42b2d85e290afd037b",
|
||||
"passed": 11,
|
||||
"failed": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
{
|
||||
"path": "refactor/.cache/multi-entities-legacy.log",
|
||||
"sha256": "9eeb325f9df16ab584a8f722c4e31cc92411f776061d4ad5252bfcabbb830673",
|
||||
"passed": 11,
|
||||
"failed": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
{
|
||||
"path": "refactor/.cache/multi-entities-ci.log",
|
||||
"sha256": "c37a11d7fc5dca6e2dc84f308611fd26653a10d6b6528b62063f4aea5ccc58e6",
|
||||
"passed": 50,
|
||||
"failed": 0,
|
||||
"skipped": 0
|
||||
}
|
||||
],
|
||||
"browsers": [
|
||||
{
|
||||
"path": "refactor/.cache/multi-entities-source-browser/report.json",
|
||||
"sha256": "accb4d48bda22f45b1ea8ece707532dbdc84bc31b65b787340ad1bf77b5fe4a3",
|
||||
"stats": {
|
||||
"startTime": "2026-09-14T04:03:29.211Z",
|
||||
"duration": 50430.384999999995,
|
||||
"expected": 42,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"observations": [
|
||||
{
|
||||
"project": "chromium",
|
||||
"core": "published",
|
||||
"browser": "153.0.8010.12",
|
||||
"pluginSha256": "475afec2cd440de885520c6aee4165a7453fce8e03a920a7d809b8fe93db510c",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
},
|
||||
{
|
||||
"project": "chromium",
|
||||
"core": "candidate",
|
||||
"browser": "153.0.8010.12",
|
||||
"pluginSha256": "475afec2cd440de885520c6aee4165a7453fce8e03a920a7d809b8fe93db510c",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
},
|
||||
{
|
||||
"project": "firefox",
|
||||
"core": "published",
|
||||
"browser": "155.0",
|
||||
"pluginSha256": "475afec2cd440de885520c6aee4165a7453fce8e03a920a7d809b8fe93db510c",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
},
|
||||
{
|
||||
"project": "firefox",
|
||||
"core": "candidate",
|
||||
"browser": "155.0",
|
||||
"pluginSha256": "475afec2cd440de885520c6aee4165a7453fce8e03a920a7d809b8fe93db510c",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
},
|
||||
{
|
||||
"project": "webkit",
|
||||
"core": "published",
|
||||
"browser": "26.6",
|
||||
"pluginSha256": "475afec2cd440de885520c6aee4165a7453fce8e03a920a7d809b8fe93db510c",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
},
|
||||
{
|
||||
"project": "webkit",
|
||||
"core": "candidate",
|
||||
"browser": "26.6",
|
||||
"pluginSha256": "475afec2cd440de885520c6aee4165a7453fce8e03a920a7d809b8fe93db510c",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
}
|
||||
],
|
||||
"scope": "Source entity and lifecycle tests; final equivalent wrapper loop separately covered by full Node and actual artifact browser run"
|
||||
},
|
||||
{
|
||||
"path": "refactor/.cache/multi-entities-artifact-browser/report.json",
|
||||
"sha256": "0f8d3c53fd5616814ee9bf9cba1afb197dd3b956c0b5e84a8ce9cb390fcc1140",
|
||||
"stats": {
|
||||
"startTime": "2026-09-14T04:07:08.987Z",
|
||||
"duration": 15602.046,
|
||||
"expected": 6,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"observations": [
|
||||
{
|
||||
"project": "chromium",
|
||||
"core": "published",
|
||||
"browser": "153.0.8010.12",
|
||||
"pluginSha256": "e2192514ab4836a04c56420fa8c260efa657e2afa32f299d0cf7786c91adc39b",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
},
|
||||
{
|
||||
"project": "chromium",
|
||||
"core": "candidate",
|
||||
"browser": "153.0.8010.12",
|
||||
"pluginSha256": "e2192514ab4836a04c56420fa8c260efa657e2afa32f299d0cf7786c91adc39b",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
},
|
||||
{
|
||||
"project": "firefox",
|
||||
"core": "published",
|
||||
"browser": "155.0",
|
||||
"pluginSha256": "e2192514ab4836a04c56420fa8c260efa657e2afa32f299d0cf7786c91adc39b",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
},
|
||||
{
|
||||
"project": "firefox",
|
||||
"core": "candidate",
|
||||
"browser": "155.0",
|
||||
"pluginSha256": "e2192514ab4836a04c56420fa8c260efa657e2afa32f299d0cf7786c91adc39b",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
},
|
||||
{
|
||||
"project": "webkit",
|
||||
"core": "published",
|
||||
"browser": "26.6",
|
||||
"pluginSha256": "e2192514ab4836a04c56420fa8c260efa657e2afa32f299d0cf7786c91adc39b",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
},
|
||||
{
|
||||
"project": "webkit",
|
||||
"core": "candidate",
|
||||
"browser": "26.6",
|
||||
"pluginSha256": "e2192514ab4836a04c56420fa8c260efa657e2afa32f299d0cf7786c91adc39b",
|
||||
"native": [
|
||||
"A & < > | <b>literal</b> | <",
|
||||
"bold & safe tail <i>"
|
||||
],
|
||||
"literal": "A & < > | <b>literal</b> | <",
|
||||
"scope": "Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only"
|
||||
}
|
||||
],
|
||||
"scope": "Actual main UMD, published/candidate core, native fragment and HTML caption text"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
{
|
||||
"path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.js",
|
||||
"sha256": "e2192514ab4836a04c56420fa8c260efa657e2afa32f299d0cf7786c91adc39b",
|
||||
"docs": {
|
||||
"path": "docs/compiled/artplayer-plugin-multiple-subtitles.js",
|
||||
"sha256": "e2192514ab4836a04c56420fa8c260efa657e2afa32f299d0cf7786c91adc39b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.legacy.js",
|
||||
"sha256": "6e3716ea5e70a028b4fdb6648d1f8be151364bd6fd798719e3a5809924457c6e",
|
||||
"docs": {
|
||||
"path": "docs/compiled/artplayer-plugin-multiple-subtitles.legacy.js",
|
||||
"sha256": "6e3716ea5e70a028b4fdb6648d1f8be151364bd6fd798719e3a5809924457c6e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.mjs",
|
||||
"sha256": "8d1f36140e6c4ca64d6330621fd421f1323ed9fb58434a797cfa2afae6205b39",
|
||||
"docs": {
|
||||
"path": "docs/compiled/artplayer-plugin-multiple-subtitles.mjs",
|
||||
"sha256": "8d1f36140e6c4ca64d6330621fd421f1323ed9fb58434a797cfa2afae6205b39"
|
||||
}
|
||||
}
|
||||
],
|
||||
"checks": [
|
||||
{
|
||||
"path": "refactor/.cache/multi-entities-types.log",
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"exitCode": 0
|
||||
},
|
||||
{
|
||||
"path": "refactor/.cache/multi-entities-build.log",
|
||||
"sha256": "d352d68782fddd9e62989746c6e4f1a8e0310069115f6d779bdd13663cddfbfc",
|
||||
"exitCode": 0
|
||||
},
|
||||
{
|
||||
"path": "refactor/.cache/multi-entities-lint-final.log",
|
||||
"sha256": "c5b143fd109b076988b84670ce8fc536d4069edf71a512ad2990f926c63baaa1",
|
||||
"exitCode": 0
|
||||
},
|
||||
{
|
||||
"path": "refactor/.cache/multi-entities-final.log",
|
||||
"sha256": "e3bd33003aa4d5b47edd8da0966a1830656a4279e3b1cf519541e11bff9509ba",
|
||||
"exitCode": 0
|
||||
}
|
||||
],
|
||||
"initialLintFailure": {
|
||||
"path": "refactor/.cache/multi-entities-lint-initial.log",
|
||||
"sha256": "9202a274e0cca24968f932d2fac2a31ba602f652897d083b454573042bd68772"
|
||||
},
|
||||
"scope": [
|
||||
"Fix six existing named entities via configured vendor table; preserve lenient aliases and vendor body",
|
||||
"Escape literal cue text through existing onVttLoad pipeline; wrap semantic nodes outside annotation fields",
|
||||
"Nine historical implementations still reproduce entity and malformed-tag defects",
|
||||
"Public types, factory alias, Promise/selection ordering unchanged",
|
||||
"Desktop Windows engines only; full old-core/device/distribution gates remain tasks05/06",
|
||||
"No new dependency, lock/version change, push or publication"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
# PKG-MULTI-SUB-08: entity text and semantic tag wrapping
|
||||
|
||||
Baseline: 4acf85ea696a99f05fce71089711bc4a4ca0d5b6. This independent correction
|
||||
precedes task05's complete core/combination acceptance; it does not complete that
|
||||
task or publication readiness.
|
||||
|
||||
## Old behavior and corrections
|
||||
|
||||
Nine historical implementations reproduce `<`/`>`/`&` decoding with stray
|
||||
semicolons. The vendor's default table contains bare keys, while its exact-match
|
||||
branch looks for the semicolon-terminated key and falls through to partial-match
|
||||
handling. `text.ts` supplies both forms for the six existing named entities using
|
||||
the parser's already-supported constructor option. Bare/partial-name aliases,
|
||||
numeric references and unknown-reference handling retain the existing parser path.
|
||||
No upstream/vendor execution body or provenance snapshot is edited.
|
||||
|
||||
Correctly decoded text must remain literal after the existing onVttLoad unescape.
|
||||
Text values therefore receive a separate escaping layer before the serializer
|
||||
protects the complete VTT. Literal `<b>` and timestamp-shaped text remain visible
|
||||
text; `&lt;` displays `<` rather than decoding repeatedly. Real b/i/c/v/ruby
|
||||
cue nodes remain semantic nodes, and inline timestamps retain their numeric data.
|
||||
|
||||
Related tag coverage exposed a second historical bug: the old merge assigned an
|
||||
HTML wrapper to a semantic node's annotation field, yielding output such as
|
||||
`<b <div ...>undefined</div>>bold</b>`. Nine historical implementations now reproduce
|
||||
that exact malformed structure separately. Merge wraps prepared nodes with sibling
|
||||
opening/closing text nodes instead. CSS hooks remain `.art-subtitle-<name>`; markup
|
||||
annotations are preserved and source trees are still immutable across selections.
|
||||
Plain-text/timing output remains byte-matched outside these intentional fixes.
|
||||
|
||||
## Structure, compatibility and ownership
|
||||
|
||||
- `text.ts`: entity configuration and literal-text protection through the two
|
||||
existing serialization/HTML stages. It introduces no browser API or dependency.
|
||||
- `merge.ts`: recursively prepare text/timestamp/object nodes and wrap top-level
|
||||
nodes without changing annotations. A simple loop avoids requiring Array.flatMap
|
||||
in older environments. Parsing/selection order and original cue times remain.
|
||||
- Vendor parser/serializer, request/lifetime/render/caption code, public types,
|
||||
factory/default alias, tracks/reset, Promise behavior and distribution paths
|
||||
are unchanged. This is an authored adapter around a frozen vendor, not a vendor
|
||||
upgrade or an unexplained relaxation of its fingerprint check.
|
||||
|
||||
There are no new resources or lifetime responsibilities. Existing download, URL,
|
||||
destroy/restart and host-error checks still apply. Corrected entity/tag output is
|
||||
an intentional bug fix; malformed attributes and visible semicolons are not kept
|
||||
as compatibility requirements. Tests formerly comparing those erroneous serialized
|
||||
bytes move to explicit corrected text/markup expectations. Raw ampersands and
|
||||
malformed angle text retain a separate actual-old-versus-current visible-output
|
||||
comparison. No historical failure fixture is rewritten to pretend old releases
|
||||
were correct.
|
||||
|
||||
## Verification
|
||||
|
||||
Source/log/artifact hashes and actual counts are in
|
||||
[multiple-subtitles-entities-validation.json](../baselines/multiple-subtitles-entities-validation.json).
|
||||
|
||||
- The initial5 entity/rendering tests fail against the frozen pre-change bundle.
|
||||
The final expanded11-case file is rerun against both old and new artifacts.
|
||||
- Full package suite294 passes, including11 focused cases, immutable tree and
|
||||
timestamps,9 historical entity failures,9 historical malformed-tag failures,
|
||||
actual release contracts/types and complete unchanged vendor parity/notices.
|
||||
- Windows source browser suite42 passes: entity/markup native VTT and HTML display
|
||||
on published/candidate cores, plus existing playback, SRT, timestamps, selection,
|
||||
source replacement and cleanup. An early implementation used flatMap; the final
|
||||
equivalent loop has full Node coverage and actual built-browser verification.
|
||||
- Production main/legacy artifacts each run the focused Node suite; actual main
|
||||
also runs entity display in all3 engines on both core variants. UMD/legacy/ESM
|
||||
and matching docs copies are regenerated using the normal build.
|
||||
- Strict package TypeScript passes. Initial Unicode escape casing/list formatting
|
||||
lint errors are corrected; final root lint has only its one existing docs type
|
||||
warning. CI and final plan/risk checks are recorded separately.
|
||||
|
||||
This is desktop engine evidence, not physical Safari/mobile, all historical core
|
||||
versions or a remote CI run. Complete core combinations remain task05, distribution
|
||||
and demo acceptance task06. No lockfile, dependency, public declaration or version
|
||||
change. The package architecture and test maintenance documentation explain the
|
||||
adapter and corrected behavior. Revert the dedicated task08 commit, including
|
||||
generated outputs, to restore the prior implementation. Local commit only; no
|
||||
push or npm publication.
|
||||
+6
-4
@@ -2,9 +2,9 @@
|
||||
|
||||
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 246 项,范围 22 个包及工作区/示例。
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 247 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 56 / doing 16 / blocked 0 / done 174 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 56 / doing 16 / blocked 0 / done 175 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 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-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, PKG-JASSUB-07, PKG-JASSUB-08, PKG-JASSUB-09 |
|
||||
| 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-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-08, 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-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 |
|
||||
@@ -226,7 +226,8 @@
|
||||
| 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 | done |
|
||||
| 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-08 | artplayer-plugin-multiple-subtitles<br>修复字幕实体解码与嵌套标签包装 | PKG-MULTI-SUB-04, PKG-MULTI-SUB-07 | 使用解析器既有实体表入口修复多余分号;保护字面文本并以兄弟节点包装标签,保留CSS钩子和时间戳 | 冻结旧实现复现、实体/双重编码/标签/选择重置回归、新旧核心真实字幕显示通过;vendor原文与来源校验不变,类型/分发兼容 | M | done |
|
||||
| PKG-MULTI-SUB-05 | artplayer-plugin-multiple-subtitles<br>验证新旧核心和组合 | PKG-MULTI-SUB-04, CORE-22, PKG-MULTI-SUB-07, PKG-MULTI-SUB-08 | 与核心字幕/偏移/全屏组合,旧名称调用不变 | 最终核心与原支持范围核心分别通过;设备/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 |
|
||||
|
||||
## 5 包迁移:artplayer-plugin-jassub
|
||||
@@ -552,6 +553,7 @@
|
||||
- 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) [记录](baselines/multiple-subtitles-public-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-public-types.md) [记录](type-compatibility-policy.md) [记录](baselines/multiple-subtitles-approved-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-approved-types.md)
|
||||
- PKG-MULTI-SUB-07: [记录](baselines/multiple-subtitles-timestamps.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md)
|
||||
- PKG-MULTI-SUB-08: [记录](changes/2026-09-14-PKG-MULTI-SUB-08-entities.md) [记录](baselines/multiple-subtitles-entities-validation.json)
|
||||
- PKG-JASSUB-01: [记录](baselines/jassub-release.json) [记录](baselines/jassub-vendor.json) [记录](baselines/jassub-font-metadata.json) [记录](baselines/jassub-contract.md) [记录](baselines/jassub-contract-validation.json) [记录](changes/2026-09-13-PKG-JASSUB-01-baseline.md) [记录](baselines/jassub-provenance.json) [记录](baselines/jassub-provenance-validation.json) [记录](changes/2026-09-14-PKG-JASSUB-01-provenance.md)
|
||||
- PKG-JASSUB-02: [记录](changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md) [记录](baselines/jassub-failures-validation.json)
|
||||
- PKG-JASSUB-03: [记录](changes/2026-09-14-PKG-JASSUB-03-registration.md) [记录](baselines/jassub-registration-validation.json)
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-MULTI-SUB-08 字幕实体与标签包装完成
|
||||
|
||||
通过解析器既有实体表入口修复标准命名实体后多余分号,保留原始vendor文件与
|
||||
来源校验;独立转义字面文本,避免实体形式的标签/时间戳被再次解释。同步修复
|
||||
把包装节点塞进b等标签注释字段的问题,改为节点外包装,保留CSS钩子与时间戳。
|
||||
九套历史实现分别复现两类旧缺陷,最终11项新用例在旧bundle中8红3绿,候选及
|
||||
实际main/legacy各11项全绿。完整插件294项、源码浏览器42项、实际main浏览器
|
||||
6项、CI50项通过;严格TS与vendor校验通过,lint 1既有warning。三格式与docs
|
||||
副本正常重建,公开类型/依赖/版本未改。详见[记录](changes/2026-09-14-PKG-MULTI-SUB-08-entities.md)
|
||||
和[证据](baselines/multiple-subtitles-entities-validation.json)。关闭实体与标签包装
|
||||
风险,247项变为175 done、16 doing、56 todo;这不完成全部旧核心/真机/分发
|
||||
验收,下一步继续任务05组合验证。独立本地提交,无推送/发布。
|
||||
|
||||
## PKG-AUTO-THUMB-10 编码归属与取消时限完成
|
||||
|
||||
JPEG编码拆为独立TS模块,原来帧等待结束后编码无时限,现有独立30秒编码时限。
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
| 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 | 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 |
|
||||
| MULTI-SUB-ENTITY-01 | resolved / 已复现 | Vendored cue entity decoding retains semicolons after lt, gt and amp entities | PKG-MULTI-SUB-05, PKG-MULTI-SUB-08 |
|
||||
| JASSUB-TYPE-01 | open / 已复现 | JASSUB declared required URLs, Promise methods and resize parameter order differ from actual historical behavior | PKG-JASSUB-04 |
|
||||
| JASSUB-EXPORT-01 | open / 已复现 | JASSUB historical CommonJS object.default changes to a direct factory in 1.1.0 | PKG-JASSUB-04, PKG-JASSUB-06 |
|
||||
| CORE-DEFAULTS-SSR-01 | resolved / 已复现 | Reading static defaults throws when navigator is absent | CORE-25 |
|
||||
@@ -264,3 +264,4 @@
|
||||
| SCAFFOLD-OUTPUT-01 | resolved / 已复现 | Plugin generation overwrites existing examples and links to a different example name | MOD-PLUGIN-01 |
|
||||
| DEV-SERVOR-PORT-01 | resolved / 已复现 | Servor exits the entire dev process with status zero when the requested port is occupied | MOD-DEV-01 |
|
||||
| AUTO-THUMB-ENCODE-TIMEOUT-01 | resolved / 源码/产物事实 | Pending JPEG encoding has no bounded lifetime after frame readiness finishes | PKG-AUTO-THUMB-10 |
|
||||
| MULTI-SUB-MARKUP-01 | resolved / 已复现 | Semantic cue nodes receive HTML wrappers inside their annotation fields | PKG-MULTI-SUB-08 |
|
||||
+36
-5
@@ -4521,17 +4521,26 @@
|
||||
"id": "MULTI-SUB-ENTITY-01",
|
||||
"title": "Vendored cue entity decoding retains semicolons after lt, gt and amp entities",
|
||||
"confirmation": "reproduced",
|
||||
"status": "open",
|
||||
"status": "resolved",
|
||||
"owners": [
|
||||
"PKG-MULTI-SUB-05"
|
||||
"PKG-MULTI-SUB-05",
|
||||
"PKG-MULTI-SUB-08"
|
||||
],
|
||||
"evidence": [
|
||||
"test/multiple-subtitles-failures.test.js",
|
||||
"refactor/baselines/multiple-subtitles-timestamps.json",
|
||||
"refactor/changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md"
|
||||
"refactor/changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md",
|
||||
"refactor/changes/2026-09-14-PKG-MULTI-SUB-08-entities.md",
|
||||
"refactor/baselines/multiple-subtitles-entities-validation.json"
|
||||
],
|
||||
"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."
|
||||
"compatibleResolution": "Supply exact named entities through the existing parser constructor option, preserve historical lenient aliases and the frozen vendor body, and protect literal text through serialization/unescape.",
|
||||
"closureCriteria": "Nine historical implementations reproduce the defect; candidate entity/markup regressions and real subtitle display pass, with vendor adaptation and provenance documentation updated.",
|
||||
"workspaceState": "Nine historical implementations retain stray semicolons; candidate table adapter and text escaping fix six named references, double encoding and literal tags. Actual native VTT and HTML display pass in all3 Windows engines on published/candidate cores. Complete historical-core/device combinations remain task05.",
|
||||
"resolutionEvidence": [
|
||||
"refactor/changes/2026-09-14-PKG-MULTI-SUB-08-entities.md",
|
||||
"refactor/baselines/multiple-subtitles-entities-validation.json"
|
||||
],
|
||||
"resolutionRationale": "Eleven focused cases,294 package tests,42 source browser and6 actual built-main browser cases pass. The parser executable body is unchanged and its full upstream adaptation/notices checks remain enforced; the authored configuration adapter resolves the plugin defect without a vendor patch."
|
||||
},
|
||||
{
|
||||
"id": "JASSUB-TYPE-01",
|
||||
@@ -5962,6 +5971,28 @@
|
||||
"test/browser/auto-thumbnail-lifecycle.spec.js"
|
||||
],
|
||||
"resolutionRationale": "Nine controlled cases and real native JPEG tests cover held/late callbacks and decoder/canvas disposal. Actual production UMD passes11 Node and9 browser checks; previous preview survives timeout. Very slow encoding can now be discarded explicitly; public options, registration and frame-time formula remain intact."
|
||||
},
|
||||
{
|
||||
"id": "MULTI-SUB-MARKUP-01",
|
||||
"title": "Semantic cue nodes receive HTML wrappers inside their annotation fields",
|
||||
"confirmation": "reproduced",
|
||||
"status": "resolved",
|
||||
"owners": [
|
||||
"PKG-MULTI-SUB-08"
|
||||
],
|
||||
"evidence": [
|
||||
"test/multiple-subtitles-failures.test.js",
|
||||
"refactor/changes/2026-09-14-PKG-MULTI-SUB-08-entities.md",
|
||||
"refactor/baselines/multiple-subtitles-entities-validation.json"
|
||||
],
|
||||
"compatibleResolution": "Wrap prepared cue nodes with sibling opening/closing nodes, retaining annotations, CSS hooks, nested timestamps and escaped literal text.",
|
||||
"closureCriteria": "Nine historical implementations reproduce malformed b attributes and undefined text; candidate tag/text/selection/reset and real published/candidate core display pass.",
|
||||
"workspaceState": "Nine frozen implementations serialize malformed b attributes containing an undefined wrapper. Authored merge now wraps nodes outside annotations. Vendor and public types are unchanged.",
|
||||
"resolutionEvidence": [
|
||||
"refactor/changes/2026-09-14-PKG-MULTI-SUB-08-entities.md",
|
||||
"refactor/baselines/multiple-subtitles-entities-validation.json"
|
||||
],
|
||||
"resolutionRationale": "Historical regressions retain old output; corrected b/c/v/ruby and text cases plus actual b/literal-tag native/HTML display pass. Immutable trees, timestamps and lifecycle tests remain green."
|
||||
}
|
||||
]
|
||||
}
|
||||
+22
-1
@@ -2377,6 +2377,26 @@
|
||||
"changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-MULTI-SUB-08",
|
||||
"phase": "5 包迁移:artplayer-plugin-multiple-subtitles",
|
||||
"title": "修复字幕实体解码与嵌套标签包装",
|
||||
"scope": [
|
||||
"artplayer-plugin-multiple-subtitles"
|
||||
],
|
||||
"dependsOn": [
|
||||
"PKG-MULTI-SUB-04",
|
||||
"PKG-MULTI-SUB-07"
|
||||
],
|
||||
"status": "done",
|
||||
"risk": "M",
|
||||
"deliverable": "使用解析器既有实体表入口修复多余分号;保护字面文本并以兄弟节点包装标签,保留CSS钩子和时间戳",
|
||||
"acceptance": "冻结旧实现复现、实体/双重编码/标签/选择重置回归、新旧核心真实字幕显示通过;vendor原文与来源校验不变,类型/分发兼容",
|
||||
"evidence": [
|
||||
"changes/2026-09-14-PKG-MULTI-SUB-08-entities.md",
|
||||
"baselines/multiple-subtitles-entities-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-MULTI-SUB-05",
|
||||
"phase": "5 包迁移:artplayer-plugin-multiple-subtitles",
|
||||
@@ -2387,7 +2407,8 @@
|
||||
"dependsOn": [
|
||||
"PKG-MULTI-SUB-04",
|
||||
"CORE-22",
|
||||
"PKG-MULTI-SUB-07"
|
||||
"PKG-MULTI-SUB-07",
|
||||
"PKG-MULTI-SUB-08"
|
||||
],
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Tests and fixture ownership
|
||||
|
||||
`test/multiple-subtitles-entities.test.js` checks named entities, literal tags,
|
||||
double encoding, nested semantic tags and selection/reset through the real plugin.
|
||||
The vendor parity suite stays unchanged: the plugin uses the parser's existing
|
||||
entity-table option. Historical malformed-tag and stray-semicolon tests continue
|
||||
to assert old defects; corrected cases have explicit rendering expectations instead
|
||||
of treating those old serialized bytes as a required contract. Browser entity
|
||||
tests compare native VTT fragments and actual HTML captions on both core variants.
|
||||
|
||||
`yarn test:library` runs actual library builds, build-input freshness checks and the
|
||||
scaffold's generated consumers. The new build modules live in `scripts/library/`;
|
||||
`yarn typecheck:library` checks them and the retained JS/MJS entrypoints. Browser
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { multipleSubtitlesCandidate } from '../helpers/multiple-subtitles.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const implementation = await multipleSubtitlesCandidate()
|
||||
const literal = 'A & < > \u200E \u200F \u00A0 | <b>literal</b> | <'
|
||||
const vtt = text => `WEBVTT\n\n00:00.000 --> 00:08.000\n${text}\n`
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
test(`Multiple subtitles ${core} core decodes entities once and preserves literal and semantic tags`, async ({ page, browser }, testInfo) => {
|
||||
const errors = []
|
||||
page.on('pageerror', error => errors.push(error.message))
|
||||
await page.route('**/test/entities-a.vtt', route => route.fulfill({ body: vtt('A & < > ‎ ‏ | <b>literal</b> | &lt;'), contentType: 'text/vtt' }))
|
||||
await page.route('**/test/entities-b.vtt', route => route.fulfill({ body: vtt('<b>bold & safe</b> tail <i>'), contentType: 'text/vtt' }))
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
await page.addScriptTag({ content: implementation.code })
|
||||
await page.evaluate(() => window.createPlayer('/test/pattern.mp4'))
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await page.evaluate(async () => {
|
||||
await window.art.plugins.add(window.artplayerPluginMultipleSubtitles({ subtitles: [{ name: 'a', url: '/test/entities-a.vtt' }, { name: 'b', url: '/test/entities-b.vtt' }] }))
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.subtitle.cues.length)).toBe(2)
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
|
||||
await page.locator('#pause').click()
|
||||
await expect.poll(() => page.locator('.art-subtitle-a').textContent()).toBe(literal)
|
||||
await expect(page.locator('.art-subtitle-a b')).toHaveCount(0)
|
||||
await expect(page.locator('.art-subtitle-b b')).toHaveText('bold & safe')
|
||||
await expect(page.locator('.art-subtitle-b i')).toHaveCount(0)
|
||||
expect(await page.locator('.art-subtitle-b').allTextContents()).toEqual(['bold & safe', ' tail <i>'])
|
||||
const native = await page.evaluate(() => window.art.subtitle.cues.map(cue => cue.getCueAsHTML().textContent))
|
||||
expect(native).toEqual([literal, 'bold & safe tail <i>'])
|
||||
await page.evaluate(() => window.art.plugins.multipleSubtitles.tracks(['a']))
|
||||
await expect(page.locator('.art-subtitle-b')).toHaveCount(0)
|
||||
await expect.poll(() => page.locator('.art-subtitle-a').textContent()).toBe(literal)
|
||||
await page.evaluate(() => window.art.plugins.multipleSubtitles.reset())
|
||||
await expect(page.locator('.art-subtitle-b b')).toHaveText('bold & safe')
|
||||
await testInfo.attach('entities.json', { contentType: 'application/json', body: JSON.stringify({ core, browser: browser.version(), pluginSha256: hash(implementation.code), native, literal, scope: 'Actual native VTT and HTML caption display, playback, selection/reset; desktop browser engines only' }) })
|
||||
await page.evaluate(() => window.art.destroy(false))
|
||||
expect(errors).toEqual([])
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import assert from 'node:assert/strict'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Text entity regressions use real built plugin serialization.
|
||||
import test from 'node:test'
|
||||
import { parseHTML } from 'linkedom'
|
||||
import { multipleSubtitlesCandidate, multipleSubtitlesEnvironment, multipleSubtitlesHistorical, subtitleVtt } from './helpers/multiple-subtitles.js'
|
||||
|
||||
const implementation = await multipleSubtitlesCandidate()
|
||||
for (const [input, expected] of [
|
||||
['& < > ‎ ‏ ', '& < > \u200E \u200F \u00A0'],
|
||||
['Literal <00:02.000> and <b>text</b>', 'Literal <00:02.000> and <b>text</b>'],
|
||||
['&lt; &amp; &gt;', '< & >'],
|
||||
['One &; two <;', 'One &; two <;'],
|
||||
['<b>bold & safe</b> tail <i>', 'bold & safe tail <i>'],
|
||||
['<c.red>color</c> <i>italic</i>', 'color italic'],
|
||||
['<v Speaker>voice</v>', 'voice'],
|
||||
['<ruby>text<rt>note</rt></ruby>', 'textnote'],
|
||||
['&unknown; &litude; &', '&unknown; &litude; &'],
|
||||
['A A', 'A A'],
|
||||
]) {
|
||||
test(`Multiple subtitles entity text remains literal after selection/reset: ${input}`, async () => {
|
||||
const env = multipleSubtitlesEnvironment(implementation, { responses: { 'a.vtt': subtitleVtt(input), 'b.vtt': subtitleVtt('Other') } })
|
||||
const result = await env.factory({ subtitles: [{ name: 'a', url: 'a.vtt' }, { name: 'b', url: 'b.vtt' }] })(env.art)
|
||||
for (const names of [['a'], ['b', 'a'], ['a', 'a']]) {
|
||||
result.tracks(names)
|
||||
const vtt = env.utils.unescape(await env.latestText())
|
||||
const text = vtt.split(/\n\d[^\n]*-->[^\n]*\n/).slice(1).join('')
|
||||
const { document } = parseHTML(`<html><body>${text}</body></html>`)
|
||||
assert.equal(document.body.textContent.trim(), names.map(name => name === 'a' ? expected : 'Other').join('\n\n').trim())
|
||||
if (input.includes('<i>'))
|
||||
assert.equal(document.querySelectorAll('i').length, 0)
|
||||
if (input.startsWith('<b>'))
|
||||
assert.equal(document.querySelector('b').textContent, 'bold & safe')
|
||||
}
|
||||
result.reset()
|
||||
assert((env.utils.unescape(await env.latestText())).includes('Other'))
|
||||
env.emit('destroy')
|
||||
})
|
||||
}
|
||||
|
||||
test('Multiple subtitles raw ampersand and malformed angle text retain historical visible output', async () => {
|
||||
const historical = (await multipleSubtitlesHistorical()).find(item => item.name === 'published-1.2.0-main')
|
||||
const output = []
|
||||
for (const code of [historical, implementation]) {
|
||||
const env = multipleSubtitlesEnvironment(code, { responses: { 'a.vtt': subtitleVtt('A & B < C > D') } })
|
||||
await env.factory({ subtitles: [{ name: 'a', url: 'a.vtt' }] })(env.art)
|
||||
const { document } = parseHTML(`<html><body>${env.utils.unescape(await env.latestText())}</body></html>`)
|
||||
output.push(document.body.textContent)
|
||||
env.emit('destroy')
|
||||
}
|
||||
assert.equal(output[1], output[0])
|
||||
})
|
||||
@@ -269,4 +269,10 @@ for (const implementation of implementations) {
|
||||
await env.factory(options(subtitle()))(env.art)
|
||||
assert(env.utils.unescape(await env.latestText()).includes('Literal <;00:02.000>; example &;'))
|
||||
})
|
||||
|
||||
test(`Multiple-subtitles ${implementation.name} failure baseline: semantic tag wrappers become malformed attributes`, async () => {
|
||||
const env = create({ responses: { 'a.vtt': subtitleVtt('<b>bold</b>') } })
|
||||
await env.factory(options(subtitle()))(env.art)
|
||||
assert(env.utils.unescape(await env.latestText()).includes('<b <div class="art-subtitle-a">undefined</div>>bold</b>'))
|
||||
})
|
||||
}
|
||||
@@ -31,13 +31,9 @@ test('Multiple subtitles merge: repeated selection serializes frozen trees witho
|
||||
assert.equal(JSON.stringify(trees), original)
|
||||
})
|
||||
|
||||
test('Multiple subtitles merge: actual released serializer output is preserved for text, markup and timing cases', async () => {
|
||||
test('Multiple subtitles merge: released serializer output is preserved outside corrected text escaping and tag wrappers', async () => {
|
||||
const samples = [
|
||||
subtitleVtt('A & B < C > D'),
|
||||
subtitleVtt('<b>bold</b> tail'),
|
||||
subtitleVtt('<c.red>color</c> <i>italic</i>'),
|
||||
subtitleVtt('<v Speaker>voice</v>'),
|
||||
subtitleVtt('<ruby>text<rt>note</rt></ruby>'),
|
||||
subtitleVtt('Plain text'),
|
||||
subtitleVtt('line one\nline two'),
|
||||
'WEBVTT\n\n00:02.000 --> 00:05.000\nLater\n\n00:00.000 --> 00:03.000\nEarlier\n',
|
||||
subtitleVtt('best effort').replace('WEBVTT', 'BAD HEADER'),
|
||||
|
||||
Reference in new issue
Block a user