From 318de37e8a1be59942facc62d98e4847b0ef4891 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Mon, 14 Sep 2026 13:42:46 +0800 Subject: [PATCH] fix(multiple-subtitles): repair old ASS conversion line breaks [PKG-MULTI-SUB-11] --- .../artplayer-plugin-multiple-subtitles.js | 2 +- ...player-plugin-multiple-subtitles.legacy.js | 2 +- .../artplayer-plugin-multiple-subtitles.mjs | 27 +- package.json | 4 +- .../ARCHITECTURE.md | 13 +- .../artplayer-plugin-multiple-subtitles.js | 2 +- ...player-plugin-multiple-subtitles.legacy.js | 2 +- .../artplayer-plugin-multiple-subtitles.mjs | 27 +- .../src/ass-conversion.ts | 26 + .../src/request.ts | 3 +- .../multiple-subtitles-ass-validation.json | 2875 +++++++++++++++++ ...6-09-14-PKG-MULTI-SUB-11-ass-conversion.md | 74 + refactor/plan.md | 5 +- refactor/progress.md | 14 + refactor/risk-table.md | 2 +- refactor/risks.json | 19 +- refactor/tasks.json | 7 +- test/README.md | 7 + test/browser/README.md | 12 +- test/browser/multiple-subtitles-ass.spec.js | 58 + .../browser/multiple-subtitles-legacy.spec.js | 5 +- test/multiple-subtitles-ass.test.js | 109 + 22 files changed, 3268 insertions(+), 27 deletions(-) create mode 100644 packages/artplayer-plugin-multiple-subtitles/src/ass-conversion.ts create mode 100644 refactor/baselines/multiple-subtitles-ass-validation.json create mode 100644 refactor/changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md create mode 100644 test/browser/multiple-subtitles-ass.spec.js create mode 100644 test/multiple-subtitles-ass.test.js diff --git a/docs/compiled/artplayer-plugin-multiple-subtitles.js b/docs/compiled/artplayer-plugin-multiple-subtitles.js index 2b6e22918..3c16d0790 100644 --- a/docs/compiled/artplayer-plugin-multiple-subtitles.js +++ b/docs/compiled/artplayer-plugin-multiple-subtitles.js @@ -55,4 +55,4 @@ * * d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},t=function(t){t||(t={"&":"&","<":"<",">":">","&lrm":"\u200e","&rlm":"\u200f"," ":"\xa0"}),this.entities=t,this.parse=function(s,a){s=s.replace(/\0/g,"\ufffd");var r=Date.now(),o=0,l=s.split(/\r\n|\r|\n/),c=!1,f=[],u=[],d=[];function p(e,t){d.push({message:e,line:o+1,col:t})}var m=l[o],g=m.length,h="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(gb&&" "!==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 L=new i(x.text,p,a,t);x.tree=L.parse(x.startTime,x.endTime),u.push(x)}else for(x=null,o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}}return u.sort(function(e,t){return e.startTimet.startTime?1:e.endTime>t.endTime?-1:e.endTime2||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"==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;r100)){a("Line position cannot be >100%.");continue}if(""===d||isNaN(d)||!isFinite(d)){a("Line position needs to be a number");continue}if(void 0!==u){if(!["start","center","end"].includes(u)){a("Line alignment needs to be one of start, center or end");continue}t.lineAlign=u}t.snapToLines=!p,t.linePosition=parseFloat(d),parseFloat(d).toString()!==d&&(t.nonSerializable=!0)}else if("position"==l){if(/,/.test(c)){f=c.split(","),c=f[0];var m=f[1]}if("%"!=c[c.length-1]){a("Text position must be a percentage.");continue}if(parseInt(c,10)>100||parseInt(c,10)<0){a("Text position needs to be between 0 and 100%.");continue}if(""===(d=c.slice(0,c.length-1))||isNaN(d)||!isFinite(d)){a("Line position needs to be a number");continue}if(void 0!==m){if(!["line-left","center","line-right"].includes(m)){a("Position alignment needs to be one of line-left, center or line-right");continue}t.positionAlign=m}t.textPosition=parseFloat(d)}else if("size"==l){if("%"!=c[c.length-1]){a("Size must be a percentage.");continue}if(parseInt(c,10)>100){a("Size cannot be >100%.");continue}var g=c.slice(0,c.length-1);if(void 0===g||""===g||isNaN(g)){a("Size needs to be a number"),g=100;continue}if((g=parseFloat(g))<0||g>100){a("Size needs to be between 0 and 100%.");continue}t.size=g}else if("align"==l){var h=["start","center","end","left","right"];if(-1==h.indexOf(c)){a("Alignment can only be set to one of "+h.join(", ")+".");continue}t.alignment=c}else a("Invalid setting.")}}(e.substring(s),t),!0}else a("No valid timestamp separator found.");else a("No valid timestamp separator found.");else a("No valid timestamp separator found.")},this.parseTimestamp=function(){var t=l();if(void 0==e[s])return t;a("Timestamp must not have trailing characters.")}},i=function(e,t,i,s){this.entities=s;var a=this,r=(e=e,0),o=function(e){"metadata"!=i&&t(e,r+1)};function l(){for(var t="data",n="",i="",l=[];void 0!=e[r-1]||0==r;){var c=e[r];if("data"==t)if("&"==c)i=c,t="escape";else if("<"==c&&""==n)t="tag";else{if("<"==c||void 0==c)return["text",n];n+=c}else if("escape"==t){if("<"==c||void 0==c){let e;return o("Incorrect escape."),(e=i.match(/^&#([0-9]+)$/))?n+=String.fromCharCode(e[1]):a.entities[i]?n+=a.entities[i]:n+=i,["text",n]}if("&"==c)o("Incorrect escape."),n+=i,i=c;else if(/[a-z#0-9]/i.test(c))i+=c;else if(";"==c){let e;(e=i.match(/^&#(x?[0-9]+)$/))?n+=String.fromCharCode("0"+e[1]):a.entities[i+c]?n+=a.entities[i+c]:(e=Object.keys(s).find(e=>i.startsWith(e)))?n+=a.entities[e]+i.slice(e.length)+c:(o("Incorrect escape."),n+=i+";"),t="data"}else o("Incorrect escape."),n+=i+c,t="data"}else if("tag"==t)if("\t"==c||"\n"==c||"\f"==c||" "==c)t="start tag annotation";else if("."==c)t="start tag class";else if("/"==c)t="end tag";else if(/\d/.test(c))n=c,t="timestamp tag";else{if(">"==c||void 0==c)return">"==c&&r++,["start tag","",[],""];n=c,t="start tag"}else if("start tag"==t)if("\t"==c||"\f"==c||" "==c)t="start tag annotation";else if("\n"==c)i=c,t="start tag annotation";else if("."==c)t="start tag class";else{if(">"==c||void 0==c)return">"==c&&r++,["start tag",n,[],""];n+=c}else if("start tag class"==t)if("\t"==c||"\f"==c||" "==c)i&&l.push(i),i="",t="start tag annotation";else if("\n"==c)i&&l.push(i),i=c,t="start tag annotation";else if("."==c)i&&l.push(i),i="";else{if(">"==c||void 0==c)return">"==c&&r++,i&&l.push(i),["start tag",n,l,""];i+=c}else if("start tag annotation"==t){if(">"==c||void 0==c)return">"==c&&r++,["start tag",n,l,i=i.split(/[\u0020\t\f\r\n]+/).filter(function(e){if(e)return!0}).join(" ")];i+=c}else if("end tag"==t){if(">"==c||void 0==c)return">"==c&&r++,["end tag",n];n+=c}else if("timestamp tag"==t){if(">"==c||void 0==c)return">"==c&&r++,["timestamp",n];n+=c}else o("Never happens.");r++}}this.parse=function(t,s){var a={children:[]},c=a,f=[];function u(e){c.children.push({type:"object",name:e[1],classes:e[2],children:[],parent:c}),c=c.children[c.children.length-1]}function d(e){for(var t=c;t;){if(t.name==e)return!0;t=t.parent}}for(;void 0!=e[r];){var p=l();if("text"==p[0])c.children.push({type:"text",value:p[1],parent:c});else if("start tag"==p[0]){"chapters"==i&&o("Start tags not allowed in chapter title text.");var m=p[1];"v"!=m&&"lang"!=m&&""!=p[3]&&o("Only and can have an annotation."),"c"==m||"i"==m||"b"==m||"u"==m||"ruby"==m||"rt"==m&&"ruby"==c.name?u(p):"v"==m?(d("v")&&o(" cannot be nested inside itself."),u(p),c.value=p[3],p[3]||o(" requires an annotation.")):"lang"==m?(u(p),c.value=p[3]):o("Incorrect start tag.")}else if("end tag"==p[0])"chapters"==i&&o("End tags not allowed in chapter title text."),p[1]==c.name?c=c.parent:"ruby"==p[1]&&"rt"==c.name?c=c.parent.parent:o("Incorrect end tag.");else if("timestamp"==p[0]){"chapters"==i&&o("Timestamp not allowed in chapter title text.");var g=new n(p[1],o).parseTimestamp();void 0!=g&&((g<=t||g>=s)&&o("Timestamp must be between start timestamp and end timestamp."),f.length>0&&f[f.length-1]>=g&&o("Timestamp must be greater than any previous timestamp."),c.children.push({type:"timestamp",value:g,parent:c}),f.push(g))}}for(;c.parent;)"v"!=c.name&&o("Required end tag missing."),c=c.parent;return function e(t){const n={...t};return t.children&&(n.children=t.children.map(e)),n.parent&&delete n.parent,n}(a)}},s=function(){function t(e){const t=("00"+1e3*(e-Math.floor(e)).toFixed(3)).slice(-3);let n=0,i=0,s=0;return e>=3600&&(n=Math.floor(e/3600)),i=Math.floor((e-3600*n)/60),s=Math.floor(e-3600*n-60*i),(n?n+":":"")+(""+i).padStart(2,"0")+":"+(""+s).padStart(2,"0")+"."+t}function n(e){for(var i="",s=0;s/g,">");else if("object"==a.type){if(i+="<"+a.name,a.classes)for(var r=0;r"}else"timestamp"==a.type?i+="<"+t(a.value)+">":i+="<"+a.value+">"}return i}function i(i){return(void 0!==i.id?i.id+"\n":"")+t(i.startTime)+" --\x3e "+t(i.endTime)+function(t){var n="";const i=Object.keys(e).filter(n=>t[n]!==e[n]);return i.includes("direction")&&(n+=" vertical:"+t.direction),i.includes("alignment")&&(n+=" align:"+t.alignment),i.includes("size")&&(n+=" size:"+t.size+"%"),(i.includes("lineAlign")||i.includes("linePosition"))&&(n+=" line:"+t.linePosition+(t.snapToLines?"":"%")+(t.lineAlign&&t.lineAlign!=e.lineAlign?","+t.lineAlign:"")),(i.includes("textPosition")||i.includes("positionAlign"))&&(n+=" position:"+t.textPosition+"%"+(t.positionAlign&&t.positionAlign!==e.positionAlign?","+t.positionAlign:"")),n}(i)+"\n"+n(i.tree.children)+"\n\n"}function s(e){return"STYLE\n"+e+"\n\n"}this.serialize=function(e,t){var n="WEBVTT\n\n";if(t)for(var a=0;a",">":">","&lrm":"\u200e","‎":"\u200e","&rlm":"\u200f","‏":"\u200f"," ":"\xa0"," ":"\xa0"};const r="artplayer-multiple-subtitles-timestamp";function o(e){return{type:"object",name:"c",classes:[r],children:[e]}}function l(e){return"timestamp"===e.type?o(e):"object"===e.type?{...e,children:e.children.map(l)}:{...e,value:(t=e.value,t.replace(/&/g,"&").replace(//g,">"))};var t}function c(e,t){const n=!!e.subtitle&&"activeCue"in e.subtitle&&!("activeCues"in e.subtitle),i=n?"subtitleUpdate":"subtitleAfterUpdate";function s(){if(!t.closed){n&&function(e){const t=e.subtitle?.textTrack?.activeCues;if(!t||t.length<2)return;const n=Array.from(t,e=>"text"in e&&"string"===typeof e.text?e.text:""),i=e.template.$subtitle;if(e.option?.subtitle?.escape){i.innerHTML="";for(const e of n)for(const t of e.split(/\r?\n/)){const e=i.ownerDocument.createElement("div");e.className="art-subtitle-line",e.textContent=t,i.appendChild(e)}}else i.innerHTML=n.join("")}(e);for(const t of Array.from(e.template.$subtitle.getElementsByTagName(`c.${r}`))){const e=t.firstChild;3===e?.nodeType&&(e.nodeValue=(e.nodeValue||"").replace(/^<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/,"")),t.replaceWith(...Array.from(t.childNodes))}}}t.closed||(t.own(()=>e.off(i,s)),e.on(i,s))}function f(e,t,n){let i=null;return s=>{if(t.closed)return;const a=URL.createObjectURL(new Blob([s],{type:"text/vtt"})),r=function(e){let n=!1,i=()=>{};const s={free(){n||(n=!0,i(),URL.revokeObjectURL(e))}};return i=t.own(s.free),s}(a);if(t.closed)return;const o=i;let l,c,f=!1;i=r;try{if(l=e.option.subtitle,c=l.escape,t.closed||i!==r)return;l.escape=!1,f=!0;const s={...l,url:a,type:"vtt",onVttLoad:n};if(t.closed||i!==r)return;const o=e.subtitle.init(s);Promise.resolve(o).catch(e=>{i===r&&(i=null,r.free()),t.closed||console.warn("Failed to initialize multiple subtitles:",e)})}catch(u){throw i===r&&(i=o,f&&!1===l.escape&&(l.escape=c)),r.free(),u}finally{o!==i&&o?.free(),r!==i&&r.free()}}}function u({subtitles:e=[]}){return async n=>{const{unescape:i,getExt:r,srtToVtt:u,assToVtt:d}=n.constructor.utils,p=function(e){let t=Boolean(e.isDestroy);const n=new Set;let i;const s=new Promise(e=>{i=e});function a(e){try{e()}catch(t){console.warn("Failed to clean up multiple subtitles:",t)}}const r={get closed(){return t},own:e=>(t?a(e):n.add(e),()=>n.delete(e)),wait:e=>Promise.race([e,s]),dispose(){if(t)return;t=!0,i();const e=[...n].reverse();n.clear();for(const t of e)a(t)}};if(!t){r.own(()=>e.off("destroy",r.dispose));try{e.on("destroy",r.dispose)}catch(o){throw r.dispose(),o}}return r}(n),m=f(n,p,i);let g=[];function h(e){p.closed||m(function(e){const t=[];for(const n of e){const e=n;for(const n of e.cues){const i=[];for(const t of n.tree.children)"timestamp"===t.type?i.push(o(t)):i.push({type:"text",value:`
`},l(t),{type:"text",value:"
"});t.push({...n,tree:{...n.tree,children:i}})}}return(new s).serialize(t)}(e))}const v={name:"multipleSubtitles",tracks(e=[]){p.closed||h(e.map(e=>g.find(t=>t.name===e)))},reset(){h(g)}};try{if(p.closed)return v;const i=await Promise.all(e.map(e=>async function(e,{getExt:t,srtToVtt:n,assToVtt:i},s){if(s.closed)return;const a="function"===typeof AbortController?new AbortController:null,r=s.own(()=>a?.abort());try{const r=e.url;if(s.closed)return;const o=await s.wait(a?fetch(r,{signal:a.signal}):fetch(r));if(s.closed)return;if(!1===o.ok)throw new Error(`Failed to fetch multiple subtitles: HTTP ${o.status}`);const l=await s.wait(o.arrayBuffer());if(s.closed)return;const c=new TextDecoder(e.encoding||"utf-8").decode(l);switch(e.type||t(e.url)){case"srt":return n(c);case"ass":return i(c);case"vtt":return c;default:return""}}catch(o){throw a?.abort(),o}finally{r()}}(e,{getExt:r,srtToVtt:u,assToVtt:d},p)));return p.closed||(g=function(e,n){const i=new t(a);return e.map((e,t)=>({...i.parse(e,"metadata"),url:n[t].url,name:n[t].name}))}(i,e),c(n,p),h(g)),v}catch(b){throw p.dispose(),b}}}return Object.assign(u,{default:u})}); +!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),(gb&&" "!==m[b]&&"\t"!==m[b])&&p('No valid signature. (File needs to start with "WEBVTT".)'),o++;""!=l[o]&&void 0!=l[o];){if(p("No blank line after the signature."),-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}for(;void 0!=l[o];){for(var x;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;x=Object.assign({},e,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var T=!0;if(-1==l[o].indexOf("--\x3e")){if(x.id=l[o],/^NOTE($|[ \t])/.test(x.id)){for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&p("Cannot have timestamp in a comment."),o++;continue}if(/^STYLE($|[ \t])/.test(x.id)){var y=[],w=!1;for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&(p("Cannot have timestamp in a style block."),w=!0),y.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(y.join("\n"));continue}if(""==l[++o]||void 0==l[o]){p("Cue identifier cannot be standalone.");continue}if(-1==l[o].indexOf("--\x3e")){T=!1,p("Cue identifier needs to be followed by timestamp.");continue}}c=!1;var $=new n(l[o],p),A=0;if(u.length>0&&(A=u[u.length-1].startTime),!T||$.parse(x,A)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=x.text&&(x.text+="\n"),x.text+=l[o],o++}var O=new i(x.text,p,a,t);x.tree=O.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.startTimet.startTime?1:e.endTime>t.endTime?-1:e.endTime2||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"==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;r100)){a("Line position cannot be >100%.");continue}if(""===d||isNaN(d)||!isFinite(d)){a("Line position needs to be a number");continue}if(void 0!==u){if(!["start","center","end"].includes(u)){a("Line alignment needs to be one of start, center or end");continue}t.lineAlign=u}t.snapToLines=!p,t.linePosition=parseFloat(d),parseFloat(d).toString()!==d&&(t.nonSerializable=!0)}else if("position"==l){if(/,/.test(c)){f=c.split(","),c=f[0];var m=f[1]}if("%"!=c[c.length-1]){a("Text position must be a percentage.");continue}if(parseInt(c,10)>100||parseInt(c,10)<0){a("Text position needs to be between 0 and 100%.");continue}if(""===(d=c.slice(0,c.length-1))||isNaN(d)||!isFinite(d)){a("Line position needs to be a number");continue}if(void 0!==m){if(!["line-left","center","line-right"].includes(m)){a("Position alignment needs to be one of line-left, center or line-right");continue}t.positionAlign=m}t.textPosition=parseFloat(d)}else if("size"==l){if("%"!=c[c.length-1]){a("Size must be a percentage.");continue}if(parseInt(c,10)>100){a("Size cannot be >100%.");continue}var g=c.slice(0,c.length-1);if(void 0===g||""===g||isNaN(g)){a("Size needs to be a number"),g=100;continue}if((g=parseFloat(g))<0||g>100){a("Size needs to be between 0 and 100%.");continue}t.size=g}else if("align"==l){var h=["start","center","end","left","right"];if(-1==h.indexOf(c)){a("Alignment can only be set to one of "+h.join(", ")+".");continue}t.alignment=c}else a("Invalid setting.")}}(e.substring(s),t),!0}else a("No valid timestamp separator found.");else a("No valid timestamp separator found.");else a("No valid timestamp separator found.")},this.parseTimestamp=function(){var t=l();if(void 0==e[s])return t;a("Timestamp must not have trailing characters.")}},i=function(e,t,i,s){this.entities=s;var a=this,r=(e=e,0),o=function(e){"metadata"!=i&&t(e,r+1)};function l(){for(var t="data",n="",i="",l=[];void 0!=e[r-1]||0==r;){var c=e[r];if("data"==t)if("&"==c)i=c,t="escape";else if("<"==c&&""==n)t="tag";else{if("<"==c||void 0==c)return["text",n];n+=c}else if("escape"==t){if("<"==c||void 0==c){let e;return o("Incorrect escape."),(e=i.match(/^&#([0-9]+)$/))?n+=String.fromCharCode(e[1]):a.entities[i]?n+=a.entities[i]:n+=i,["text",n]}if("&"==c)o("Incorrect escape."),n+=i,i=c;else if(/[a-z#0-9]/i.test(c))i+=c;else if(";"==c){let e;(e=i.match(/^&#(x?[0-9]+)$/))?n+=String.fromCharCode("0"+e[1]):a.entities[i+c]?n+=a.entities[i+c]:(e=Object.keys(s).find(e=>i.startsWith(e)))?n+=a.entities[e]+i.slice(e.length)+c:(o("Incorrect escape."),n+=i+";"),t="data"}else o("Incorrect escape."),n+=i+c,t="data"}else if("tag"==t)if("\t"==c||"\n"==c||"\f"==c||" "==c)t="start tag annotation";else if("."==c)t="start tag class";else if("/"==c)t="end tag";else if(/\d/.test(c))n=c,t="timestamp tag";else{if(">"==c||void 0==c)return">"==c&&r++,["start tag","",[],""];n=c,t="start tag"}else if("start tag"==t)if("\t"==c||"\f"==c||" "==c)t="start tag annotation";else if("\n"==c)i=c,t="start tag annotation";else if("."==c)t="start tag class";else{if(">"==c||void 0==c)return">"==c&&r++,["start tag",n,[],""];n+=c}else if("start tag class"==t)if("\t"==c||"\f"==c||" "==c)i&&l.push(i),i="",t="start tag annotation";else if("\n"==c)i&&l.push(i),i=c,t="start tag annotation";else if("."==c)i&&l.push(i),i="";else{if(">"==c||void 0==c)return">"==c&&r++,i&&l.push(i),["start tag",n,l,""];i+=c}else if("start tag annotation"==t){if(">"==c||void 0==c)return">"==c&&r++,["start tag",n,l,i=i.split(/[\u0020\t\f\r\n]+/).filter(function(e){if(e)return!0}).join(" ")];i+=c}else if("end tag"==t){if(">"==c||void 0==c)return">"==c&&r++,["end tag",n];n+=c}else if("timestamp tag"==t){if(">"==c||void 0==c)return">"==c&&r++,["timestamp",n];n+=c}else o("Never happens.");r++}}this.parse=function(t,s){var a={children:[]},c=a,f=[];function u(e){c.children.push({type:"object",name:e[1],classes:e[2],children:[],parent:c}),c=c.children[c.children.length-1]}function d(e){for(var t=c;t;){if(t.name==e)return!0;t=t.parent}}for(;void 0!=e[r];){var p=l();if("text"==p[0])c.children.push({type:"text",value:p[1],parent:c});else if("start tag"==p[0]){"chapters"==i&&o("Start tags not allowed in chapter title text.");var m=p[1];"v"!=m&&"lang"!=m&&""!=p[3]&&o("Only and can have an annotation."),"c"==m||"i"==m||"b"==m||"u"==m||"ruby"==m||"rt"==m&&"ruby"==c.name?u(p):"v"==m?(d("v")&&o(" cannot be nested inside itself."),u(p),c.value=p[3],p[3]||o(" requires an annotation.")):"lang"==m?(u(p),c.value=p[3]):o("Incorrect start tag.")}else if("end tag"==p[0])"chapters"==i&&o("End tags not allowed in chapter title text."),p[1]==c.name?c=c.parent:"ruby"==p[1]&&"rt"==c.name?c=c.parent.parent:o("Incorrect end tag.");else if("timestamp"==p[0]){"chapters"==i&&o("Timestamp not allowed in chapter title text.");var g=new n(p[1],o).parseTimestamp();void 0!=g&&((g<=t||g>=s)&&o("Timestamp must be between start timestamp and end timestamp."),f.length>0&&f[f.length-1]>=g&&o("Timestamp must be greater than any previous timestamp."),c.children.push({type:"timestamp",value:g,parent:c}),f.push(g))}}for(;c.parent;)"v"!=c.name&&o("Required end tag missing."),c=c.parent;return function e(t){const n={...t};return t.children&&(n.children=t.children.map(e)),n.parent&&delete n.parent,n}(a)}},s=function(){function t(e){const t=("00"+1e3*(e-Math.floor(e)).toFixed(3)).slice(-3);let n=0,i=0,s=0;return e>=3600&&(n=Math.floor(e/3600)),i=Math.floor((e-3600*n)/60),s=Math.floor(e-3600*n-60*i),(n?n+":":"")+(""+i).padStart(2,"0")+":"+(""+s).padStart(2,"0")+"."+t}function n(e){for(var i="",s=0;s/g,">");else if("object"==a.type){if(i+="<"+a.name,a.classes)for(var r=0;r"}else"timestamp"==a.type?i+="<"+t(a.value)+">":i+="<"+a.value+">"}return i}function i(i){return(void 0!==i.id?i.id+"\n":"")+t(i.startTime)+" --\x3e "+t(i.endTime)+function(t){var n="";const i=Object.keys(e).filter(n=>t[n]!==e[n]);return i.includes("direction")&&(n+=" vertical:"+t.direction),i.includes("alignment")&&(n+=" align:"+t.alignment),i.includes("size")&&(n+=" size:"+t.size+"%"),(i.includes("lineAlign")||i.includes("linePosition"))&&(n+=" line:"+t.linePosition+(t.snapToLines?"":"%")+(t.lineAlign&&t.lineAlign!=e.lineAlign?","+t.lineAlign:"")),(i.includes("textPosition")||i.includes("positionAlign"))&&(n+=" position:"+t.textPosition+"%"+(t.positionAlign&&t.positionAlign!==e.positionAlign?","+t.positionAlign:"")),n}(i)+"\n"+n(i.tree.children)+"\n\n"}function s(e){return"STYLE\n"+e+"\n\n"}this.serialize=function(e,t){var n="WEBVTT\n\n";if(t)for(var a=0;a",">":">","&lrm":"\u200e","‎":"\u200e","&rlm":"\u200f","‏":"\u200f"," ":"\xa0"," ":"\xa0"};const r="artplayer-multiple-subtitles-timestamp";function o(e){return{type:"object",name:"c",classes:[r],children:[e]}}function l(e){return"timestamp"===e.type?o(e):"object"===e.type?{...e,children:e.children.map(l)}:{...e,value:(t=e.value,t.replace(/&/g,"&").replace(//g,">"))};var t}function c(e,t){const n=!!e.subtitle&&"activeCue"in e.subtitle&&!("activeCues"in e.subtitle),i=n?"subtitleUpdate":"subtitleAfterUpdate";function s(){if(!t.closed){n&&function(e){const t=e.subtitle?.textTrack?.activeCues;if(!t||t.length<2)return;const n=Array.from(t,e=>"text"in e&&"string"===typeof e.text?e.text:""),i=e.template.$subtitle;if(e.option?.subtitle?.escape){i.innerHTML="";for(const e of n)for(const t of e.split(/\r?\n/)){const e=i.ownerDocument.createElement("div");e.className="art-subtitle-line",e.textContent=t,i.appendChild(e)}}else i.innerHTML=n.join("")}(e);for(const t of Array.from(e.template.$subtitle.getElementsByTagName(`c.${r}`))){const e=t.firstChild;3===e?.nodeType&&(e.nodeValue=(e.nodeValue||"").replace(/^<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/,"")),t.replaceWith(...Array.from(t.childNodes))}}}t.closed||(t.own(()=>e.off(i,s)),e.on(i,s))}function f(e,t,n){let i=null;return s=>{if(t.closed)return;const a=URL.createObjectURL(new Blob([s],{type:"text/vtt"})),r=function(e){let n=!1,i=()=>{};const s={free(){n||(n=!0,i(),URL.revokeObjectURL(e))}};return i=t.own(s.free),s}(a);if(t.closed)return;const o=i;let l,c,f=!1;i=r;try{if(l=e.option.subtitle,c=l.escape,t.closed||i!==r)return;l.escape=!1,f=!0;const s={...l,url:a,type:"vtt",onVttLoad:n};if(t.closed||i!==r)return;const o=e.subtitle.init(s);Promise.resolve(o).catch(e=>{i===r&&(i=null,r.free()),t.closed||console.warn("Failed to initialize multiple subtitles:",e)})}catch(u){throw i===r&&(i=o,f&&!1===l.escape&&(l.escape=c)),r.free(),u}finally{o!==i&&o?.free(),r!==i&&r.free()}}}function u({subtitles:e=[]}){return async n=>{const{unescape:i,getExt:r,srtToVtt:u,assToVtt:d}=n.constructor.utils,p=function(e){let t=Boolean(e.isDestroy);const n=new Set;let i;const s=new Promise(e=>{i=e});function a(e){try{e()}catch(t){console.warn("Failed to clean up multiple subtitles:",t)}}const r={get closed(){return t},own:e=>(t?a(e):n.add(e),()=>n.delete(e)),wait:e=>Promise.race([e,s]),dispose(){if(t)return;t=!0,i();const e=[...n].reverse();n.clear();for(const t of e)a(t)}};if(!t){r.own(()=>e.off("destroy",r.dispose));try{e.on("destroy",r.dispose)}catch(o){throw r.dispose(),o}}return r}(n),m=f(n,p,i);let g=[];function h(e){p.closed||m(function(e){const t=[];for(const n of e){const e=n;for(const n of e.cues){const i=[];for(const t of n.tree.children)"timestamp"===t.type?i.push(o(t)):i.push({type:"text",value:`
`},l(t),{type:"text",value:"
"});t.push({...n,tree:{...n.tree,children:i}})}}return(new s).serialize(t)}(e))}const v={name:"multipleSubtitles",tracks(e=[]){p.closed||h(e.map(e=>g.find(t=>t.name===e)))},reset(){h(g)}};try{if(p.closed)return v;const i=await Promise.all(e.map(e=>async function(e,{getExt:t,srtToVtt:n,assToVtt:i},s){if(s.closed)return;const a="function"===typeof AbortController?new AbortController:null,r=s.own(()=>a?.abort());try{const r=e.url;if(s.closed)return;const o=await s.wait(a?fetch(r,{signal:a.signal}):fetch(r));if(s.closed)return;if(!1===o.ok)throw new Error(`Failed to fetch multiple subtitles: HTTP ${o.status}`);const l=await s.wait(o.arrayBuffer());if(s.closed)return;const c=new TextDecoder(e.encoding||"utf-8").decode(l);switch(e.type||t(e.url)){case"srt":return n(c);case"ass":return function(e,t){const n=t(e);if(!/^WEBVTT \d+ \d+:/.test(n))return n;const i=[];for(const s of e.split(/\r?\n/)){const e=s.match(/Dialogue:\s\d,(\d+:\d{2}:\d{2}\.\d{2}),(\d+:\d{2}:\d{2}\.\d{2}),(?:[^,]*,){6}([\s\S]*)$/i);if(!e)continue;const[,t="",n="",a=""]=e;i.push({start:`${t.replace(/^(\d):/,"0$1:")}0`,end:`${n.replace(/^(\d):/,"0$1:")}0`,text:a.replace(/\{[\s\S]*?\}/g,"").replace(/\\N/g,"\n").trim().split(/\r?\n/).map(e=>e.trim()).join("\n")})}return n!==`WEBVTT ${i.map((e,t)=>`${t+1} ${e.start} --\x3e ${e.end} ${e.text}`).join("\n\n")}`?n:`WEBVTT\n\n${i.map((e,t)=>`${t+1}\n${e.start} --\x3e ${e.end}\n${e.text}`).join("\n\n")}`}(c,i);case"vtt":return c;default:return""}}catch(o){throw a?.abort(),o}finally{r()}}(e,{getExt:r,srtToVtt:u,assToVtt:d},p)));return p.closed||(g=function(e,n){const i=new t(a);return e.map((e,t)=>({...i.parse(e,"metadata"),url:n[t].url,name:n[t].name}))}(i,e),c(n,p),h(g)),v}catch(b){throw p.dispose(),b}}}return Object.assign(u,{default:u})}); diff --git a/docs/compiled/artplayer-plugin-multiple-subtitles.legacy.js b/docs/compiled/artplayer-plugin-multiple-subtitles.legacy.js index 2acbfccb5..ee39b3f8b 100644 --- a/docs/compiled/artplayer-plugin-multiple-subtitles.legacy.js +++ b/docs/compiled/artplayer-plugin-multiple-subtitles.legacy.js @@ -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,o=(t,n,i)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[n]=i,a=(e,t)=>{for(var n in t||(t={}))r.call(t,n)&&o(e,n,t[n]);if(i)for(var n of i(t))s.call(t,n)&&o(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{a(n.next(e))}catch(t){r(t)}},o=e=>{try{a(n.throw(e))}catch(t){r(t)}},a=e=>e.done?i(e.value):Promise.resolve(e.value).then(s,o);a((n=n.apply(e,t)).next())});var u={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},f=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/),o=!1,a=[],l=[],c=[];function f(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),(gb&&" "!==m[b]&&"\t"!==m[b])&&f('No valid signature. (File needs to start with "WEBVTT".)'),r++;""!=s[r]&&void 0!=s[r];){if(f("No blank line after the signature."),-1!=s[r].indexOf("--\x3e")){o=!0;break}r++}for(;void 0!=s[r];){for(var y;!o&&""==s[r];)r++;if(!o&&void 0==s[r])break;y=Object.assign({},u,{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")&&f("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")&&(f("Cannot have timestamp in a style block."),w=!0),T.push(s[r]),r++;if(l.length){f("Style blocks cannot appear after the first cue.");continue}w||a.push(T.join("\n"));continue}if(""==s[++r]||void 0==s[r]){f("Cue identifier cannot be standalone.");continue}if(-1==s[r].indexOf("--\x3e")){x=!1,f("Cue identifier needs to be followed by timestamp.");continue}}o=!1;var O=new d(s[r],f),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")){f("Blank line missing before cue."),o=!0;break}""!=y.text&&(y.text+="\n"),y.text+=s[r],r++}var A=new p(y.text,f,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")){o=!0;break}r++}}return l.sort(function(e,t){return e.startTimet.startTime?1:e.endTime>t.endTime?-1:e.endTime2||parseInt(t,10)>59)&&(l="hours"),":"==e[r])if(r++,2==(n=a(/\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=a(/\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==(o=a(/\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(o,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,a){if(o(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime"==e[r]){if(r++,i.test(e[r])&&s("'--\x3e' not separated from timestamp by whitespace."),o(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]),o(n),function(e,t){for(var i=e.split(n),r=[],o=0;o100)){s("Line position cannot be >100%.");continue}if(""===d||isNaN(d)||!isFinite(d)){s("Line position needs to be a number");continue}if(void 0!==f){if(!["start","center","end"].includes(f)){s("Line alignment needs to be one of start, center or end");continue}t.lineAlign=f}t.snapToLines=!p,t.linePosition=parseFloat(d),parseFloat(d).toString()!==d&&(t.nonSerializable=!0)}else if("position"==l){if(/,/.test(c)){u=c.split(","),c=u[0];var m=u[1]}if("%"!=c[c.length-1]){s("Text position must be a percentage.");continue}if(parseInt(c,10)>100||parseInt(c,10)<0){s("Text position needs to be between 0 and 100%.");continue}if(""===(d=c.slice(0,c.length-1))||isNaN(d)||!isFinite(d)){s("Line position needs to be a number");continue}if(void 0!==m){if(!["line-left","center","line-right"].includes(m)){s("Position alignment needs to be one of line-left, center or line-right");continue}t.positionAlign=m}t.textPosition=parseFloat(d)}else if("size"==l){if("%"!=c[c.length-1]){s("Size must be a percentage.");continue}if(parseInt(c,10)>100){s("Size cannot be >100%.");continue}var g=c.slice(0,c.length-1);if(void 0===g||""===g||isNaN(g)){s("Size needs to be a number"),g=100;continue}if((g=parseFloat(g))<0||g>100){s("Size needs to be between 0 and 100%.");continue}t.size=g}else if("align"==l){var h=["start","center","end","left","right"];if(-1==h.indexOf(c)){s("Alignment can only be set to one of "+h.join(", ")+".");continue}t.alignment=c}else s("Invalid setting.")}}(e.substring(r),t),!0}else s("No valid timestamp separator found.");else s("No valid timestamp separator found.");else s("No valid timestamp separator found.")},this.parseTimestamp=function(){var t=l();if(void 0==e[r])return t;s("Timestamp must not have trailing characters.")}},p=function(e,t,n,i){this.entities=i;var r=this,s=(e=e,0),o=function(e){"metadata"!=n&&t(e,s+1)};function l(){for(var t="data",n="",a="",l=[];void 0!=e[s-1]||0==s;){var c=e[s];if("data"==t)if("&"==c)a=c,t="escape";else if("<"==c&&""==n)t="tag";else{if("<"==c||void 0==c)return["text",n];n+=c}else if("escape"==t){if("<"==c||void 0==c){let e;return o("Incorrect escape."),(e=a.match(/^&#([0-9]+)$/))?n+=String.fromCharCode(e[1]):r.entities[a]?n+=r.entities[a]:n+=a,["text",n]}if("&"==c)o("Incorrect escape."),n+=a,a=c;else if(/[a-z#0-9]/i.test(c))a+=c;else if(";"==c){let e;(e=a.match(/^&#(x?[0-9]+)$/))?n+=String.fromCharCode("0"+e[1]):r.entities[a+c]?n+=r.entities[a+c]:(e=Object.keys(i).find(e=>a.startsWith(e)))?n+=r.entities[e]+a.slice(e.length)+c:(o("Incorrect escape."),n+=a+";"),t="data"}else o("Incorrect escape."),n+=a+c,t="data"}else if("tag"==t)if("\t"==c||"\n"==c||"\f"==c||" "==c)t="start tag annotation";else if("."==c)t="start tag class";else if("/"==c)t="end tag";else if(/\d/.test(c))n=c,t="timestamp tag";else{if(">"==c||void 0==c)return">"==c&&s++,["start tag","",[],""];n=c,t="start tag"}else if("start tag"==t)if("\t"==c||"\f"==c||" "==c)t="start tag annotation";else if("\n"==c)a=c,t="start tag annotation";else if("."==c)t="start tag class";else{if(">"==c||void 0==c)return">"==c&&s++,["start tag",n,[],""];n+=c}else if("start tag class"==t)if("\t"==c||"\f"==c||" "==c)a&&l.push(a),a="",t="start tag annotation";else if("\n"==c)a&&l.push(a),a=c,t="start tag annotation";else if("."==c)a&&l.push(a),a="";else{if(">"==c||void 0==c)return">"==c&&s++,a&&l.push(a),["start tag",n,l,""];a+=c}else if("start tag annotation"==t){if(">"==c||void 0==c)return">"==c&&s++,["start tag",n,l,a=a.split(/[\u0020\t\f\r\n]+/).filter(function(e){if(e)return!0}).join(" ")];a+=c}else if("end tag"==t){if(">"==c||void 0==c)return">"==c&&s++,["end tag",n];n+=c}else if("timestamp tag"==t){if(">"==c||void 0==c)return">"==c&&s++,["timestamp",n];n+=c}else o("Never happens.");s++}}this.parse=function(t,i){var r={children:[]},c=r,u=[];function f(e){c.children.push({type:"object",name:e[1],classes:e[2],children:[],parent:c}),c=c.children[c.children.length-1]}function p(e){for(var t=c;t;){if(t.name==e)return!0;t=t.parent}}for(;void 0!=e[s];){var m=l();if("text"==m[0])c.children.push({type:"text",value:m[1],parent:c});else if("start tag"==m[0]){"chapters"==n&&o("Start tags not allowed in chapter title text.");var g=m[1];"v"!=g&&"lang"!=g&&""!=m[3]&&o("Only and can have an annotation."),"c"==g||"i"==g||"b"==g||"u"==g||"ruby"==g||"rt"==g&&"ruby"==c.name?f(m):"v"==g?(p("v")&&o(" cannot be nested inside itself."),f(m),c.value=m[3],m[3]||o(" requires an annotation.")):"lang"==g?(f(m),c.value=m[3]):o("Incorrect start tag.")}else if("end tag"==m[0])"chapters"==n&&o("End tags not allowed in chapter title text."),m[1]==c.name?c=c.parent:"ruby"==m[1]&&"rt"==c.name?c=c.parent.parent:o("Incorrect end tag.");else if("timestamp"==m[0]){"chapters"==n&&o("Timestamp not allowed in chapter title text.");var h=new d(m[1],o).parseTimestamp();void 0!=h&&((h<=t||h>=i)&&o("Timestamp must be between start timestamp and end timestamp."),u.length>0&&u[u.length-1]>=h&&o("Timestamp must be greater than any previous timestamp."),c.children.push({type:"timestamp",value:h,parent:c}),u.push(h))}}for(;c.parent;)"v"!=c.name&&o("Required end tag missing."),c=c.parent;return function e(t){const n=a({},t);return t.children&&(n.children=t.children.map(e)),n.parent&&delete n.parent,n}(r)}},m=function(){function e(e){const t=("00"+1e3*(e-Math.floor(e)).toFixed(3)).slice(-3);let n=0,i=0,r=0;return e>=3600&&(n=Math.floor(e/3600)),i=Math.floor((e-3600*n)/60),r=Math.floor(e-3600*n-60*i),(n?n+":":"")+(""+i).padStart(2,"0")+":"+(""+r).padStart(2,"0")+"."+t}function t(n){for(var i="",r=0;r/g,">");else if("object"==s.type){if(i+="<"+s.name,s.classes)for(var o=0;o"}else"timestamp"==s.type?i+="<"+e(s.value)+">":i+="<"+s.value+">"}return i}function n(n){return(void 0!==n.id?n.id+"\n":"")+e(n.startTime)+" --\x3e "+e(n.endTime)+function(e){var t="";const n=Object.keys(u).filter(t=>e[t]!==u[t]);return n.includes("direction")&&(t+=" vertical:"+e.direction),n.includes("alignment")&&(t+=" align:"+e.alignment),n.includes("size")&&(t+=" size:"+e.size+"%"),(n.includes("lineAlign")||n.includes("linePosition"))&&(t+=" line:"+e.linePosition+(e.snapToLines?"":"%")+(e.lineAlign&&e.lineAlign!=u.lineAlign?","+e.lineAlign:"")),(n.includes("textPosition")||n.includes("positionAlign"))&&(t+=" position:"+e.textPosition+"%"+(e.positionAlign&&e.positionAlign!==u.positionAlign?","+e.positionAlign:"")),t}(n)+"\n"+t(n.tree.children)+"\n\n"}function i(e){return"STYLE\n"+e+"\n\n"}this.serialize=function(e,t){var r="WEBVTT\n\n";if(t)for(var s=0;s",">":">","&lrm":"\u200e","‎":"\u200e","&rlm":"\u200f","‏":"\u200f"," ":"\xa0"," ":"\xa0"};const h="artplayer-multiple-subtitles-timestamp";function v(e){return{type:"object",name:"c",classes:[h],children:[e]}}function b(e){return"timestamp"===e.type?v(e):"object"===e.type?l(a({},e),{children:e.children.map(b)}):l(a({},e),{value:(t=e.value,t.replace(/&/g,"&").replace(//g,">"))});var t}function y(e,t){const n=!!e.subtitle&&"activeCue"in e.subtitle&&!("activeCues"in e.subtitle),i=n?"subtitleUpdate":"subtitleAfterUpdate";function r(){if(!t.closed){n&&function(e){var t,n,i,r;const s=null==(n=null==(t=e.subtitle)?void 0:t.textTrack)?void 0:n.activeCues;if(!s||s.length<2)return;const o=Array.from(s,e=>"text"in e&&"string"===typeof e.text?e.text:""),a=e.template.$subtitle;if(null==(r=null==(i=e.option)?void 0:i.subtitle)?void 0:r.escape){a.innerHTML="";for(const e of o)for(const t of e.split(/\r?\n/)){const e=a.ownerDocument.createElement("div");e.className="art-subtitle-line",e.textContent=t,a.appendChild(e)}}else a.innerHTML=o.join("")}(e);for(const t of Array.from(e.template.$subtitle.getElementsByTagName(`c.${h}`))){const e=t.firstChild;3===(null==e?void 0:e.nodeType)&&(e.nodeValue=(e.nodeValue||"").replace(/^<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/,"")),t.replaceWith(...Array.from(t.childNodes))}}}t.closed||(t.own(()=>e.off(i,r)),e.on(i,r))}function x(e,t,n){let i=null;return r=>{if(t.closed)return;const s=URL.createObjectURL(new Blob([r],{type:"text/vtt"})),o=function(e){let n=!1,i=()=>{};const r={free(){n||(n=!0,i(),URL.revokeObjectURL(e))}};return i=t.own(r.free),r}(s);if(t.closed)return;const c=i;let u,f,d=!1;i=o;try{if(u=e.option.subtitle,f=u.escape,t.closed||i!==o)return;u.escape=!1,d=!0;const r=l(a({},u),{url:s,type:"vtt",onVttLoad:n});if(t.closed||i!==o)return;const c=e.subtitle.init(r);Promise.resolve(c).catch(e=>{i===o&&(i=null,o.free()),t.closed||console.warn("Failed to initialize multiple subtitles:",e)})}catch(p){throw i===o&&(i=c,d&&!1===u.escape&&(u.escape=f)),o.free(),p}finally{c!==i&&(null==c||c.free()),o!==i&&o.free()}}}function T({subtitles:e=[]}){return t=>c(null,null,function*(){const{unescape:n,getExt:i,srtToVtt:r,assToVtt:s}=t.constructor.utils,o=function(e){let t=Boolean(e.isDestroy);const n=new Set;let i;const r=new Promise(e=>{i=e});function s(e){try{e()}catch(t){console.warn("Failed to clean up multiple subtitles:",t)}}const o={get closed(){return t},own:e=>(t?s(e):n.add(e),()=>n.delete(e)),wait:e=>Promise.race([e,r]),dispose(){if(t)return;t=!0,i();const e=[...n].reverse();n.clear();for(const t of e)s(t)}};if(!t){o.own(()=>e.off("destroy",o.dispose));try{e.on("destroy",o.dispose)}catch(a){throw o.dispose(),a}}return o}(t),u=x(t,o,n);let d=[];function p(e){o.closed||u(function(e){const t=[];for(const n of e){const e=n;for(const n of e.cues){const i=[];for(const t of n.tree.children)"timestamp"===t.type?i.push(v(t)):i.push({type:"text",value:`
`},b(t),{type:"text",value:"
"});t.push(l(a({},n),{tree:l(a({},n.tree),{children:i})}))}}return(new m).serialize(t)}(e))}const h={name:"multipleSubtitles",tracks(e=[]){o.closed||p(e.map(e=>d.find(t=>t.name===e)))},reset(){p(d)}};try{if(o.closed)return h;const n=yield Promise.all(e.map(e=>function(e,t,n){return c(this,arguments,function*(e,{getExt:t,srtToVtt:n,assToVtt:i},r){if(r.closed)return;const s="function"===typeof AbortController?new AbortController:null,o=r.own(()=>null==s?void 0:s.abort());try{const o=e.url;if(r.closed)return;const a=yield r.wait(s?fetch(o,{signal:s.signal}):fetch(o));if(r.closed)return;if(!1===a.ok)throw new Error(`Failed to fetch multiple subtitles: HTTP ${a.status}`);const l=yield r.wait(a.arrayBuffer());if(r.closed)return;const c=new TextDecoder(e.encoding||"utf-8").decode(l);switch(e.type||t(e.url)){case"srt":return n(c);case"ass":return i(c);case"vtt":return c;default:return""}}catch(a){throw null==s||s.abort(),a}finally{o()}})}(e,{getExt:i,srtToVtt:r,assToVtt:s},o)));return o.closed||(d=function(e,t){const n=new f(g);return e.map((e,i)=>{const r=n.parse(e,"metadata");return l(a({},r),{url:t[i].url,name:t[i].name})})}(n,e),y(t,o),p(d)),h}catch(T){throw o.dispose(),T}})}return Object.assign(T,{default:T})}); +!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,o=(t,n,i)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[n]=i,a=(e,t)=>{for(var n in t||(t={}))r.call(t,n)&&o(e,n,t[n]);if(i)for(var n of i(t))s.call(t,n)&&o(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{a(n.next(e))}catch(t){r(t)}},o=e=>{try{a(n.throw(e))}catch(t){r(t)}},a=e=>e.done?i(e.value):Promise.resolve(e.value).then(s,o);a((n=n.apply(e,t)).next())});var u={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},f=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/),o=!1,a=[],l=[],c=[];function f(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),(gb&&" "!==m[b]&&"\t"!==m[b])&&f('No valid signature. (File needs to start with "WEBVTT".)'),r++;""!=s[r]&&void 0!=s[r];){if(f("No blank line after the signature."),-1!=s[r].indexOf("--\x3e")){o=!0;break}r++}for(;void 0!=s[r];){for(var y;!o&&""==s[r];)r++;if(!o&&void 0==s[r])break;y=Object.assign({},u,{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")&&f("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")&&(f("Cannot have timestamp in a style block."),w=!0),T.push(s[r]),r++;if(l.length){f("Style blocks cannot appear after the first cue.");continue}w||a.push(T.join("\n"));continue}if(""==s[++r]||void 0==s[r]){f("Cue identifier cannot be standalone.");continue}if(-1==s[r].indexOf("--\x3e")){x=!1,f("Cue identifier needs to be followed by timestamp.");continue}}o=!1;var O=new d(s[r],f),$=0;if(l.length>0&&($=l[l.length-1].startTime),!x||O.parse(y,$)){for(r++;""!=s[r]&&void 0!=s[r];){if(-1!=s[r].indexOf("--\x3e")){f("Blank line missing before cue."),o=!0;break}""!=y.text&&(y.text+="\n"),y.text+=s[r],r++}var j=new p(y.text,f,n,e);y.tree=j.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")){o=!0;break}r++}}return l.sort(function(e,t){return e.startTimet.startTime?1:e.endTime>t.endTime?-1:e.endTime2||parseInt(t,10)>59)&&(l="hours"),":"==e[r])if(r++,2==(n=a(/\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=a(/\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==(o=a(/\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(o,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,a){if(o(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime"==e[r]){if(r++,i.test(e[r])&&s("'--\x3e' not separated from timestamp by whitespace."),o(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]),o(n),function(e,t){for(var i=e.split(n),r=[],o=0;o100)){s("Line position cannot be >100%.");continue}if(""===d||isNaN(d)||!isFinite(d)){s("Line position needs to be a number");continue}if(void 0!==f){if(!["start","center","end"].includes(f)){s("Line alignment needs to be one of start, center or end");continue}t.lineAlign=f}t.snapToLines=!p,t.linePosition=parseFloat(d),parseFloat(d).toString()!==d&&(t.nonSerializable=!0)}else if("position"==l){if(/,/.test(c)){u=c.split(","),c=u[0];var m=u[1]}if("%"!=c[c.length-1]){s("Text position must be a percentage.");continue}if(parseInt(c,10)>100||parseInt(c,10)<0){s("Text position needs to be between 0 and 100%.");continue}if(""===(d=c.slice(0,c.length-1))||isNaN(d)||!isFinite(d)){s("Line position needs to be a number");continue}if(void 0!==m){if(!["line-left","center","line-right"].includes(m)){s("Position alignment needs to be one of line-left, center or line-right");continue}t.positionAlign=m}t.textPosition=parseFloat(d)}else if("size"==l){if("%"!=c[c.length-1]){s("Size must be a percentage.");continue}if(parseInt(c,10)>100){s("Size cannot be >100%.");continue}var g=c.slice(0,c.length-1);if(void 0===g||""===g||isNaN(g)){s("Size needs to be a number"),g=100;continue}if((g=parseFloat(g))<0||g>100){s("Size needs to be between 0 and 100%.");continue}t.size=g}else if("align"==l){var h=["start","center","end","left","right"];if(-1==h.indexOf(c)){s("Alignment can only be set to one of "+h.join(", ")+".");continue}t.alignment=c}else s("Invalid setting.")}}(e.substring(r),t),!0}else s("No valid timestamp separator found.");else s("No valid timestamp separator found.");else s("No valid timestamp separator found.")},this.parseTimestamp=function(){var t=l();if(void 0==e[r])return t;s("Timestamp must not have trailing characters.")}},p=function(e,t,n,i){this.entities=i;var r=this,s=(e=e,0),o=function(e){"metadata"!=n&&t(e,s+1)};function l(){for(var t="data",n="",a="",l=[];void 0!=e[s-1]||0==s;){var c=e[s];if("data"==t)if("&"==c)a=c,t="escape";else if("<"==c&&""==n)t="tag";else{if("<"==c||void 0==c)return["text",n];n+=c}else if("escape"==t){if("<"==c||void 0==c){let e;return o("Incorrect escape."),(e=a.match(/^&#([0-9]+)$/))?n+=String.fromCharCode(e[1]):r.entities[a]?n+=r.entities[a]:n+=a,["text",n]}if("&"==c)o("Incorrect escape."),n+=a,a=c;else if(/[a-z#0-9]/i.test(c))a+=c;else if(";"==c){let e;(e=a.match(/^&#(x?[0-9]+)$/))?n+=String.fromCharCode("0"+e[1]):r.entities[a+c]?n+=r.entities[a+c]:(e=Object.keys(i).find(e=>a.startsWith(e)))?n+=r.entities[e]+a.slice(e.length)+c:(o("Incorrect escape."),n+=a+";"),t="data"}else o("Incorrect escape."),n+=a+c,t="data"}else if("tag"==t)if("\t"==c||"\n"==c||"\f"==c||" "==c)t="start tag annotation";else if("."==c)t="start tag class";else if("/"==c)t="end tag";else if(/\d/.test(c))n=c,t="timestamp tag";else{if(">"==c||void 0==c)return">"==c&&s++,["start tag","",[],""];n=c,t="start tag"}else if("start tag"==t)if("\t"==c||"\f"==c||" "==c)t="start tag annotation";else if("\n"==c)a=c,t="start tag annotation";else if("."==c)t="start tag class";else{if(">"==c||void 0==c)return">"==c&&s++,["start tag",n,[],""];n+=c}else if("start tag class"==t)if("\t"==c||"\f"==c||" "==c)a&&l.push(a),a="",t="start tag annotation";else if("\n"==c)a&&l.push(a),a=c,t="start tag annotation";else if("."==c)a&&l.push(a),a="";else{if(">"==c||void 0==c)return">"==c&&s++,a&&l.push(a),["start tag",n,l,""];a+=c}else if("start tag annotation"==t){if(">"==c||void 0==c)return">"==c&&s++,["start tag",n,l,a=a.split(/[\u0020\t\f\r\n]+/).filter(function(e){if(e)return!0}).join(" ")];a+=c}else if("end tag"==t){if(">"==c||void 0==c)return">"==c&&s++,["end tag",n];n+=c}else if("timestamp tag"==t){if(">"==c||void 0==c)return">"==c&&s++,["timestamp",n];n+=c}else o("Never happens.");s++}}this.parse=function(t,i){var r={children:[]},c=r,u=[];function f(e){c.children.push({type:"object",name:e[1],classes:e[2],children:[],parent:c}),c=c.children[c.children.length-1]}function p(e){for(var t=c;t;){if(t.name==e)return!0;t=t.parent}}for(;void 0!=e[s];){var m=l();if("text"==m[0])c.children.push({type:"text",value:m[1],parent:c});else if("start tag"==m[0]){"chapters"==n&&o("Start tags not allowed in chapter title text.");var g=m[1];"v"!=g&&"lang"!=g&&""!=m[3]&&o("Only and can have an annotation."),"c"==g||"i"==g||"b"==g||"u"==g||"ruby"==g||"rt"==g&&"ruby"==c.name?f(m):"v"==g?(p("v")&&o(" cannot be nested inside itself."),f(m),c.value=m[3],m[3]||o(" requires an annotation.")):"lang"==g?(f(m),c.value=m[3]):o("Incorrect start tag.")}else if("end tag"==m[0])"chapters"==n&&o("End tags not allowed in chapter title text."),m[1]==c.name?c=c.parent:"ruby"==m[1]&&"rt"==c.name?c=c.parent.parent:o("Incorrect end tag.");else if("timestamp"==m[0]){"chapters"==n&&o("Timestamp not allowed in chapter title text.");var h=new d(m[1],o).parseTimestamp();void 0!=h&&((h<=t||h>=i)&&o("Timestamp must be between start timestamp and end timestamp."),u.length>0&&u[u.length-1]>=h&&o("Timestamp must be greater than any previous timestamp."),c.children.push({type:"timestamp",value:h,parent:c}),u.push(h))}}for(;c.parent;)"v"!=c.name&&o("Required end tag missing."),c=c.parent;return function e(t){const n=a({},t);return t.children&&(n.children=t.children.map(e)),n.parent&&delete n.parent,n}(r)}},m=function(){function e(e){const t=("00"+1e3*(e-Math.floor(e)).toFixed(3)).slice(-3);let n=0,i=0,r=0;return e>=3600&&(n=Math.floor(e/3600)),i=Math.floor((e-3600*n)/60),r=Math.floor(e-3600*n-60*i),(n?n+":":"")+(""+i).padStart(2,"0")+":"+(""+r).padStart(2,"0")+"."+t}function t(n){for(var i="",r=0;r/g,">");else if("object"==s.type){if(i+="<"+s.name,s.classes)for(var o=0;o"}else"timestamp"==s.type?i+="<"+e(s.value)+">":i+="<"+s.value+">"}return i}function n(n){return(void 0!==n.id?n.id+"\n":"")+e(n.startTime)+" --\x3e "+e(n.endTime)+function(e){var t="";const n=Object.keys(u).filter(t=>e[t]!==u[t]);return n.includes("direction")&&(t+=" vertical:"+e.direction),n.includes("alignment")&&(t+=" align:"+e.alignment),n.includes("size")&&(t+=" size:"+e.size+"%"),(n.includes("lineAlign")||n.includes("linePosition"))&&(t+=" line:"+e.linePosition+(e.snapToLines?"":"%")+(e.lineAlign&&e.lineAlign!=u.lineAlign?","+e.lineAlign:"")),(n.includes("textPosition")||n.includes("positionAlign"))&&(t+=" position:"+e.textPosition+"%"+(e.positionAlign&&e.positionAlign!==u.positionAlign?","+e.positionAlign:"")),t}(n)+"\n"+t(n.tree.children)+"\n\n"}function i(e){return"STYLE\n"+e+"\n\n"}this.serialize=function(e,t){var r="WEBVTT\n\n";if(t)for(var s=0;s",">":">","&lrm":"\u200e","‎":"\u200e","&rlm":"\u200f","‏":"\u200f"," ":"\xa0"," ":"\xa0"};const h="artplayer-multiple-subtitles-timestamp";function v(e){return{type:"object",name:"c",classes:[h],children:[e]}}function b(e){return"timestamp"===e.type?v(e):"object"===e.type?l(a({},e),{children:e.children.map(b)}):l(a({},e),{value:(t=e.value,t.replace(/&/g,"&").replace(//g,">"))});var t}function y(e,t){const n=!!e.subtitle&&"activeCue"in e.subtitle&&!("activeCues"in e.subtitle),i=n?"subtitleUpdate":"subtitleAfterUpdate";function r(){if(!t.closed){n&&function(e){var t,n,i,r;const s=null==(n=null==(t=e.subtitle)?void 0:t.textTrack)?void 0:n.activeCues;if(!s||s.length<2)return;const o=Array.from(s,e=>"text"in e&&"string"===typeof e.text?e.text:""),a=e.template.$subtitle;if(null==(r=null==(i=e.option)?void 0:i.subtitle)?void 0:r.escape){a.innerHTML="";for(const e of o)for(const t of e.split(/\r?\n/)){const e=a.ownerDocument.createElement("div");e.className="art-subtitle-line",e.textContent=t,a.appendChild(e)}}else a.innerHTML=o.join("")}(e);for(const t of Array.from(e.template.$subtitle.getElementsByTagName(`c.${h}`))){const e=t.firstChild;3===(null==e?void 0:e.nodeType)&&(e.nodeValue=(e.nodeValue||"").replace(/^<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/,"")),t.replaceWith(...Array.from(t.childNodes))}}}t.closed||(t.own(()=>e.off(i,r)),e.on(i,r))}function x(e,t,n){let i=null;return r=>{if(t.closed)return;const s=URL.createObjectURL(new Blob([r],{type:"text/vtt"})),o=function(e){let n=!1,i=()=>{};const r={free(){n||(n=!0,i(),URL.revokeObjectURL(e))}};return i=t.own(r.free),r}(s);if(t.closed)return;const c=i;let u,f,d=!1;i=o;try{if(u=e.option.subtitle,f=u.escape,t.closed||i!==o)return;u.escape=!1,d=!0;const r=l(a({},u),{url:s,type:"vtt",onVttLoad:n});if(t.closed||i!==o)return;const c=e.subtitle.init(r);Promise.resolve(c).catch(e=>{i===o&&(i=null,o.free()),t.closed||console.warn("Failed to initialize multiple subtitles:",e)})}catch(p){throw i===o&&(i=c,d&&!1===u.escape&&(u.escape=f)),o.free(),p}finally{c!==i&&(null==c||c.free()),o!==i&&o.free()}}}function T({subtitles:e=[]}){return t=>c(null,null,function*(){const{unescape:n,getExt:i,srtToVtt:r,assToVtt:s}=t.constructor.utils,o=function(e){let t=Boolean(e.isDestroy);const n=new Set;let i;const r=new Promise(e=>{i=e});function s(e){try{e()}catch(t){console.warn("Failed to clean up multiple subtitles:",t)}}const o={get closed(){return t},own:e=>(t?s(e):n.add(e),()=>n.delete(e)),wait:e=>Promise.race([e,r]),dispose(){if(t)return;t=!0,i();const e=[...n].reverse();n.clear();for(const t of e)s(t)}};if(!t){o.own(()=>e.off("destroy",o.dispose));try{e.on("destroy",o.dispose)}catch(a){throw o.dispose(),a}}return o}(t),u=x(t,o,n);let d=[];function p(e){o.closed||u(function(e){const t=[];for(const n of e){const e=n;for(const n of e.cues){const i=[];for(const t of n.tree.children)"timestamp"===t.type?i.push(v(t)):i.push({type:"text",value:`
`},b(t),{type:"text",value:"
"});t.push(l(a({},n),{tree:l(a({},n.tree),{children:i})}))}}return(new m).serialize(t)}(e))}const h={name:"multipleSubtitles",tracks(e=[]){o.closed||p(e.map(e=>d.find(t=>t.name===e)))},reset(){p(d)}};try{if(o.closed)return h;const n=yield Promise.all(e.map(e=>function(e,t,n){return c(this,arguments,function*(e,{getExt:t,srtToVtt:n,assToVtt:i},r){if(r.closed)return;const s="function"===typeof AbortController?new AbortController:null,o=r.own(()=>null==s?void 0:s.abort());try{const o=e.url;if(r.closed)return;const a=yield r.wait(s?fetch(o,{signal:s.signal}):fetch(o));if(r.closed)return;if(!1===a.ok)throw new Error(`Failed to fetch multiple subtitles: HTTP ${a.status}`);const l=yield r.wait(a.arrayBuffer());if(r.closed)return;const c=new TextDecoder(e.encoding||"utf-8").decode(l);switch(e.type||t(e.url)){case"srt":return n(c);case"ass":return function(e,t){const n=t(e);if(!/^WEBVTT \d+ \d+:/.test(n))return n;const i=[];for(const r of e.split(/\r?\n/)){const e=r.match(/Dialogue:\s\d,(\d+:\d{2}:\d{2}\.\d{2}),(\d+:\d{2}:\d{2}\.\d{2}),(?:[^,]*,){6}([\s\S]*)$/i);if(!e)continue;const[,t="",n="",s=""]=e;i.push({start:`${t.replace(/^(\d):/,"0$1:")}0`,end:`${n.replace(/^(\d):/,"0$1:")}0`,text:s.replace(/\{[\s\S]*?\}/g,"").replace(/\\N/g,"\n").trim().split(/\r?\n/).map(e=>e.trim()).join("\n")})}return n!==`WEBVTT ${i.map((e,t)=>`${t+1} ${e.start} --\x3e ${e.end} ${e.text}`).join("\n\n")}`?n:`WEBVTT\n\n${i.map((e,t)=>`${t+1}\n${e.start} --\x3e ${e.end}\n${e.text}`).join("\n\n")}`}(c,i);case"vtt":return c;default:return""}}catch(a){throw null==s||s.abort(),a}finally{o()}})}(e,{getExt:i,srtToVtt:r,assToVtt:s},o)));return o.closed||(d=function(e,t){const n=new f(g);return e.map((e,i)=>{const r=n.parse(e,"metadata");return l(a({},r),{url:t[i].url,name:t[i].name})})}(n,e),y(t,o),p(d)),h}catch(T){throw o.dispose(),T}})}return Object.assign(T,{default:T})}); diff --git a/docs/compiled/artplayer-plugin-multiple-subtitles.mjs b/docs/compiled/artplayer-plugin-multiple-subtitles.mjs index 80bcb1c49..c67f10fae 100644 --- a/docs/compiled/artplayer-plugin-multiple-subtitles.mjs +++ b/docs/compiled/artplayer-plugin-multiple-subtitles.mjs @@ -1017,6 +1017,31 @@ function createRenderer(art, lifetime, unescape) { } }; } +function convertAss(text, converter) { + const converted = converter(text); + if (!/^WEBVTT \d+ \d+:/.test(converted)) + return converted; + const cues = []; + for (const line of text.split(/\r?\n/)) { + const match = line.match(/Dialogue:\s\d,(\d+:\d{2}:\d{2}\.\d{2}),(\d+:\d{2}:\d{2}\.\d{2}),(?:[^,]*,){6}([\s\S]*)$/i); + if (!match) + continue; + const [, start = "", end = "", content = ""] = match; + cues.push({ + start: `${start.replace(/^(\d):/, "0$1:")}0`, + end: `${end.replace(/^(\d):/, "0$1:")}0`, + text: content.replace(/\{[\s\S]*?\}/g, "").replace(/\\N/g, "\n").trim().split(/\r?\n/).map((line2) => line2.trim()).join("\n") + }); + } + const collapsed = `WEBVTT ${cues.map((cue, index2) => `${index2 + 1} ${cue.start} --> ${cue.end} ${cue.text}`).join("\n\n")}`; + if (converted !== collapsed) + return converted; + return `WEBVTT + +${cues.map((cue, index2) => `${index2 + 1} +${cue.start} --> ${cue.end} +${cue.text}`).join("\n\n")}`; +} async function loadVtt(option, { getExt, srtToVtt, assToVtt }, lifetime) { if (lifetime.closed) return; @@ -1039,7 +1064,7 @@ async function loadVtt(option, { getExt, srtToVtt, assToVtt }, lifetime) { case "srt": return srtToVtt(text); case "ass": - return assToVtt(text); + return convertAss(text, assToVtt); case "vtt": return text; default: diff --git a/package.json b/package.json index 8a10604a9..52a174256 100644 --- a/package.json +++ b/package.json @@ -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-caption.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: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-ass.test.js test/multiple-subtitles-caption.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-caption.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 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-ass.test.js test/multiple-subtitles-caption.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 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", diff --git a/packages/artplayer-plugin-multiple-subtitles/ARCHITECTURE.md b/packages/artplayer-plugin-multiple-subtitles/ARCHITECTURE.md index 9936323e7..c6a3f181a 100644 --- a/packages/artplayer-plugin-multiple-subtitles/ARCHITECTURE.md +++ b/packages/artplayer-plugin-multiple-subtitles/ARCHITECTURE.md @@ -17,6 +17,12 @@ to npm 1.2.0; the runtime entry requires an option object but allows omitted `su - `src/index.ts` composes registration, named selection and the unchanged public result. - `src/request.ts` owns each fetch through byte decoding and delegates SRT/ASS conversion to the existing core utils. Failure aborts the request; registration failure closes siblings. +- `src/ass-conversion.ts` calls the host ASS converter once. It repairs only output + exactly matching the collapsed-line format of core5.1.2 main. Reconstruction + retains that host's supported Dialogue fields, hundredth-second times and text + cleanup; complete-output equality protects custom converters. Valid conversion, + empty output and converter exceptions retain their original behavior. This + private fallback avoids depending on a newer core or patching its shared utils. - `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 @@ -160,8 +166,11 @@ active cue; the matrix explicitly records lost simultaneous languages for publis 1.1.0/1.2.0, while1.0.0's index-merged cue displays both. Task10 adds the candidate view adapter without adopting index merging or altering independent cue timings. `multiple-subtitles-legacy.spec.js` checks asymmetric three-cue overlaps, HTML/CSS, -selection/reset and clearing on both old cores. ASS is additionally exercised on -5.1.7; published5.1.2's malformed ASS converter is separately tracked by task11. +selection/reset and clearing on both old cores, now using ASS on both after task11. +`multiple-subtitles-ass.spec.js` adds UTF16 input with an explicit type override, +multiline text, semantic HTML and translation overlaps on five recorded cores. +Task11's conversion repair leaves the original host utility untouched; it does +not add ASS positioning, animation or styling beyond the existing VTT conversion. This adapter does not repair an old browser host's stale native active-cue list. CORE-SUBTITLE-OFFSET-01 corrects paused offsets in the candidate core; published Firefox hosts retain their exact diff --git a/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.js b/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.js index 2b6e22918..3c16d0790 100644 --- a/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.js +++ b/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.js @@ -55,4 +55,4 @@ * * d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. */ -!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginMultipleSubtitles=t(),define(function(){return e.artplayerPluginMultipleSubtitles})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginMultipleSubtitles=t()}(this,function(){"use strict";var e={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},t=function(t){t||(t={"&":"&","<":"<",">":">","&lrm":"\u200e","&rlm":"\u200f"," ":"\xa0"}),this.entities=t,this.parse=function(s,a){s=s.replace(/\0/g,"\ufffd");var r=Date.now(),o=0,l=s.split(/\r\n|\r|\n/),c=!1,f=[],u=[],d=[];function p(e,t){d.push({message:e,line:o+1,col:t})}var m=l[o],g=m.length,h="WEBVTT",v=0,b=6;for("\ufeff"===m[0]&&(v=1,b+=1),(gb&&" "!==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 L=new i(x.text,p,a,t);x.tree=L.parse(x.startTime,x.endTime),u.push(x)}else for(x=null,o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}}return u.sort(function(e,t){return e.startTimet.startTime?1:e.endTime>t.endTime?-1:e.endTime2||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"==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;r100)){a("Line position cannot be >100%.");continue}if(""===d||isNaN(d)||!isFinite(d)){a("Line position needs to be a number");continue}if(void 0!==u){if(!["start","center","end"].includes(u)){a("Line alignment needs to be one of start, center or end");continue}t.lineAlign=u}t.snapToLines=!p,t.linePosition=parseFloat(d),parseFloat(d).toString()!==d&&(t.nonSerializable=!0)}else if("position"==l){if(/,/.test(c)){f=c.split(","),c=f[0];var m=f[1]}if("%"!=c[c.length-1]){a("Text position must be a percentage.");continue}if(parseInt(c,10)>100||parseInt(c,10)<0){a("Text position needs to be between 0 and 100%.");continue}if(""===(d=c.slice(0,c.length-1))||isNaN(d)||!isFinite(d)){a("Line position needs to be a number");continue}if(void 0!==m){if(!["line-left","center","line-right"].includes(m)){a("Position alignment needs to be one of line-left, center or line-right");continue}t.positionAlign=m}t.textPosition=parseFloat(d)}else if("size"==l){if("%"!=c[c.length-1]){a("Size must be a percentage.");continue}if(parseInt(c,10)>100){a("Size cannot be >100%.");continue}var g=c.slice(0,c.length-1);if(void 0===g||""===g||isNaN(g)){a("Size needs to be a number"),g=100;continue}if((g=parseFloat(g))<0||g>100){a("Size needs to be between 0 and 100%.");continue}t.size=g}else if("align"==l){var h=["start","center","end","left","right"];if(-1==h.indexOf(c)){a("Alignment can only be set to one of "+h.join(", ")+".");continue}t.alignment=c}else a("Invalid setting.")}}(e.substring(s),t),!0}else a("No valid timestamp separator found.");else a("No valid timestamp separator found.");else a("No valid timestamp separator found.")},this.parseTimestamp=function(){var t=l();if(void 0==e[s])return t;a("Timestamp must not have trailing characters.")}},i=function(e,t,i,s){this.entities=s;var a=this,r=(e=e,0),o=function(e){"metadata"!=i&&t(e,r+1)};function l(){for(var t="data",n="",i="",l=[];void 0!=e[r-1]||0==r;){var c=e[r];if("data"==t)if("&"==c)i=c,t="escape";else if("<"==c&&""==n)t="tag";else{if("<"==c||void 0==c)return["text",n];n+=c}else if("escape"==t){if("<"==c||void 0==c){let e;return o("Incorrect escape."),(e=i.match(/^&#([0-9]+)$/))?n+=String.fromCharCode(e[1]):a.entities[i]?n+=a.entities[i]:n+=i,["text",n]}if("&"==c)o("Incorrect escape."),n+=i,i=c;else if(/[a-z#0-9]/i.test(c))i+=c;else if(";"==c){let e;(e=i.match(/^&#(x?[0-9]+)$/))?n+=String.fromCharCode("0"+e[1]):a.entities[i+c]?n+=a.entities[i+c]:(e=Object.keys(s).find(e=>i.startsWith(e)))?n+=a.entities[e]+i.slice(e.length)+c:(o("Incorrect escape."),n+=i+";"),t="data"}else o("Incorrect escape."),n+=i+c,t="data"}else if("tag"==t)if("\t"==c||"\n"==c||"\f"==c||" "==c)t="start tag annotation";else if("."==c)t="start tag class";else if("/"==c)t="end tag";else if(/\d/.test(c))n=c,t="timestamp tag";else{if(">"==c||void 0==c)return">"==c&&r++,["start tag","",[],""];n=c,t="start tag"}else if("start tag"==t)if("\t"==c||"\f"==c||" "==c)t="start tag annotation";else if("\n"==c)i=c,t="start tag annotation";else if("."==c)t="start tag class";else{if(">"==c||void 0==c)return">"==c&&r++,["start tag",n,[],""];n+=c}else if("start tag class"==t)if("\t"==c||"\f"==c||" "==c)i&&l.push(i),i="",t="start tag annotation";else if("\n"==c)i&&l.push(i),i=c,t="start tag annotation";else if("."==c)i&&l.push(i),i="";else{if(">"==c||void 0==c)return">"==c&&r++,i&&l.push(i),["start tag",n,l,""];i+=c}else if("start tag annotation"==t){if(">"==c||void 0==c)return">"==c&&r++,["start tag",n,l,i=i.split(/[\u0020\t\f\r\n]+/).filter(function(e){if(e)return!0}).join(" ")];i+=c}else if("end tag"==t){if(">"==c||void 0==c)return">"==c&&r++,["end tag",n];n+=c}else if("timestamp tag"==t){if(">"==c||void 0==c)return">"==c&&r++,["timestamp",n];n+=c}else o("Never happens.");r++}}this.parse=function(t,s){var a={children:[]},c=a,f=[];function u(e){c.children.push({type:"object",name:e[1],classes:e[2],children:[],parent:c}),c=c.children[c.children.length-1]}function d(e){for(var t=c;t;){if(t.name==e)return!0;t=t.parent}}for(;void 0!=e[r];){var p=l();if("text"==p[0])c.children.push({type:"text",value:p[1],parent:c});else if("start tag"==p[0]){"chapters"==i&&o("Start tags not allowed in chapter title text.");var m=p[1];"v"!=m&&"lang"!=m&&""!=p[3]&&o("Only and can have an annotation."),"c"==m||"i"==m||"b"==m||"u"==m||"ruby"==m||"rt"==m&&"ruby"==c.name?u(p):"v"==m?(d("v")&&o(" cannot be nested inside itself."),u(p),c.value=p[3],p[3]||o(" requires an annotation.")):"lang"==m?(u(p),c.value=p[3]):o("Incorrect start tag.")}else if("end tag"==p[0])"chapters"==i&&o("End tags not allowed in chapter title text."),p[1]==c.name?c=c.parent:"ruby"==p[1]&&"rt"==c.name?c=c.parent.parent:o("Incorrect end tag.");else if("timestamp"==p[0]){"chapters"==i&&o("Timestamp not allowed in chapter title text.");var g=new n(p[1],o).parseTimestamp();void 0!=g&&((g<=t||g>=s)&&o("Timestamp must be between start timestamp and end timestamp."),f.length>0&&f[f.length-1]>=g&&o("Timestamp must be greater than any previous timestamp."),c.children.push({type:"timestamp",value:g,parent:c}),f.push(g))}}for(;c.parent;)"v"!=c.name&&o("Required end tag missing."),c=c.parent;return function e(t){const n={...t};return t.children&&(n.children=t.children.map(e)),n.parent&&delete n.parent,n}(a)}},s=function(){function t(e){const t=("00"+1e3*(e-Math.floor(e)).toFixed(3)).slice(-3);let n=0,i=0,s=0;return e>=3600&&(n=Math.floor(e/3600)),i=Math.floor((e-3600*n)/60),s=Math.floor(e-3600*n-60*i),(n?n+":":"")+(""+i).padStart(2,"0")+":"+(""+s).padStart(2,"0")+"."+t}function n(e){for(var i="",s=0;s/g,">");else if("object"==a.type){if(i+="<"+a.name,a.classes)for(var r=0;r"}else"timestamp"==a.type?i+="<"+t(a.value)+">":i+="<"+a.value+">"}return i}function i(i){return(void 0!==i.id?i.id+"\n":"")+t(i.startTime)+" --\x3e "+t(i.endTime)+function(t){var n="";const i=Object.keys(e).filter(n=>t[n]!==e[n]);return i.includes("direction")&&(n+=" vertical:"+t.direction),i.includes("alignment")&&(n+=" align:"+t.alignment),i.includes("size")&&(n+=" size:"+t.size+"%"),(i.includes("lineAlign")||i.includes("linePosition"))&&(n+=" line:"+t.linePosition+(t.snapToLines?"":"%")+(t.lineAlign&&t.lineAlign!=e.lineAlign?","+t.lineAlign:"")),(i.includes("textPosition")||i.includes("positionAlign"))&&(n+=" position:"+t.textPosition+"%"+(t.positionAlign&&t.positionAlign!==e.positionAlign?","+t.positionAlign:"")),n}(i)+"\n"+n(i.tree.children)+"\n\n"}function s(e){return"STYLE\n"+e+"\n\n"}this.serialize=function(e,t){var n="WEBVTT\n\n";if(t)for(var a=0;a",">":">","&lrm":"\u200e","‎":"\u200e","&rlm":"\u200f","‏":"\u200f"," ":"\xa0"," ":"\xa0"};const r="artplayer-multiple-subtitles-timestamp";function o(e){return{type:"object",name:"c",classes:[r],children:[e]}}function l(e){return"timestamp"===e.type?o(e):"object"===e.type?{...e,children:e.children.map(l)}:{...e,value:(t=e.value,t.replace(/&/g,"&").replace(//g,">"))};var t}function c(e,t){const n=!!e.subtitle&&"activeCue"in e.subtitle&&!("activeCues"in e.subtitle),i=n?"subtitleUpdate":"subtitleAfterUpdate";function s(){if(!t.closed){n&&function(e){const t=e.subtitle?.textTrack?.activeCues;if(!t||t.length<2)return;const n=Array.from(t,e=>"text"in e&&"string"===typeof e.text?e.text:""),i=e.template.$subtitle;if(e.option?.subtitle?.escape){i.innerHTML="";for(const e of n)for(const t of e.split(/\r?\n/)){const e=i.ownerDocument.createElement("div");e.className="art-subtitle-line",e.textContent=t,i.appendChild(e)}}else i.innerHTML=n.join("")}(e);for(const t of Array.from(e.template.$subtitle.getElementsByTagName(`c.${r}`))){const e=t.firstChild;3===e?.nodeType&&(e.nodeValue=(e.nodeValue||"").replace(/^<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/,"")),t.replaceWith(...Array.from(t.childNodes))}}}t.closed||(t.own(()=>e.off(i,s)),e.on(i,s))}function f(e,t,n){let i=null;return s=>{if(t.closed)return;const a=URL.createObjectURL(new Blob([s],{type:"text/vtt"})),r=function(e){let n=!1,i=()=>{};const s={free(){n||(n=!0,i(),URL.revokeObjectURL(e))}};return i=t.own(s.free),s}(a);if(t.closed)return;const o=i;let l,c,f=!1;i=r;try{if(l=e.option.subtitle,c=l.escape,t.closed||i!==r)return;l.escape=!1,f=!0;const s={...l,url:a,type:"vtt",onVttLoad:n};if(t.closed||i!==r)return;const o=e.subtitle.init(s);Promise.resolve(o).catch(e=>{i===r&&(i=null,r.free()),t.closed||console.warn("Failed to initialize multiple subtitles:",e)})}catch(u){throw i===r&&(i=o,f&&!1===l.escape&&(l.escape=c)),r.free(),u}finally{o!==i&&o?.free(),r!==i&&r.free()}}}function u({subtitles:e=[]}){return async n=>{const{unescape:i,getExt:r,srtToVtt:u,assToVtt:d}=n.constructor.utils,p=function(e){let t=Boolean(e.isDestroy);const n=new Set;let i;const s=new Promise(e=>{i=e});function a(e){try{e()}catch(t){console.warn("Failed to clean up multiple subtitles:",t)}}const r={get closed(){return t},own:e=>(t?a(e):n.add(e),()=>n.delete(e)),wait:e=>Promise.race([e,s]),dispose(){if(t)return;t=!0,i();const e=[...n].reverse();n.clear();for(const t of e)a(t)}};if(!t){r.own(()=>e.off("destroy",r.dispose));try{e.on("destroy",r.dispose)}catch(o){throw r.dispose(),o}}return r}(n),m=f(n,p,i);let g=[];function h(e){p.closed||m(function(e){const t=[];for(const n of e){const e=n;for(const n of e.cues){const i=[];for(const t of n.tree.children)"timestamp"===t.type?i.push(o(t)):i.push({type:"text",value:`
`},l(t),{type:"text",value:"
"});t.push({...n,tree:{...n.tree,children:i}})}}return(new s).serialize(t)}(e))}const v={name:"multipleSubtitles",tracks(e=[]){p.closed||h(e.map(e=>g.find(t=>t.name===e)))},reset(){h(g)}};try{if(p.closed)return v;const i=await Promise.all(e.map(e=>async function(e,{getExt:t,srtToVtt:n,assToVtt:i},s){if(s.closed)return;const a="function"===typeof AbortController?new AbortController:null,r=s.own(()=>a?.abort());try{const r=e.url;if(s.closed)return;const o=await s.wait(a?fetch(r,{signal:a.signal}):fetch(r));if(s.closed)return;if(!1===o.ok)throw new Error(`Failed to fetch multiple subtitles: HTTP ${o.status}`);const l=await s.wait(o.arrayBuffer());if(s.closed)return;const c=new TextDecoder(e.encoding||"utf-8").decode(l);switch(e.type||t(e.url)){case"srt":return n(c);case"ass":return i(c);case"vtt":return c;default:return""}}catch(o){throw a?.abort(),o}finally{r()}}(e,{getExt:r,srtToVtt:u,assToVtt:d},p)));return p.closed||(g=function(e,n){const i=new t(a);return e.map((e,t)=>({...i.parse(e,"metadata"),url:n[t].url,name:n[t].name}))}(i,e),c(n,p),h(g)),v}catch(b){throw p.dispose(),b}}}return Object.assign(u,{default:u})}); +!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),(gb&&" "!==m[b]&&"\t"!==m[b])&&p('No valid signature. (File needs to start with "WEBVTT".)'),o++;""!=l[o]&&void 0!=l[o];){if(p("No blank line after the signature."),-1!=l[o].indexOf("--\x3e")){c=!0;break}o++}for(;void 0!=l[o];){for(var x;!c&&""==l[o];)o++;if(!c&&void 0==l[o])break;x=Object.assign({},e,{id:"",startTime:0,endTime:0,pauseOnExit:!1,direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center",text:"",tree:null});var T=!0;if(-1==l[o].indexOf("--\x3e")){if(x.id=l[o],/^NOTE($|[ \t])/.test(x.id)){for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&p("Cannot have timestamp in a comment."),o++;continue}if(/^STYLE($|[ \t])/.test(x.id)){var y=[],w=!1;for(o++;""!=l[o]&&void 0!=l[o];)-1!=l[o].indexOf("--\x3e")&&(p("Cannot have timestamp in a style block."),w=!0),y.push(l[o]),o++;if(u.length){p("Style blocks cannot appear after the first cue.");continue}w||f.push(y.join("\n"));continue}if(""==l[++o]||void 0==l[o]){p("Cue identifier cannot be standalone.");continue}if(-1==l[o].indexOf("--\x3e")){T=!1,p("Cue identifier needs to be followed by timestamp.");continue}}c=!1;var $=new n(l[o],p),A=0;if(u.length>0&&(A=u[u.length-1].startTime),!T||$.parse(x,A)){for(o++;""!=l[o]&&void 0!=l[o];){if(-1!=l[o].indexOf("--\x3e")){p("Blank line missing before cue."),c=!0;break}""!=x.text&&(x.text+="\n"),x.text+=l[o],o++}var O=new i(x.text,p,a,t);x.tree=O.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.startTimet.startTime?1:e.endTime>t.endTime?-1:e.endTime2||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"==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;r100)){a("Line position cannot be >100%.");continue}if(""===d||isNaN(d)||!isFinite(d)){a("Line position needs to be a number");continue}if(void 0!==u){if(!["start","center","end"].includes(u)){a("Line alignment needs to be one of start, center or end");continue}t.lineAlign=u}t.snapToLines=!p,t.linePosition=parseFloat(d),parseFloat(d).toString()!==d&&(t.nonSerializable=!0)}else if("position"==l){if(/,/.test(c)){f=c.split(","),c=f[0];var m=f[1]}if("%"!=c[c.length-1]){a("Text position must be a percentage.");continue}if(parseInt(c,10)>100||parseInt(c,10)<0){a("Text position needs to be between 0 and 100%.");continue}if(""===(d=c.slice(0,c.length-1))||isNaN(d)||!isFinite(d)){a("Line position needs to be a number");continue}if(void 0!==m){if(!["line-left","center","line-right"].includes(m)){a("Position alignment needs to be one of line-left, center or line-right");continue}t.positionAlign=m}t.textPosition=parseFloat(d)}else if("size"==l){if("%"!=c[c.length-1]){a("Size must be a percentage.");continue}if(parseInt(c,10)>100){a("Size cannot be >100%.");continue}var g=c.slice(0,c.length-1);if(void 0===g||""===g||isNaN(g)){a("Size needs to be a number"),g=100;continue}if((g=parseFloat(g))<0||g>100){a("Size needs to be between 0 and 100%.");continue}t.size=g}else if("align"==l){var h=["start","center","end","left","right"];if(-1==h.indexOf(c)){a("Alignment can only be set to one of "+h.join(", ")+".");continue}t.alignment=c}else a("Invalid setting.")}}(e.substring(s),t),!0}else a("No valid timestamp separator found.");else a("No valid timestamp separator found.");else a("No valid timestamp separator found.")},this.parseTimestamp=function(){var t=l();if(void 0==e[s])return t;a("Timestamp must not have trailing characters.")}},i=function(e,t,i,s){this.entities=s;var a=this,r=(e=e,0),o=function(e){"metadata"!=i&&t(e,r+1)};function l(){for(var t="data",n="",i="",l=[];void 0!=e[r-1]||0==r;){var c=e[r];if("data"==t)if("&"==c)i=c,t="escape";else if("<"==c&&""==n)t="tag";else{if("<"==c||void 0==c)return["text",n];n+=c}else if("escape"==t){if("<"==c||void 0==c){let e;return o("Incorrect escape."),(e=i.match(/^&#([0-9]+)$/))?n+=String.fromCharCode(e[1]):a.entities[i]?n+=a.entities[i]:n+=i,["text",n]}if("&"==c)o("Incorrect escape."),n+=i,i=c;else if(/[a-z#0-9]/i.test(c))i+=c;else if(";"==c){let e;(e=i.match(/^&#(x?[0-9]+)$/))?n+=String.fromCharCode("0"+e[1]):a.entities[i+c]?n+=a.entities[i+c]:(e=Object.keys(s).find(e=>i.startsWith(e)))?n+=a.entities[e]+i.slice(e.length)+c:(o("Incorrect escape."),n+=i+";"),t="data"}else o("Incorrect escape."),n+=i+c,t="data"}else if("tag"==t)if("\t"==c||"\n"==c||"\f"==c||" "==c)t="start tag annotation";else if("."==c)t="start tag class";else if("/"==c)t="end tag";else if(/\d/.test(c))n=c,t="timestamp tag";else{if(">"==c||void 0==c)return">"==c&&r++,["start tag","",[],""];n=c,t="start tag"}else if("start tag"==t)if("\t"==c||"\f"==c||" "==c)t="start tag annotation";else if("\n"==c)i=c,t="start tag annotation";else if("."==c)t="start tag class";else{if(">"==c||void 0==c)return">"==c&&r++,["start tag",n,[],""];n+=c}else if("start tag class"==t)if("\t"==c||"\f"==c||" "==c)i&&l.push(i),i="",t="start tag annotation";else if("\n"==c)i&&l.push(i),i=c,t="start tag annotation";else if("."==c)i&&l.push(i),i="";else{if(">"==c||void 0==c)return">"==c&&r++,i&&l.push(i),["start tag",n,l,""];i+=c}else if("start tag annotation"==t){if(">"==c||void 0==c)return">"==c&&r++,["start tag",n,l,i=i.split(/[\u0020\t\f\r\n]+/).filter(function(e){if(e)return!0}).join(" ")];i+=c}else if("end tag"==t){if(">"==c||void 0==c)return">"==c&&r++,["end tag",n];n+=c}else if("timestamp tag"==t){if(">"==c||void 0==c)return">"==c&&r++,["timestamp",n];n+=c}else o("Never happens.");r++}}this.parse=function(t,s){var a={children:[]},c=a,f=[];function u(e){c.children.push({type:"object",name:e[1],classes:e[2],children:[],parent:c}),c=c.children[c.children.length-1]}function d(e){for(var t=c;t;){if(t.name==e)return!0;t=t.parent}}for(;void 0!=e[r];){var p=l();if("text"==p[0])c.children.push({type:"text",value:p[1],parent:c});else if("start tag"==p[0]){"chapters"==i&&o("Start tags not allowed in chapter title text.");var m=p[1];"v"!=m&&"lang"!=m&&""!=p[3]&&o("Only and can have an annotation."),"c"==m||"i"==m||"b"==m||"u"==m||"ruby"==m||"rt"==m&&"ruby"==c.name?u(p):"v"==m?(d("v")&&o(" cannot be nested inside itself."),u(p),c.value=p[3],p[3]||o(" requires an annotation.")):"lang"==m?(u(p),c.value=p[3]):o("Incorrect start tag.")}else if("end tag"==p[0])"chapters"==i&&o("End tags not allowed in chapter title text."),p[1]==c.name?c=c.parent:"ruby"==p[1]&&"rt"==c.name?c=c.parent.parent:o("Incorrect end tag.");else if("timestamp"==p[0]){"chapters"==i&&o("Timestamp not allowed in chapter title text.");var g=new n(p[1],o).parseTimestamp();void 0!=g&&((g<=t||g>=s)&&o("Timestamp must be between start timestamp and end timestamp."),f.length>0&&f[f.length-1]>=g&&o("Timestamp must be greater than any previous timestamp."),c.children.push({type:"timestamp",value:g,parent:c}),f.push(g))}}for(;c.parent;)"v"!=c.name&&o("Required end tag missing."),c=c.parent;return function e(t){const n={...t};return t.children&&(n.children=t.children.map(e)),n.parent&&delete n.parent,n}(a)}},s=function(){function t(e){const t=("00"+1e3*(e-Math.floor(e)).toFixed(3)).slice(-3);let n=0,i=0,s=0;return e>=3600&&(n=Math.floor(e/3600)),i=Math.floor((e-3600*n)/60),s=Math.floor(e-3600*n-60*i),(n?n+":":"")+(""+i).padStart(2,"0")+":"+(""+s).padStart(2,"0")+"."+t}function n(e){for(var i="",s=0;s/g,">");else if("object"==a.type){if(i+="<"+a.name,a.classes)for(var r=0;r"}else"timestamp"==a.type?i+="<"+t(a.value)+">":i+="<"+a.value+">"}return i}function i(i){return(void 0!==i.id?i.id+"\n":"")+t(i.startTime)+" --\x3e "+t(i.endTime)+function(t){var n="";const i=Object.keys(e).filter(n=>t[n]!==e[n]);return i.includes("direction")&&(n+=" vertical:"+t.direction),i.includes("alignment")&&(n+=" align:"+t.alignment),i.includes("size")&&(n+=" size:"+t.size+"%"),(i.includes("lineAlign")||i.includes("linePosition"))&&(n+=" line:"+t.linePosition+(t.snapToLines?"":"%")+(t.lineAlign&&t.lineAlign!=e.lineAlign?","+t.lineAlign:"")),(i.includes("textPosition")||i.includes("positionAlign"))&&(n+=" position:"+t.textPosition+"%"+(t.positionAlign&&t.positionAlign!==e.positionAlign?","+t.positionAlign:"")),n}(i)+"\n"+n(i.tree.children)+"\n\n"}function s(e){return"STYLE\n"+e+"\n\n"}this.serialize=function(e,t){var n="WEBVTT\n\n";if(t)for(var a=0;a",">":">","&lrm":"\u200e","‎":"\u200e","&rlm":"\u200f","‏":"\u200f"," ":"\xa0"," ":"\xa0"};const r="artplayer-multiple-subtitles-timestamp";function o(e){return{type:"object",name:"c",classes:[r],children:[e]}}function l(e){return"timestamp"===e.type?o(e):"object"===e.type?{...e,children:e.children.map(l)}:{...e,value:(t=e.value,t.replace(/&/g,"&").replace(//g,">"))};var t}function c(e,t){const n=!!e.subtitle&&"activeCue"in e.subtitle&&!("activeCues"in e.subtitle),i=n?"subtitleUpdate":"subtitleAfterUpdate";function s(){if(!t.closed){n&&function(e){const t=e.subtitle?.textTrack?.activeCues;if(!t||t.length<2)return;const n=Array.from(t,e=>"text"in e&&"string"===typeof e.text?e.text:""),i=e.template.$subtitle;if(e.option?.subtitle?.escape){i.innerHTML="";for(const e of n)for(const t of e.split(/\r?\n/)){const e=i.ownerDocument.createElement("div");e.className="art-subtitle-line",e.textContent=t,i.appendChild(e)}}else i.innerHTML=n.join("")}(e);for(const t of Array.from(e.template.$subtitle.getElementsByTagName(`c.${r}`))){const e=t.firstChild;3===e?.nodeType&&(e.nodeValue=(e.nodeValue||"").replace(/^<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/,"")),t.replaceWith(...Array.from(t.childNodes))}}}t.closed||(t.own(()=>e.off(i,s)),e.on(i,s))}function f(e,t,n){let i=null;return s=>{if(t.closed)return;const a=URL.createObjectURL(new Blob([s],{type:"text/vtt"})),r=function(e){let n=!1,i=()=>{};const s={free(){n||(n=!0,i(),URL.revokeObjectURL(e))}};return i=t.own(s.free),s}(a);if(t.closed)return;const o=i;let l,c,f=!1;i=r;try{if(l=e.option.subtitle,c=l.escape,t.closed||i!==r)return;l.escape=!1,f=!0;const s={...l,url:a,type:"vtt",onVttLoad:n};if(t.closed||i!==r)return;const o=e.subtitle.init(s);Promise.resolve(o).catch(e=>{i===r&&(i=null,r.free()),t.closed||console.warn("Failed to initialize multiple subtitles:",e)})}catch(u){throw i===r&&(i=o,f&&!1===l.escape&&(l.escape=c)),r.free(),u}finally{o!==i&&o?.free(),r!==i&&r.free()}}}function u({subtitles:e=[]}){return async n=>{const{unescape:i,getExt:r,srtToVtt:u,assToVtt:d}=n.constructor.utils,p=function(e){let t=Boolean(e.isDestroy);const n=new Set;let i;const s=new Promise(e=>{i=e});function a(e){try{e()}catch(t){console.warn("Failed to clean up multiple subtitles:",t)}}const r={get closed(){return t},own:e=>(t?a(e):n.add(e),()=>n.delete(e)),wait:e=>Promise.race([e,s]),dispose(){if(t)return;t=!0,i();const e=[...n].reverse();n.clear();for(const t of e)a(t)}};if(!t){r.own(()=>e.off("destroy",r.dispose));try{e.on("destroy",r.dispose)}catch(o){throw r.dispose(),o}}return r}(n),m=f(n,p,i);let g=[];function h(e){p.closed||m(function(e){const t=[];for(const n of e){const e=n;for(const n of e.cues){const i=[];for(const t of n.tree.children)"timestamp"===t.type?i.push(o(t)):i.push({type:"text",value:`
`},l(t),{type:"text",value:"
"});t.push({...n,tree:{...n.tree,children:i}})}}return(new s).serialize(t)}(e))}const v={name:"multipleSubtitles",tracks(e=[]){p.closed||h(e.map(e=>g.find(t=>t.name===e)))},reset(){h(g)}};try{if(p.closed)return v;const i=await Promise.all(e.map(e=>async function(e,{getExt:t,srtToVtt:n,assToVtt:i},s){if(s.closed)return;const a="function"===typeof AbortController?new AbortController:null,r=s.own(()=>a?.abort());try{const r=e.url;if(s.closed)return;const o=await s.wait(a?fetch(r,{signal:a.signal}):fetch(r));if(s.closed)return;if(!1===o.ok)throw new Error(`Failed to fetch multiple subtitles: HTTP ${o.status}`);const l=await s.wait(o.arrayBuffer());if(s.closed)return;const c=new TextDecoder(e.encoding||"utf-8").decode(l);switch(e.type||t(e.url)){case"srt":return n(c);case"ass":return function(e,t){const n=t(e);if(!/^WEBVTT \d+ \d+:/.test(n))return n;const i=[];for(const s of e.split(/\r?\n/)){const e=s.match(/Dialogue:\s\d,(\d+:\d{2}:\d{2}\.\d{2}),(\d+:\d{2}:\d{2}\.\d{2}),(?:[^,]*,){6}([\s\S]*)$/i);if(!e)continue;const[,t="",n="",a=""]=e;i.push({start:`${t.replace(/^(\d):/,"0$1:")}0`,end:`${n.replace(/^(\d):/,"0$1:")}0`,text:a.replace(/\{[\s\S]*?\}/g,"").replace(/\\N/g,"\n").trim().split(/\r?\n/).map(e=>e.trim()).join("\n")})}return n!==`WEBVTT ${i.map((e,t)=>`${t+1} ${e.start} --\x3e ${e.end} ${e.text}`).join("\n\n")}`?n:`WEBVTT\n\n${i.map((e,t)=>`${t+1}\n${e.start} --\x3e ${e.end}\n${e.text}`).join("\n\n")}`}(c,i);case"vtt":return c;default:return""}}catch(o){throw a?.abort(),o}finally{r()}}(e,{getExt:r,srtToVtt:u,assToVtt:d},p)));return p.closed||(g=function(e,n){const i=new t(a);return e.map((e,t)=>({...i.parse(e,"metadata"),url:n[t].url,name:n[t].name}))}(i,e),c(n,p),h(g)),v}catch(b){throw p.dispose(),b}}}return Object.assign(u,{default:u})}); diff --git a/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.legacy.js b/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.legacy.js index 2acbfccb5..ee39b3f8b 100644 --- a/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.legacy.js +++ b/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.legacy.js @@ -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,o=(t,n,i)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[n]=i,a=(e,t)=>{for(var n in t||(t={}))r.call(t,n)&&o(e,n,t[n]);if(i)for(var n of i(t))s.call(t,n)&&o(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{a(n.next(e))}catch(t){r(t)}},o=e=>{try{a(n.throw(e))}catch(t){r(t)}},a=e=>e.done?i(e.value):Promise.resolve(e.value).then(s,o);a((n=n.apply(e,t)).next())});var u={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},f=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/),o=!1,a=[],l=[],c=[];function f(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),(gb&&" "!==m[b]&&"\t"!==m[b])&&f('No valid signature. (File needs to start with "WEBVTT".)'),r++;""!=s[r]&&void 0!=s[r];){if(f("No blank line after the signature."),-1!=s[r].indexOf("--\x3e")){o=!0;break}r++}for(;void 0!=s[r];){for(var y;!o&&""==s[r];)r++;if(!o&&void 0==s[r])break;y=Object.assign({},u,{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")&&f("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")&&(f("Cannot have timestamp in a style block."),w=!0),T.push(s[r]),r++;if(l.length){f("Style blocks cannot appear after the first cue.");continue}w||a.push(T.join("\n"));continue}if(""==s[++r]||void 0==s[r]){f("Cue identifier cannot be standalone.");continue}if(-1==s[r].indexOf("--\x3e")){x=!1,f("Cue identifier needs to be followed by timestamp.");continue}}o=!1;var O=new d(s[r],f),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")){f("Blank line missing before cue."),o=!0;break}""!=y.text&&(y.text+="\n"),y.text+=s[r],r++}var A=new p(y.text,f,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")){o=!0;break}r++}}return l.sort(function(e,t){return e.startTimet.startTime?1:e.endTime>t.endTime?-1:e.endTime2||parseInt(t,10)>59)&&(l="hours"),":"==e[r])if(r++,2==(n=a(/\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=a(/\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==(o=a(/\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(o,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,a){if(o(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime"==e[r]){if(r++,i.test(e[r])&&s("'--\x3e' not separated from timestamp by whitespace."),o(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]),o(n),function(e,t){for(var i=e.split(n),r=[],o=0;o100)){s("Line position cannot be >100%.");continue}if(""===d||isNaN(d)||!isFinite(d)){s("Line position needs to be a number");continue}if(void 0!==f){if(!["start","center","end"].includes(f)){s("Line alignment needs to be one of start, center or end");continue}t.lineAlign=f}t.snapToLines=!p,t.linePosition=parseFloat(d),parseFloat(d).toString()!==d&&(t.nonSerializable=!0)}else if("position"==l){if(/,/.test(c)){u=c.split(","),c=u[0];var m=u[1]}if("%"!=c[c.length-1]){s("Text position must be a percentage.");continue}if(parseInt(c,10)>100||parseInt(c,10)<0){s("Text position needs to be between 0 and 100%.");continue}if(""===(d=c.slice(0,c.length-1))||isNaN(d)||!isFinite(d)){s("Line position needs to be a number");continue}if(void 0!==m){if(!["line-left","center","line-right"].includes(m)){s("Position alignment needs to be one of line-left, center or line-right");continue}t.positionAlign=m}t.textPosition=parseFloat(d)}else if("size"==l){if("%"!=c[c.length-1]){s("Size must be a percentage.");continue}if(parseInt(c,10)>100){s("Size cannot be >100%.");continue}var g=c.slice(0,c.length-1);if(void 0===g||""===g||isNaN(g)){s("Size needs to be a number"),g=100;continue}if((g=parseFloat(g))<0||g>100){s("Size needs to be between 0 and 100%.");continue}t.size=g}else if("align"==l){var h=["start","center","end","left","right"];if(-1==h.indexOf(c)){s("Alignment can only be set to one of "+h.join(", ")+".");continue}t.alignment=c}else s("Invalid setting.")}}(e.substring(r),t),!0}else s("No valid timestamp separator found.");else s("No valid timestamp separator found.");else s("No valid timestamp separator found.")},this.parseTimestamp=function(){var t=l();if(void 0==e[r])return t;s("Timestamp must not have trailing characters.")}},p=function(e,t,n,i){this.entities=i;var r=this,s=(e=e,0),o=function(e){"metadata"!=n&&t(e,s+1)};function l(){for(var t="data",n="",a="",l=[];void 0!=e[s-1]||0==s;){var c=e[s];if("data"==t)if("&"==c)a=c,t="escape";else if("<"==c&&""==n)t="tag";else{if("<"==c||void 0==c)return["text",n];n+=c}else if("escape"==t){if("<"==c||void 0==c){let e;return o("Incorrect escape."),(e=a.match(/^&#([0-9]+)$/))?n+=String.fromCharCode(e[1]):r.entities[a]?n+=r.entities[a]:n+=a,["text",n]}if("&"==c)o("Incorrect escape."),n+=a,a=c;else if(/[a-z#0-9]/i.test(c))a+=c;else if(";"==c){let e;(e=a.match(/^&#(x?[0-9]+)$/))?n+=String.fromCharCode("0"+e[1]):r.entities[a+c]?n+=r.entities[a+c]:(e=Object.keys(i).find(e=>a.startsWith(e)))?n+=r.entities[e]+a.slice(e.length)+c:(o("Incorrect escape."),n+=a+";"),t="data"}else o("Incorrect escape."),n+=a+c,t="data"}else if("tag"==t)if("\t"==c||"\n"==c||"\f"==c||" "==c)t="start tag annotation";else if("."==c)t="start tag class";else if("/"==c)t="end tag";else if(/\d/.test(c))n=c,t="timestamp tag";else{if(">"==c||void 0==c)return">"==c&&s++,["start tag","",[],""];n=c,t="start tag"}else if("start tag"==t)if("\t"==c||"\f"==c||" "==c)t="start tag annotation";else if("\n"==c)a=c,t="start tag annotation";else if("."==c)t="start tag class";else{if(">"==c||void 0==c)return">"==c&&s++,["start tag",n,[],""];n+=c}else if("start tag class"==t)if("\t"==c||"\f"==c||" "==c)a&&l.push(a),a="",t="start tag annotation";else if("\n"==c)a&&l.push(a),a=c,t="start tag annotation";else if("."==c)a&&l.push(a),a="";else{if(">"==c||void 0==c)return">"==c&&s++,a&&l.push(a),["start tag",n,l,""];a+=c}else if("start tag annotation"==t){if(">"==c||void 0==c)return">"==c&&s++,["start tag",n,l,a=a.split(/[\u0020\t\f\r\n]+/).filter(function(e){if(e)return!0}).join(" ")];a+=c}else if("end tag"==t){if(">"==c||void 0==c)return">"==c&&s++,["end tag",n];n+=c}else if("timestamp tag"==t){if(">"==c||void 0==c)return">"==c&&s++,["timestamp",n];n+=c}else o("Never happens.");s++}}this.parse=function(t,i){var r={children:[]},c=r,u=[];function f(e){c.children.push({type:"object",name:e[1],classes:e[2],children:[],parent:c}),c=c.children[c.children.length-1]}function p(e){for(var t=c;t;){if(t.name==e)return!0;t=t.parent}}for(;void 0!=e[s];){var m=l();if("text"==m[0])c.children.push({type:"text",value:m[1],parent:c});else if("start tag"==m[0]){"chapters"==n&&o("Start tags not allowed in chapter title text.");var g=m[1];"v"!=g&&"lang"!=g&&""!=m[3]&&o("Only and can have an annotation."),"c"==g||"i"==g||"b"==g||"u"==g||"ruby"==g||"rt"==g&&"ruby"==c.name?f(m):"v"==g?(p("v")&&o(" cannot be nested inside itself."),f(m),c.value=m[3],m[3]||o(" requires an annotation.")):"lang"==g?(f(m),c.value=m[3]):o("Incorrect start tag.")}else if("end tag"==m[0])"chapters"==n&&o("End tags not allowed in chapter title text."),m[1]==c.name?c=c.parent:"ruby"==m[1]&&"rt"==c.name?c=c.parent.parent:o("Incorrect end tag.");else if("timestamp"==m[0]){"chapters"==n&&o("Timestamp not allowed in chapter title text.");var h=new d(m[1],o).parseTimestamp();void 0!=h&&((h<=t||h>=i)&&o("Timestamp must be between start timestamp and end timestamp."),u.length>0&&u[u.length-1]>=h&&o("Timestamp must be greater than any previous timestamp."),c.children.push({type:"timestamp",value:h,parent:c}),u.push(h))}}for(;c.parent;)"v"!=c.name&&o("Required end tag missing."),c=c.parent;return function e(t){const n=a({},t);return t.children&&(n.children=t.children.map(e)),n.parent&&delete n.parent,n}(r)}},m=function(){function e(e){const t=("00"+1e3*(e-Math.floor(e)).toFixed(3)).slice(-3);let n=0,i=0,r=0;return e>=3600&&(n=Math.floor(e/3600)),i=Math.floor((e-3600*n)/60),r=Math.floor(e-3600*n-60*i),(n?n+":":"")+(""+i).padStart(2,"0")+":"+(""+r).padStart(2,"0")+"."+t}function t(n){for(var i="",r=0;r/g,">");else if("object"==s.type){if(i+="<"+s.name,s.classes)for(var o=0;o"}else"timestamp"==s.type?i+="<"+e(s.value)+">":i+="<"+s.value+">"}return i}function n(n){return(void 0!==n.id?n.id+"\n":"")+e(n.startTime)+" --\x3e "+e(n.endTime)+function(e){var t="";const n=Object.keys(u).filter(t=>e[t]!==u[t]);return n.includes("direction")&&(t+=" vertical:"+e.direction),n.includes("alignment")&&(t+=" align:"+e.alignment),n.includes("size")&&(t+=" size:"+e.size+"%"),(n.includes("lineAlign")||n.includes("linePosition"))&&(t+=" line:"+e.linePosition+(e.snapToLines?"":"%")+(e.lineAlign&&e.lineAlign!=u.lineAlign?","+e.lineAlign:"")),(n.includes("textPosition")||n.includes("positionAlign"))&&(t+=" position:"+e.textPosition+"%"+(e.positionAlign&&e.positionAlign!==u.positionAlign?","+e.positionAlign:"")),t}(n)+"\n"+t(n.tree.children)+"\n\n"}function i(e){return"STYLE\n"+e+"\n\n"}this.serialize=function(e,t){var r="WEBVTT\n\n";if(t)for(var s=0;s",">":">","&lrm":"\u200e","‎":"\u200e","&rlm":"\u200f","‏":"\u200f"," ":"\xa0"," ":"\xa0"};const h="artplayer-multiple-subtitles-timestamp";function v(e){return{type:"object",name:"c",classes:[h],children:[e]}}function b(e){return"timestamp"===e.type?v(e):"object"===e.type?l(a({},e),{children:e.children.map(b)}):l(a({},e),{value:(t=e.value,t.replace(/&/g,"&").replace(//g,">"))});var t}function y(e,t){const n=!!e.subtitle&&"activeCue"in e.subtitle&&!("activeCues"in e.subtitle),i=n?"subtitleUpdate":"subtitleAfterUpdate";function r(){if(!t.closed){n&&function(e){var t,n,i,r;const s=null==(n=null==(t=e.subtitle)?void 0:t.textTrack)?void 0:n.activeCues;if(!s||s.length<2)return;const o=Array.from(s,e=>"text"in e&&"string"===typeof e.text?e.text:""),a=e.template.$subtitle;if(null==(r=null==(i=e.option)?void 0:i.subtitle)?void 0:r.escape){a.innerHTML="";for(const e of o)for(const t of e.split(/\r?\n/)){const e=a.ownerDocument.createElement("div");e.className="art-subtitle-line",e.textContent=t,a.appendChild(e)}}else a.innerHTML=o.join("")}(e);for(const t of Array.from(e.template.$subtitle.getElementsByTagName(`c.${h}`))){const e=t.firstChild;3===(null==e?void 0:e.nodeType)&&(e.nodeValue=(e.nodeValue||"").replace(/^<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/,"")),t.replaceWith(...Array.from(t.childNodes))}}}t.closed||(t.own(()=>e.off(i,r)),e.on(i,r))}function x(e,t,n){let i=null;return r=>{if(t.closed)return;const s=URL.createObjectURL(new Blob([r],{type:"text/vtt"})),o=function(e){let n=!1,i=()=>{};const r={free(){n||(n=!0,i(),URL.revokeObjectURL(e))}};return i=t.own(r.free),r}(s);if(t.closed)return;const c=i;let u,f,d=!1;i=o;try{if(u=e.option.subtitle,f=u.escape,t.closed||i!==o)return;u.escape=!1,d=!0;const r=l(a({},u),{url:s,type:"vtt",onVttLoad:n});if(t.closed||i!==o)return;const c=e.subtitle.init(r);Promise.resolve(c).catch(e=>{i===o&&(i=null,o.free()),t.closed||console.warn("Failed to initialize multiple subtitles:",e)})}catch(p){throw i===o&&(i=c,d&&!1===u.escape&&(u.escape=f)),o.free(),p}finally{c!==i&&(null==c||c.free()),o!==i&&o.free()}}}function T({subtitles:e=[]}){return t=>c(null,null,function*(){const{unescape:n,getExt:i,srtToVtt:r,assToVtt:s}=t.constructor.utils,o=function(e){let t=Boolean(e.isDestroy);const n=new Set;let i;const r=new Promise(e=>{i=e});function s(e){try{e()}catch(t){console.warn("Failed to clean up multiple subtitles:",t)}}const o={get closed(){return t},own:e=>(t?s(e):n.add(e),()=>n.delete(e)),wait:e=>Promise.race([e,r]),dispose(){if(t)return;t=!0,i();const e=[...n].reverse();n.clear();for(const t of e)s(t)}};if(!t){o.own(()=>e.off("destroy",o.dispose));try{e.on("destroy",o.dispose)}catch(a){throw o.dispose(),a}}return o}(t),u=x(t,o,n);let d=[];function p(e){o.closed||u(function(e){const t=[];for(const n of e){const e=n;for(const n of e.cues){const i=[];for(const t of n.tree.children)"timestamp"===t.type?i.push(v(t)):i.push({type:"text",value:`
`},b(t),{type:"text",value:"
"});t.push(l(a({},n),{tree:l(a({},n.tree),{children:i})}))}}return(new m).serialize(t)}(e))}const h={name:"multipleSubtitles",tracks(e=[]){o.closed||p(e.map(e=>d.find(t=>t.name===e)))},reset(){p(d)}};try{if(o.closed)return h;const n=yield Promise.all(e.map(e=>function(e,t,n){return c(this,arguments,function*(e,{getExt:t,srtToVtt:n,assToVtt:i},r){if(r.closed)return;const s="function"===typeof AbortController?new AbortController:null,o=r.own(()=>null==s?void 0:s.abort());try{const o=e.url;if(r.closed)return;const a=yield r.wait(s?fetch(o,{signal:s.signal}):fetch(o));if(r.closed)return;if(!1===a.ok)throw new Error(`Failed to fetch multiple subtitles: HTTP ${a.status}`);const l=yield r.wait(a.arrayBuffer());if(r.closed)return;const c=new TextDecoder(e.encoding||"utf-8").decode(l);switch(e.type||t(e.url)){case"srt":return n(c);case"ass":return i(c);case"vtt":return c;default:return""}}catch(a){throw null==s||s.abort(),a}finally{o()}})}(e,{getExt:i,srtToVtt:r,assToVtt:s},o)));return o.closed||(d=function(e,t){const n=new f(g);return e.map((e,i)=>{const r=n.parse(e,"metadata");return l(a({},r),{url:t[i].url,name:t[i].name})})}(n,e),y(t,o),p(d)),h}catch(T){throw o.dispose(),T}})}return Object.assign(T,{default:T})}); +!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,o=(t,n,i)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[n]=i,a=(e,t)=>{for(var n in t||(t={}))r.call(t,n)&&o(e,n,t[n]);if(i)for(var n of i(t))s.call(t,n)&&o(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{a(n.next(e))}catch(t){r(t)}},o=e=>{try{a(n.throw(e))}catch(t){r(t)}},a=e=>e.done?i(e.value):Promise.resolve(e.value).then(s,o);a((n=n.apply(e,t)).next())});var u={direction:"horizontal",snapToLines:!0,linePosition:"auto",lineAlign:"start",textPosition:"auto",positionAlign:"auto",size:100,alignment:"center"},f=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/),o=!1,a=[],l=[],c=[];function f(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),(gb&&" "!==m[b]&&"\t"!==m[b])&&f('No valid signature. (File needs to start with "WEBVTT".)'),r++;""!=s[r]&&void 0!=s[r];){if(f("No blank line after the signature."),-1!=s[r].indexOf("--\x3e")){o=!0;break}r++}for(;void 0!=s[r];){for(var y;!o&&""==s[r];)r++;if(!o&&void 0==s[r])break;y=Object.assign({},u,{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")&&f("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")&&(f("Cannot have timestamp in a style block."),w=!0),T.push(s[r]),r++;if(l.length){f("Style blocks cannot appear after the first cue.");continue}w||a.push(T.join("\n"));continue}if(""==s[++r]||void 0==s[r]){f("Cue identifier cannot be standalone.");continue}if(-1==s[r].indexOf("--\x3e")){x=!1,f("Cue identifier needs to be followed by timestamp.");continue}}o=!1;var O=new d(s[r],f),$=0;if(l.length>0&&($=l[l.length-1].startTime),!x||O.parse(y,$)){for(r++;""!=s[r]&&void 0!=s[r];){if(-1!=s[r].indexOf("--\x3e")){f("Blank line missing before cue."),o=!0;break}""!=y.text&&(y.text+="\n"),y.text+=s[r],r++}var j=new p(y.text,f,n,e);y.tree=j.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")){o=!0;break}r++}}return l.sort(function(e,t){return e.startTimet.startTime?1:e.endTime>t.endTime?-1:e.endTime2||parseInt(t,10)>59)&&(l="hours"),":"==e[r])if(r++,2==(n=a(/\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=a(/\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==(o=a(/\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(o,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,a){if(o(n),t.startTime=l(),void 0!=t.startTime)if(t.startTime"==e[r]){if(r++,i.test(e[r])&&s("'--\x3e' not separated from timestamp by whitespace."),o(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]),o(n),function(e,t){for(var i=e.split(n),r=[],o=0;o100)){s("Line position cannot be >100%.");continue}if(""===d||isNaN(d)||!isFinite(d)){s("Line position needs to be a number");continue}if(void 0!==f){if(!["start","center","end"].includes(f)){s("Line alignment needs to be one of start, center or end");continue}t.lineAlign=f}t.snapToLines=!p,t.linePosition=parseFloat(d),parseFloat(d).toString()!==d&&(t.nonSerializable=!0)}else if("position"==l){if(/,/.test(c)){u=c.split(","),c=u[0];var m=u[1]}if("%"!=c[c.length-1]){s("Text position must be a percentage.");continue}if(parseInt(c,10)>100||parseInt(c,10)<0){s("Text position needs to be between 0 and 100%.");continue}if(""===(d=c.slice(0,c.length-1))||isNaN(d)||!isFinite(d)){s("Line position needs to be a number");continue}if(void 0!==m){if(!["line-left","center","line-right"].includes(m)){s("Position alignment needs to be one of line-left, center or line-right");continue}t.positionAlign=m}t.textPosition=parseFloat(d)}else if("size"==l){if("%"!=c[c.length-1]){s("Size must be a percentage.");continue}if(parseInt(c,10)>100){s("Size cannot be >100%.");continue}var g=c.slice(0,c.length-1);if(void 0===g||""===g||isNaN(g)){s("Size needs to be a number"),g=100;continue}if((g=parseFloat(g))<0||g>100){s("Size needs to be between 0 and 100%.");continue}t.size=g}else if("align"==l){var h=["start","center","end","left","right"];if(-1==h.indexOf(c)){s("Alignment can only be set to one of "+h.join(", ")+".");continue}t.alignment=c}else s("Invalid setting.")}}(e.substring(r),t),!0}else s("No valid timestamp separator found.");else s("No valid timestamp separator found.");else s("No valid timestamp separator found.")},this.parseTimestamp=function(){var t=l();if(void 0==e[r])return t;s("Timestamp must not have trailing characters.")}},p=function(e,t,n,i){this.entities=i;var r=this,s=(e=e,0),o=function(e){"metadata"!=n&&t(e,s+1)};function l(){for(var t="data",n="",a="",l=[];void 0!=e[s-1]||0==s;){var c=e[s];if("data"==t)if("&"==c)a=c,t="escape";else if("<"==c&&""==n)t="tag";else{if("<"==c||void 0==c)return["text",n];n+=c}else if("escape"==t){if("<"==c||void 0==c){let e;return o("Incorrect escape."),(e=a.match(/^&#([0-9]+)$/))?n+=String.fromCharCode(e[1]):r.entities[a]?n+=r.entities[a]:n+=a,["text",n]}if("&"==c)o("Incorrect escape."),n+=a,a=c;else if(/[a-z#0-9]/i.test(c))a+=c;else if(";"==c){let e;(e=a.match(/^&#(x?[0-9]+)$/))?n+=String.fromCharCode("0"+e[1]):r.entities[a+c]?n+=r.entities[a+c]:(e=Object.keys(i).find(e=>a.startsWith(e)))?n+=r.entities[e]+a.slice(e.length)+c:(o("Incorrect escape."),n+=a+";"),t="data"}else o("Incorrect escape."),n+=a+c,t="data"}else if("tag"==t)if("\t"==c||"\n"==c||"\f"==c||" "==c)t="start tag annotation";else if("."==c)t="start tag class";else if("/"==c)t="end tag";else if(/\d/.test(c))n=c,t="timestamp tag";else{if(">"==c||void 0==c)return">"==c&&s++,["start tag","",[],""];n=c,t="start tag"}else if("start tag"==t)if("\t"==c||"\f"==c||" "==c)t="start tag annotation";else if("\n"==c)a=c,t="start tag annotation";else if("."==c)t="start tag class";else{if(">"==c||void 0==c)return">"==c&&s++,["start tag",n,[],""];n+=c}else if("start tag class"==t)if("\t"==c||"\f"==c||" "==c)a&&l.push(a),a="",t="start tag annotation";else if("\n"==c)a&&l.push(a),a=c,t="start tag annotation";else if("."==c)a&&l.push(a),a="";else{if(">"==c||void 0==c)return">"==c&&s++,a&&l.push(a),["start tag",n,l,""];a+=c}else if("start tag annotation"==t){if(">"==c||void 0==c)return">"==c&&s++,["start tag",n,l,a=a.split(/[\u0020\t\f\r\n]+/).filter(function(e){if(e)return!0}).join(" ")];a+=c}else if("end tag"==t){if(">"==c||void 0==c)return">"==c&&s++,["end tag",n];n+=c}else if("timestamp tag"==t){if(">"==c||void 0==c)return">"==c&&s++,["timestamp",n];n+=c}else o("Never happens.");s++}}this.parse=function(t,i){var r={children:[]},c=r,u=[];function f(e){c.children.push({type:"object",name:e[1],classes:e[2],children:[],parent:c}),c=c.children[c.children.length-1]}function p(e){for(var t=c;t;){if(t.name==e)return!0;t=t.parent}}for(;void 0!=e[s];){var m=l();if("text"==m[0])c.children.push({type:"text",value:m[1],parent:c});else if("start tag"==m[0]){"chapters"==n&&o("Start tags not allowed in chapter title text.");var g=m[1];"v"!=g&&"lang"!=g&&""!=m[3]&&o("Only and can have an annotation."),"c"==g||"i"==g||"b"==g||"u"==g||"ruby"==g||"rt"==g&&"ruby"==c.name?f(m):"v"==g?(p("v")&&o(" cannot be nested inside itself."),f(m),c.value=m[3],m[3]||o(" requires an annotation.")):"lang"==g?(f(m),c.value=m[3]):o("Incorrect start tag.")}else if("end tag"==m[0])"chapters"==n&&o("End tags not allowed in chapter title text."),m[1]==c.name?c=c.parent:"ruby"==m[1]&&"rt"==c.name?c=c.parent.parent:o("Incorrect end tag.");else if("timestamp"==m[0]){"chapters"==n&&o("Timestamp not allowed in chapter title text.");var h=new d(m[1],o).parseTimestamp();void 0!=h&&((h<=t||h>=i)&&o("Timestamp must be between start timestamp and end timestamp."),u.length>0&&u[u.length-1]>=h&&o("Timestamp must be greater than any previous timestamp."),c.children.push({type:"timestamp",value:h,parent:c}),u.push(h))}}for(;c.parent;)"v"!=c.name&&o("Required end tag missing."),c=c.parent;return function e(t){const n=a({},t);return t.children&&(n.children=t.children.map(e)),n.parent&&delete n.parent,n}(r)}},m=function(){function e(e){const t=("00"+1e3*(e-Math.floor(e)).toFixed(3)).slice(-3);let n=0,i=0,r=0;return e>=3600&&(n=Math.floor(e/3600)),i=Math.floor((e-3600*n)/60),r=Math.floor(e-3600*n-60*i),(n?n+":":"")+(""+i).padStart(2,"0")+":"+(""+r).padStart(2,"0")+"."+t}function t(n){for(var i="",r=0;r/g,">");else if("object"==s.type){if(i+="<"+s.name,s.classes)for(var o=0;o"}else"timestamp"==s.type?i+="<"+e(s.value)+">":i+="<"+s.value+">"}return i}function n(n){return(void 0!==n.id?n.id+"\n":"")+e(n.startTime)+" --\x3e "+e(n.endTime)+function(e){var t="";const n=Object.keys(u).filter(t=>e[t]!==u[t]);return n.includes("direction")&&(t+=" vertical:"+e.direction),n.includes("alignment")&&(t+=" align:"+e.alignment),n.includes("size")&&(t+=" size:"+e.size+"%"),(n.includes("lineAlign")||n.includes("linePosition"))&&(t+=" line:"+e.linePosition+(e.snapToLines?"":"%")+(e.lineAlign&&e.lineAlign!=u.lineAlign?","+e.lineAlign:"")),(n.includes("textPosition")||n.includes("positionAlign"))&&(t+=" position:"+e.textPosition+"%"+(e.positionAlign&&e.positionAlign!==u.positionAlign?","+e.positionAlign:"")),t}(n)+"\n"+t(n.tree.children)+"\n\n"}function i(e){return"STYLE\n"+e+"\n\n"}this.serialize=function(e,t){var r="WEBVTT\n\n";if(t)for(var s=0;s",">":">","&lrm":"\u200e","‎":"\u200e","&rlm":"\u200f","‏":"\u200f"," ":"\xa0"," ":"\xa0"};const h="artplayer-multiple-subtitles-timestamp";function v(e){return{type:"object",name:"c",classes:[h],children:[e]}}function b(e){return"timestamp"===e.type?v(e):"object"===e.type?l(a({},e),{children:e.children.map(b)}):l(a({},e),{value:(t=e.value,t.replace(/&/g,"&").replace(//g,">"))});var t}function y(e,t){const n=!!e.subtitle&&"activeCue"in e.subtitle&&!("activeCues"in e.subtitle),i=n?"subtitleUpdate":"subtitleAfterUpdate";function r(){if(!t.closed){n&&function(e){var t,n,i,r;const s=null==(n=null==(t=e.subtitle)?void 0:t.textTrack)?void 0:n.activeCues;if(!s||s.length<2)return;const o=Array.from(s,e=>"text"in e&&"string"===typeof e.text?e.text:""),a=e.template.$subtitle;if(null==(r=null==(i=e.option)?void 0:i.subtitle)?void 0:r.escape){a.innerHTML="";for(const e of o)for(const t of e.split(/\r?\n/)){const e=a.ownerDocument.createElement("div");e.className="art-subtitle-line",e.textContent=t,a.appendChild(e)}}else a.innerHTML=o.join("")}(e);for(const t of Array.from(e.template.$subtitle.getElementsByTagName(`c.${h}`))){const e=t.firstChild;3===(null==e?void 0:e.nodeType)&&(e.nodeValue=(e.nodeValue||"").replace(/^<(?:\d+:)?\d{2}:\d{2}\.\d{3}>/,"")),t.replaceWith(...Array.from(t.childNodes))}}}t.closed||(t.own(()=>e.off(i,r)),e.on(i,r))}function x(e,t,n){let i=null;return r=>{if(t.closed)return;const s=URL.createObjectURL(new Blob([r],{type:"text/vtt"})),o=function(e){let n=!1,i=()=>{};const r={free(){n||(n=!0,i(),URL.revokeObjectURL(e))}};return i=t.own(r.free),r}(s);if(t.closed)return;const c=i;let u,f,d=!1;i=o;try{if(u=e.option.subtitle,f=u.escape,t.closed||i!==o)return;u.escape=!1,d=!0;const r=l(a({},u),{url:s,type:"vtt",onVttLoad:n});if(t.closed||i!==o)return;const c=e.subtitle.init(r);Promise.resolve(c).catch(e=>{i===o&&(i=null,o.free()),t.closed||console.warn("Failed to initialize multiple subtitles:",e)})}catch(p){throw i===o&&(i=c,d&&!1===u.escape&&(u.escape=f)),o.free(),p}finally{c!==i&&(null==c||c.free()),o!==i&&o.free()}}}function T({subtitles:e=[]}){return t=>c(null,null,function*(){const{unescape:n,getExt:i,srtToVtt:r,assToVtt:s}=t.constructor.utils,o=function(e){let t=Boolean(e.isDestroy);const n=new Set;let i;const r=new Promise(e=>{i=e});function s(e){try{e()}catch(t){console.warn("Failed to clean up multiple subtitles:",t)}}const o={get closed(){return t},own:e=>(t?s(e):n.add(e),()=>n.delete(e)),wait:e=>Promise.race([e,r]),dispose(){if(t)return;t=!0,i();const e=[...n].reverse();n.clear();for(const t of e)s(t)}};if(!t){o.own(()=>e.off("destroy",o.dispose));try{e.on("destroy",o.dispose)}catch(a){throw o.dispose(),a}}return o}(t),u=x(t,o,n);let d=[];function p(e){o.closed||u(function(e){const t=[];for(const n of e){const e=n;for(const n of e.cues){const i=[];for(const t of n.tree.children)"timestamp"===t.type?i.push(v(t)):i.push({type:"text",value:`
`},b(t),{type:"text",value:"
"});t.push(l(a({},n),{tree:l(a({},n.tree),{children:i})}))}}return(new m).serialize(t)}(e))}const h={name:"multipleSubtitles",tracks(e=[]){o.closed||p(e.map(e=>d.find(t=>t.name===e)))},reset(){p(d)}};try{if(o.closed)return h;const n=yield Promise.all(e.map(e=>function(e,t,n){return c(this,arguments,function*(e,{getExt:t,srtToVtt:n,assToVtt:i},r){if(r.closed)return;const s="function"===typeof AbortController?new AbortController:null,o=r.own(()=>null==s?void 0:s.abort());try{const o=e.url;if(r.closed)return;const a=yield r.wait(s?fetch(o,{signal:s.signal}):fetch(o));if(r.closed)return;if(!1===a.ok)throw new Error(`Failed to fetch multiple subtitles: HTTP ${a.status}`);const l=yield r.wait(a.arrayBuffer());if(r.closed)return;const c=new TextDecoder(e.encoding||"utf-8").decode(l);switch(e.type||t(e.url)){case"srt":return n(c);case"ass":return function(e,t){const n=t(e);if(!/^WEBVTT \d+ \d+:/.test(n))return n;const i=[];for(const r of e.split(/\r?\n/)){const e=r.match(/Dialogue:\s\d,(\d+:\d{2}:\d{2}\.\d{2}),(\d+:\d{2}:\d{2}\.\d{2}),(?:[^,]*,){6}([\s\S]*)$/i);if(!e)continue;const[,t="",n="",s=""]=e;i.push({start:`${t.replace(/^(\d):/,"0$1:")}0`,end:`${n.replace(/^(\d):/,"0$1:")}0`,text:s.replace(/\{[\s\S]*?\}/g,"").replace(/\\N/g,"\n").trim().split(/\r?\n/).map(e=>e.trim()).join("\n")})}return n!==`WEBVTT ${i.map((e,t)=>`${t+1} ${e.start} --\x3e ${e.end} ${e.text}`).join("\n\n")}`?n:`WEBVTT\n\n${i.map((e,t)=>`${t+1}\n${e.start} --\x3e ${e.end}\n${e.text}`).join("\n\n")}`}(c,i);case"vtt":return c;default:return""}}catch(a){throw null==s||s.abort(),a}finally{o()}})}(e,{getExt:i,srtToVtt:r,assToVtt:s},o)));return o.closed||(d=function(e,t){const n=new f(g);return e.map((e,i)=>{const r=n.parse(e,"metadata");return l(a({},r),{url:t[i].url,name:t[i].name})})}(n,e),y(t,o),p(d)),h}catch(T){throw o.dispose(),T}})}return Object.assign(T,{default:T})}); diff --git a/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.mjs b/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.mjs index 80bcb1c49..c67f10fae 100644 --- a/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.mjs +++ b/packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.mjs @@ -1017,6 +1017,31 @@ function createRenderer(art, lifetime, unescape) { } }; } +function convertAss(text, converter) { + const converted = converter(text); + if (!/^WEBVTT \d+ \d+:/.test(converted)) + return converted; + const cues = []; + for (const line of text.split(/\r?\n/)) { + const match = line.match(/Dialogue:\s\d,(\d+:\d{2}:\d{2}\.\d{2}),(\d+:\d{2}:\d{2}\.\d{2}),(?:[^,]*,){6}([\s\S]*)$/i); + if (!match) + continue; + const [, start = "", end = "", content = ""] = match; + cues.push({ + start: `${start.replace(/^(\d):/, "0$1:")}0`, + end: `${end.replace(/^(\d):/, "0$1:")}0`, + text: content.replace(/\{[\s\S]*?\}/g, "").replace(/\\N/g, "\n").trim().split(/\r?\n/).map((line2) => line2.trim()).join("\n") + }); + } + const collapsed = `WEBVTT ${cues.map((cue, index2) => `${index2 + 1} ${cue.start} --> ${cue.end} ${cue.text}`).join("\n\n")}`; + if (converted !== collapsed) + return converted; + return `WEBVTT + +${cues.map((cue, index2) => `${index2 + 1} +${cue.start} --> ${cue.end} +${cue.text}`).join("\n\n")}`; +} async function loadVtt(option, { getExt, srtToVtt, assToVtt }, lifetime) { if (lifetime.closed) return; @@ -1039,7 +1064,7 @@ async function loadVtt(option, { getExt, srtToVtt, assToVtt }, lifetime) { case "srt": return srtToVtt(text); case "ass": - return assToVtt(text); + return convertAss(text, assToVtt); case "vtt": return text; default: diff --git a/packages/artplayer-plugin-multiple-subtitles/src/ass-conversion.ts b/packages/artplayer-plugin-multiple-subtitles/src/ass-conversion.ts new file mode 100644 index 000000000..9bbef4cba --- /dev/null +++ b/packages/artplayer-plugin-multiple-subtitles/src/ass-conversion.ts @@ -0,0 +1,26 @@ +import type { Converters } from './types' + +export default function convertAss(text: string, converter: Converters['assToVtt']): string { + const converted = converter(text) + if (!/^WEBVTT \d+ \d+:/.test(converted)) + return converted + + // Core5.1.2 main collapsed template line breaks; compare its complete output + // before repairing so custom converters and valid headers remain untouched. + const cues = [] + for (const line of text.split(/\r?\n/)) { + const match = line.match(/Dialogue:\s\d,(\d+:\d{2}:\d{2}\.\d{2}),(\d+:\d{2}:\d{2}\.\d{2}),(?:[^,]*,){6}([\s\S]*)$/i) + if (!match) + continue + const [, start = '', end = '', content = ''] = match + cues.push({ + start: `${start.replace(/^(\d):/, '0$1:')}0`, + end: `${end.replace(/^(\d):/, '0$1:')}0`, + text: content.replace(/\{[\s\S]*?\}/g, '').replace(/\\N/g, '\n').trim().split(/\r?\n/).map(line => line.trim()).join('\n'), + }) + } + const collapsed = `WEBVTT ${cues.map((cue, index) => `${index + 1} ${cue.start} --> ${cue.end} ${cue.text}`).join('\n\n')}` + if (converted !== collapsed) + return converted + return `WEBVTT\n\n${cues.map((cue, index) => `${index + 1}\n${cue.start} --> ${cue.end}\n${cue.text}`).join('\n\n')}` +} diff --git a/packages/artplayer-plugin-multiple-subtitles/src/request.ts b/packages/artplayer-plugin-multiple-subtitles/src/request.ts index 4373dff25..74bd8d683 100644 --- a/packages/artplayer-plugin-multiple-subtitles/src/request.ts +++ b/packages/artplayer-plugin-multiple-subtitles/src/request.ts @@ -1,4 +1,5 @@ import type { Converters, Lifetime, TrackOption } from './types' +import convertAss from './ass-conversion' export default async function loadVtt(option: TrackOption, { getExt, srtToVtt, assToVtt }: Converters, lifetime: Lifetime): Promise { if (lifetime.closed) @@ -21,7 +22,7 @@ export default async function loadVtt(option: TrackOption, { getExt, srtToVtt, a const text = new TextDecoder(option.encoding || 'utf-8').decode(buffer!) switch (option.type || getExt(option.url!)) { case 'srt': return srtToVtt(text) - case 'ass': return assToVtt(text) + case 'ass': return convertAss(text, assToVtt) case 'vtt': return text default: return '' } diff --git a/refactor/baselines/multiple-subtitles-ass-validation.json b/refactor/baselines/multiple-subtitles-ass-validation.json new file mode 100644 index 000000000..0c8ba2f75 --- /dev/null +++ b/refactor/baselines/multiple-subtitles-ass-validation.json @@ -0,0 +1,2875 @@ +{ + "task": "PKG-MULTI-SUB-11", + "baselineCommit": "8ae2762a23188e3262a16fcb0e468fa7588f00b0", + "recordedAt": "2026-09-14T05:40:03.497Z", + "node": "v24.21.0", + "yarn": "1.22.22", + "frozenHost": { + "archiveSha256": "cb06570fb55f28577e702b15e011f79293a1378b429b0acb542caeda49ab6c00", + "files": { + "package/dist/artplayer.js": "3dfd035b4cde34c9d9dd1b303f95bfbb42bde105bcb3e5671a8d375a719290b1", + "package/dist/artplayer.legacy.js": "eedce04975dcf9a78a166ca3038d2b2bd06ce39696ce90f32ed39c8356132c4d", + "package/src/utils/subtitle.js": "2f932f5812f7cf9e5684d10da81c40f757de7fdfe0175da3b24da84c08befe1b" + }, + "observation": "Only main collapses required line breaks. Actual legacy and source agree; parity and input verification are exercised by the Node test." + }, + "sources": [ + { + "path": "packages/artplayer-plugin-multiple-subtitles/src/request.ts", + "sha256": "31d8af86664b7f58c31d478e36fb5598fa1afa3970a816d1a8f4d40a7a03b2d7" + }, + { + "path": "packages/artplayer-plugin-multiple-subtitles/src/ass-conversion.ts", + "sha256": "a8468971f6776925ad9d8897d07246a4c1a98ed30506c9558e5799d2ee8d0be4" + } + ], + "artifacts": [ + { + "path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.js", + "sha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "docsCopy": { + "path": "docs/compiled/artplayer-plugin-multiple-subtitles.js", + "sha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b" + }, + "previousSha256": "df7db958a7b6e2b5afdcc655f72422f7698ab15118c884f6675bff39f8ec0591", + "gzip9Bytes": 9114, + "previousGzip9Bytes": 8876 + }, + { + "path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.legacy.js", + "sha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "docsCopy": { + "path": "docs/compiled/artplayer-plugin-multiple-subtitles.legacy.js", + "sha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3" + }, + "previousSha256": "506cc844c10b8836690acf94725ca405400b6efbc20505b79ad44d21d125a5f5", + "gzip9Bytes": 9445, + "previousGzip9Bytes": 9204 + }, + { + "path": "packages/artplayer-plugin-multiple-subtitles/dist/artplayer-plugin-multiple-subtitles.mjs", + "sha256": "4655b1aa785ece0bbdc4cdae970c775356a5c89f40444a0a0c9942ada8dd38fb", + "docsCopy": { + "path": "docs/compiled/artplayer-plugin-multiple-subtitles.mjs", + "sha256": "4655b1aa785ece0bbdc4cdae970c775356a5c89f40444a0a0c9942ada8dd38fb" + }, + "previousSha256": "0b8866b82b81e58cfcd3535ae27f12da7ecf02e1bc55dbbbe0727ab09066ec5f", + "gzip9Bytes": 10964, + "previousGzip9Bytes": 10635 + } + ], + "tests": [ + { + "path": "test/multiple-subtitles-ass.test.js", + "sha256": "7cf2f82e1bffab2247f7456c0dfbf21568cec825f5632fc2c28b9259abc15038" + }, + { + "path": "test/browser/multiple-subtitles-ass.spec.js", + "sha256": "d96d950c7b741517a06b3dc23a19662046bddd519b351a6f11b047434d05d84b" + }, + { + "path": "test/browser/multiple-subtitles-legacy.spec.js", + "sha256": "c98c46135cff20cc56a7ba7ce990bb83f0e5cdc6bb324d8bc84f61fa44589c29" + } + ], + "runs": [ + { + "name": "before-main", + "report": { + "path": "refactor/.cache/multi-ass-before-browser/report.json", + "sha256": "933fc92e3e96626f979fa424724eb2da4158870080e33491be83fc2fdb9ff725" + }, + "log": { + "path": "refactor/.cache/multi-ass-before-browser.log", + "sha256": "46b51da193f1547758174272df322a9fe935c34dcbb935df0773e9485558b9b4" + }, + "stats": { + "startTime": "2026-09-14T05:31:17.497Z", + "duration": 10477.76, + "expected": 0, + "skipped": 0, + "unexpected": 1, + "flaky": 0 + }, + "inputs": [ + { + "project": "chromium", + "browser": "153.0.8010.12", + "platform": "win32", + "cores": { + "/candidate/artplayer.js": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "2b99c7fe675a0aa73b32ec955455db86e33d560c31882b146a30e6510b5690cc", + "bytes": 378769 + }, + "/published/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "/published-5.1.7/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.7", + "integrity": "sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "484ccc3f22446ec6fd0dd8b8a5602c740b601d55097a1ae043262d92aab35357", + "bytes": 155940 + }, + "/published-5.3.0/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.3.0", + "integrity": "sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "54ffaf7598b910d5a88d2d2eec1aa6faa806583915b99b892dd32129c53e421a", + "bytes": 159238 + }, + "/published-5.1.2/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.2", + "integrity": "sha512-eIWlxiP2ghgftaZWIGnclkOpXGFZTOf5xrm6sGZCGx5A8wGbTHRSMe+ZiyPFpbNhBVupi/QObSjX/FcD+YbS1Q==", + "member": "package/dist/artplayer.js" + }, + "sha256": "3dfd035b4cde34c9d9dd1b303f95bfbb42bde105bcb3e5671a8d375a719290b1", + "bytes": 154583 + } + }, + "media": { + "source": { + "kind": "generated-media", + "file": "test/browser/media/pattern.mp4" + }, + "sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "bytes": 251538 + } + } + ], + "tests": [ + { + "title": "Multiple subtitles legacy host published-5.1.2: independently timed captions overlap and clear", + "project": "chromium", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 24 | await window.art.plugins.add(window.artplayerPluginMultipleSubtitles({ subtitles: [{ name: 'a', url: '/test/legacy-a.vtt' }, { name: 'b', url: '/test/legacy-b.srt' }, { name: 'c', url: `/test/legacy-c.${thirdFormat}` }] }))\n 25 | }, thirdFormat)\n> 26 | await expect.poll(() => page.evaluate(() => window.art.template.$track.track.cues?.length)).toBe(3)\n | ^\n 27 | await page.evaluate(() => {\n 28 | window.cueIdentities = Array.from(window.art.template.$track.track.cues)\n 29 | })\n at D:\\github\\ArtPlayer\\test\\browser\\multiple-subtitles-legacy.spec.js:26:97" + ] + } + ] + }, + { + "name": "source", + "report": { + "path": "refactor/.cache/multi-ass-source-browser/report.json", + "sha256": "9e8721ee636a6f720d897e4883579dad724d5c80e12f5ff5c0d1b7f62f8888f4" + }, + "log": { + "path": "refactor/.cache/multi-ass-source-browser.log", + "sha256": "ff6d98036b543e5ed048ea2d5d09335db57610c59819cc4c4375e700f5b1973f" + }, + "stats": { + "startTime": "2026-09-14T05:33:45.745Z", + "duration": 29092.512000000002, + "expected": 21, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "inputs": [ + { + "project": "chromium", + "browser": "153.0.8010.12", + "platform": "win32", + "cores": { + "/candidate/artplayer.js": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "2b99c7fe675a0aa73b32ec955455db86e33d560c31882b146a30e6510b5690cc", + "bytes": 378769 + }, + "/published/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "/published-5.1.7/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.7", + "integrity": "sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "484ccc3f22446ec6fd0dd8b8a5602c740b601d55097a1ae043262d92aab35357", + "bytes": 155940 + }, + "/published-5.3.0/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.3.0", + "integrity": "sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "54ffaf7598b910d5a88d2d2eec1aa6faa806583915b99b892dd32129c53e421a", + "bytes": 159238 + }, + "/published-5.1.2/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.2", + "integrity": "sha512-eIWlxiP2ghgftaZWIGnclkOpXGFZTOf5xrm6sGZCGx5A8wGbTHRSMe+ZiyPFpbNhBVupi/QObSjX/FcD+YbS1Q==", + "member": "package/dist/artplayer.js" + }, + "sha256": "3dfd035b4cde34c9d9dd1b303f95bfbb42bde105bcb3e5671a8d375a719290b1", + "bytes": 154583 + } + }, + "media": { + "source": { + "kind": "generated-media", + "file": "test/browser/media/pattern.mp4" + }, + "sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "bytes": 251538 + } + }, + { + "project": "firefox", + "browser": "155.0", + "platform": "win32", + "cores": { + "/candidate/artplayer.js": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "2b99c7fe675a0aa73b32ec955455db86e33d560c31882b146a30e6510b5690cc", + "bytes": 378769 + }, + "/published/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "/published-5.1.7/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.7", + "integrity": "sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "484ccc3f22446ec6fd0dd8b8a5602c740b601d55097a1ae043262d92aab35357", + "bytes": 155940 + }, + "/published-5.3.0/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.3.0", + "integrity": "sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "54ffaf7598b910d5a88d2d2eec1aa6faa806583915b99b892dd32129c53e421a", + "bytes": 159238 + }, + "/published-5.1.2/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.2", + "integrity": "sha512-eIWlxiP2ghgftaZWIGnclkOpXGFZTOf5xrm6sGZCGx5A8wGbTHRSMe+ZiyPFpbNhBVupi/QObSjX/FcD+YbS1Q==", + "member": "package/dist/artplayer.js" + }, + "sha256": "3dfd035b4cde34c9d9dd1b303f95bfbb42bde105bcb3e5671a8d375a719290b1", + "bytes": 154583 + } + }, + "media": { + "source": { + "kind": "generated-media", + "file": "test/browser/media/pattern.mp4" + }, + "sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "bytes": 251538 + } + }, + { + "project": "webkit", + "browser": "26.6", + "platform": "win32", + "cores": { + "/candidate/artplayer.js": { + "source": { + "kind": "workspace-build", + "entry": "packages\\artplayer\\src\\index.ts" + }, + "sha256": "2b99c7fe675a0aa73b32ec955455db86e33d560c31882b146a30e6510b5690cc", + "bytes": 378769 + }, + "/published/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "/published-5.1.7/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.7", + "integrity": "sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "484ccc3f22446ec6fd0dd8b8a5602c740b601d55097a1ae043262d92aab35357", + "bytes": 155940 + }, + "/published-5.3.0/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.3.0", + "integrity": "sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "54ffaf7598b910d5a88d2d2eec1aa6faa806583915b99b892dd32129c53e421a", + "bytes": 159238 + }, + "/published-5.1.2/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.2", + "integrity": "sha512-eIWlxiP2ghgftaZWIGnclkOpXGFZTOf5xrm6sGZCGx5A8wGbTHRSMe+ZiyPFpbNhBVupi/QObSjX/FcD+YbS1Q==", + "member": "package/dist/artplayer.js" + }, + "sha256": "3dfd035b4cde34c9d9dd1b303f95bfbb42bde105bcb3e5671a8d375a719290b1", + "bytes": 154583 + } + }, + "media": { + "source": { + "kind": "generated-media", + "file": "test/browser/media/pattern.mp4" + }, + "sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "bytes": 251538 + } + } + ], + "tests": [ + { + "title": "Multiple subtitles ASS / published-5.1.2: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.2", + "browser": "153.0.8010.12", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.7: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.7", + "browser": "153.0.8010.12", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.3.0: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published-5.3.0", + "browser": "153.0.8010.12", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published", + "browser": "153.0.8010.12", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / candidate: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "candidate", + "browser": "153.0.8010.12", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.2: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.2", + "browser": "155.0", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.7: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.7", + "browser": "155.0", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.3.0: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published-5.3.0", + "browser": "155.0", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published", + "browser": "155.0", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / candidate: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "candidate", + "browser": "155.0", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.2: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.2", + "browser": "26.6", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.7: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.7", + "browser": "26.6", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.3.0: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published-5.3.0", + "browser": "26.6", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published", + "browser": "26.6", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / candidate: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "candidate", + "browser": "26.6", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.2: independently timed captions overlap and clear", + "project": "chromium", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.2", + "browser": "153.0.8010.12", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.7: independently timed captions overlap and clear", + "project": "chromium", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.7", + "browser": "153.0.8010.12", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.2: independently timed captions overlap and clear", + "project": "firefox", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.2", + "browser": "155.0", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.7: independently timed captions overlap and clear", + "project": "firefox", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.7", + "browser": "155.0", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.2: independently timed captions overlap and clear", + "project": "webkit", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.2", + "browser": "26.6", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.7: independently timed captions overlap and clear", + "project": "webkit", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.7", + "browser": "26.6", + "pluginSha256": "6ea028e0ab36417f1ee8f6d114be89d0ff0eb2bde239347f3e951ead640d20b4", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + } + ] + }, + { + "name": "main", + "report": { + "path": "refactor/.cache/multi-ass-main-browser/report.json", + "sha256": "13905f335e30e89fb070c8a09fbe5d567c25dd95ac591fe2ba1c619c51d51bc5" + }, + "log": { + "path": "refactor/.cache/multi-ass-main-browser.log", + "sha256": "6cb6aaaf328afd9db6c07b44d8d8793bb88fdbd07b00a2749c18bb839f774206" + }, + "stats": { + "startTime": "2026-09-14T05:35:49.765Z", + "duration": 23154.222, + "expected": 21, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "inputs": [ + { + "project": "chromium", + "browser": "153.0.8010.12", + "platform": "win32", + "cores": { + "/candidate/artplayer.js": { + "source": { + "kind": "artifact", + "file": "D:\\github\\ArtPlayer\\packages\\artplayer\\dist\\artplayer.js" + }, + "sha256": "cab41b4a6b47dfacfc2d99b923273043b48f0ad93c3e808af0bfa84ffe1cf26e", + "bytes": 208931 + }, + "/published/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "/published-5.1.7/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.7", + "integrity": "sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "484ccc3f22446ec6fd0dd8b8a5602c740b601d55097a1ae043262d92aab35357", + "bytes": 155940 + }, + "/published-5.3.0/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.3.0", + "integrity": "sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "54ffaf7598b910d5a88d2d2eec1aa6faa806583915b99b892dd32129c53e421a", + "bytes": 159238 + }, + "/published-5.1.2/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.2", + "integrity": "sha512-eIWlxiP2ghgftaZWIGnclkOpXGFZTOf5xrm6sGZCGx5A8wGbTHRSMe+ZiyPFpbNhBVupi/QObSjX/FcD+YbS1Q==", + "member": "package/dist/artplayer.js" + }, + "sha256": "3dfd035b4cde34c9d9dd1b303f95bfbb42bde105bcb3e5671a8d375a719290b1", + "bytes": 154583 + } + }, + "media": { + "source": { + "kind": "generated-media", + "file": "test/browser/media/pattern.mp4" + }, + "sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "bytes": 251538 + } + }, + { + "project": "firefox", + "browser": "155.0", + "platform": "win32", + "cores": { + "/candidate/artplayer.js": { + "source": { + "kind": "artifact", + "file": "D:\\github\\ArtPlayer\\packages\\artplayer\\dist\\artplayer.js" + }, + "sha256": "cab41b4a6b47dfacfc2d99b923273043b48f0ad93c3e808af0bfa84ffe1cf26e", + "bytes": 208931 + }, + "/published/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "/published-5.1.7/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.7", + "integrity": "sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "484ccc3f22446ec6fd0dd8b8a5602c740b601d55097a1ae043262d92aab35357", + "bytes": 155940 + }, + "/published-5.3.0/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.3.0", + "integrity": "sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "54ffaf7598b910d5a88d2d2eec1aa6faa806583915b99b892dd32129c53e421a", + "bytes": 159238 + }, + "/published-5.1.2/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.2", + "integrity": "sha512-eIWlxiP2ghgftaZWIGnclkOpXGFZTOf5xrm6sGZCGx5A8wGbTHRSMe+ZiyPFpbNhBVupi/QObSjX/FcD+YbS1Q==", + "member": "package/dist/artplayer.js" + }, + "sha256": "3dfd035b4cde34c9d9dd1b303f95bfbb42bde105bcb3e5671a8d375a719290b1", + "bytes": 154583 + } + }, + "media": { + "source": { + "kind": "generated-media", + "file": "test/browser/media/pattern.mp4" + }, + "sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "bytes": 251538 + } + }, + { + "project": "webkit", + "browser": "26.6", + "platform": "win32", + "cores": { + "/candidate/artplayer.js": { + "source": { + "kind": "artifact", + "file": "D:\\github\\ArtPlayer\\packages\\artplayer\\dist\\artplayer.js" + }, + "sha256": "cab41b4a6b47dfacfc2d99b923273043b48f0ad93c3e808af0bfa84ffe1cf26e", + "bytes": 208931 + }, + "/published/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "/published-5.1.7/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.7", + "integrity": "sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "484ccc3f22446ec6fd0dd8b8a5602c740b601d55097a1ae043262d92aab35357", + "bytes": 155940 + }, + "/published-5.3.0/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.3.0", + "integrity": "sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "54ffaf7598b910d5a88d2d2eec1aa6faa806583915b99b892dd32129c53e421a", + "bytes": 159238 + }, + "/published-5.1.2/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.2", + "integrity": "sha512-eIWlxiP2ghgftaZWIGnclkOpXGFZTOf5xrm6sGZCGx5A8wGbTHRSMe+ZiyPFpbNhBVupi/QObSjX/FcD+YbS1Q==", + "member": "package/dist/artplayer.js" + }, + "sha256": "3dfd035b4cde34c9d9dd1b303f95bfbb42bde105bcb3e5671a8d375a719290b1", + "bytes": 154583 + } + }, + "media": { + "source": { + "kind": "generated-media", + "file": "test/browser/media/pattern.mp4" + }, + "sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "bytes": 251538 + } + } + ], + "tests": [ + { + "title": "Multiple subtitles ASS / published-5.1.2: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.2", + "browser": "153.0.8010.12", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.7: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.7", + "browser": "153.0.8010.12", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.3.0: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published-5.3.0", + "browser": "153.0.8010.12", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published", + "browser": "153.0.8010.12", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / candidate: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "candidate", + "browser": "153.0.8010.12", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.2: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.2", + "browser": "155.0", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.7: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.7", + "browser": "155.0", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.3.0: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published-5.3.0", + "browser": "155.0", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published", + "browser": "155.0", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / candidate: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "candidate", + "browser": "155.0", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.2: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.2", + "browser": "26.6", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.7: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.7", + "browser": "26.6", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.3.0: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published-5.3.0", + "browser": "26.6", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published", + "browser": "26.6", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / candidate: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "candidate", + "browser": "26.6", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.2: independently timed captions overlap and clear", + "project": "chromium", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.2", + "browser": "153.0.8010.12", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.7: independently timed captions overlap and clear", + "project": "chromium", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.7", + "browser": "153.0.8010.12", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.2: independently timed captions overlap and clear", + "project": "firefox", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.2", + "browser": "155.0", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.7: independently timed captions overlap and clear", + "project": "firefox", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.7", + "browser": "155.0", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.2: independently timed captions overlap and clear", + "project": "webkit", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.2", + "browser": "26.6", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.7: independently timed captions overlap and clear", + "project": "webkit", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.7", + "browser": "26.6", + "pluginSha256": "103e5f75e6ce4b8b9f14b2b8caa983c97eb24da4536048135f90517b959dcf6b", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + } + ] + }, + { + "name": "legacy", + "report": { + "path": "refactor/.cache/multi-ass-legacy-browser/report.json", + "sha256": "cb745560b8223a1b1a408985598a0dd8c5357477fa5004dcff675b7497433b93" + }, + "log": { + "path": "refactor/.cache/multi-ass-legacy-browser.log", + "sha256": "66cdbd5116e8bb30e43bc35ef18118ccba1e64774aee5c1139ec00d52cc52d56" + }, + "stats": { + "startTime": "2026-09-14T05:37:27.954Z", + "duration": 22820.602000000003, + "expected": 21, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "inputs": [ + { + "project": "chromium", + "browser": "153.0.8010.12", + "platform": "win32", + "cores": { + "/candidate/artplayer.js": { + "source": { + "kind": "artifact", + "file": "D:\\github\\ArtPlayer\\packages\\artplayer\\dist\\artplayer.legacy.js" + }, + "sha256": "86c38e41d611cc48195db72f41ade863de3b13f4fba66e3fd2a335dfc3094c87", + "bytes": 213200 + }, + "/published/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "/published-5.1.7/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.7", + "integrity": "sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "484ccc3f22446ec6fd0dd8b8a5602c740b601d55097a1ae043262d92aab35357", + "bytes": 155940 + }, + "/published-5.3.0/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.3.0", + "integrity": "sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "54ffaf7598b910d5a88d2d2eec1aa6faa806583915b99b892dd32129c53e421a", + "bytes": 159238 + }, + "/published-5.1.2/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.2", + "integrity": "sha512-eIWlxiP2ghgftaZWIGnclkOpXGFZTOf5xrm6sGZCGx5A8wGbTHRSMe+ZiyPFpbNhBVupi/QObSjX/FcD+YbS1Q==", + "member": "package/dist/artplayer.js" + }, + "sha256": "3dfd035b4cde34c9d9dd1b303f95bfbb42bde105bcb3e5671a8d375a719290b1", + "bytes": 154583 + } + }, + "media": { + "source": { + "kind": "generated-media", + "file": "test/browser/media/pattern.mp4" + }, + "sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "bytes": 251538 + } + }, + { + "project": "firefox", + "browser": "155.0", + "platform": "win32", + "cores": { + "/candidate/artplayer.js": { + "source": { + "kind": "artifact", + "file": "D:\\github\\ArtPlayer\\packages\\artplayer\\dist\\artplayer.legacy.js" + }, + "sha256": "86c38e41d611cc48195db72f41ade863de3b13f4fba66e3fd2a335dfc3094c87", + "bytes": 213200 + }, + "/published/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "/published-5.1.7/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.7", + "integrity": "sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "484ccc3f22446ec6fd0dd8b8a5602c740b601d55097a1ae043262d92aab35357", + "bytes": 155940 + }, + "/published-5.3.0/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.3.0", + "integrity": "sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "54ffaf7598b910d5a88d2d2eec1aa6faa806583915b99b892dd32129c53e421a", + "bytes": 159238 + }, + "/published-5.1.2/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.2", + "integrity": "sha512-eIWlxiP2ghgftaZWIGnclkOpXGFZTOf5xrm6sGZCGx5A8wGbTHRSMe+ZiyPFpbNhBVupi/QObSjX/FcD+YbS1Q==", + "member": "package/dist/artplayer.js" + }, + "sha256": "3dfd035b4cde34c9d9dd1b303f95bfbb42bde105bcb3e5671a8d375a719290b1", + "bytes": 154583 + } + }, + "media": { + "source": { + "kind": "generated-media", + "file": "test/browser/media/pattern.mp4" + }, + "sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "bytes": 251538 + } + }, + { + "project": "webkit", + "browser": "26.6", + "platform": "win32", + "cores": { + "/candidate/artplayer.js": { + "source": { + "kind": "artifact", + "file": "D:\\github\\ArtPlayer\\packages\\artplayer\\dist\\artplayer.legacy.js" + }, + "sha256": "86c38e41d611cc48195db72f41ade863de3b13f4fba66e3fd2a335dfc3094c87", + "bytes": 213200 + }, + "/published/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.4.0", + "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==", + "member": "package/dist/artplayer.js" + }, + "sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f", + "bytes": 134037 + }, + "/published-5.1.7/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.7", + "integrity": "sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "484ccc3f22446ec6fd0dd8b8a5602c740b601d55097a1ae043262d92aab35357", + "bytes": 155940 + }, + "/published-5.3.0/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.3.0", + "integrity": "sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==", + "member": "package/dist/artplayer.js" + }, + "sha256": "54ffaf7598b910d5a88d2d2eec1aa6faa806583915b99b892dd32129c53e421a", + "bytes": 159238 + }, + "/published-5.1.2/artplayer.js": { + "source": { + "kind": "npm-release", + "version": "5.1.2", + "integrity": "sha512-eIWlxiP2ghgftaZWIGnclkOpXGFZTOf5xrm6sGZCGx5A8wGbTHRSMe+ZiyPFpbNhBVupi/QObSjX/FcD+YbS1Q==", + "member": "package/dist/artplayer.js" + }, + "sha256": "3dfd035b4cde34c9d9dd1b303f95bfbb42bde105bcb3e5671a8d375a719290b1", + "bytes": 154583 + } + }, + "media": { + "source": { + "kind": "generated-media", + "file": "test/browser/media/pattern.mp4" + }, + "sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "bytes": 251538 + } + } + ], + "tests": [ + { + "title": "Multiple subtitles ASS / published-5.1.2: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.2", + "browser": "153.0.8010.12", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.7: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.7", + "browser": "153.0.8010.12", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.3.0: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published-5.3.0", + "browser": "153.0.8010.12", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "published", + "browser": "153.0.8010.12", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / candidate: encoded multiline cues, overlapping translation and selection", + "project": "chromium", + "status": "passed", + "ass-conversion": { + "core": "candidate", + "browser": "153.0.8010.12", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.2: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.2", + "browser": "155.0", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.7: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.7", + "browser": "155.0", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.3.0: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published-5.3.0", + "browser": "155.0", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "published", + "browser": "155.0", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / candidate: encoded multiline cues, overlapping translation and selection", + "project": "firefox", + "status": "passed", + "ass-conversion": { + "core": "candidate", + "browser": "155.0", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.2: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.2", + "browser": "26.6", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.1.7: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published-5.1.7", + "browser": "26.6", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published-5.3.0: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published-5.3.0", + "browser": "26.6", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT 1 00:00:01.250 --> 00:00:03.750 漢字 café, comma bold\nSecond line\n\n2 00:00:04.250 --> 00:00:06.750 Later cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / published: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "published", + "browser": "26.6", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles ASS / candidate: encoded multiline cues, overlapping translation and selection", + "project": "webkit", + "status": "passed", + "ass-conversion": { + "core": "candidate", + "browser": "26.6", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "sourceSha256": "91aeb65afe1a8193b21dfe6f37e6b16f1c3934af3f04677484a140b1c8d4a681", + "rawHostOutput": "WEBVTT\n\n1\n00:00:01.250 --> 00:00:03.750\n漢字 café, comma bold\nSecond line\n\n2\n00:00:04.250 --> 00:00:06.750\nLater cue", + "native": [ + { + "start": 1.25, + "end": 3.75, + "text": "漢字 café, comma bold\nSecond line" + }, + { + "start": 2, + "end": 4, + "text": "Translation" + }, + { + "start": 4.25, + "end": 6.75, + "text": "Later cue" + } + ], + "scope": "Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged." + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.2: independently timed captions overlap and clear", + "project": "chromium", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.2", + "browser": "153.0.8010.12", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.7: independently timed captions overlap and clear", + "project": "chromium", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.7", + "browser": "153.0.8010.12", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.2: independently timed captions overlap and clear", + "project": "firefox", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.2", + "browser": "155.0", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.7: independently timed captions overlap and clear", + "project": "firefox", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.7", + "browser": "155.0", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.2: independently timed captions overlap and clear", + "project": "webkit", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.2", + "browser": "26.6", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + }, + { + "title": "Multiple subtitles legacy host published-5.1.7: independently timed captions overlap and clear", + "project": "webkit", + "status": "passed", + "legacy-captions": { + "core": "published-5.1.7", + "browser": "26.6", + "pluginSha256": "01f27a9b7b5752334e9abe85324322d50b48508f6180b41f09d84a74ef3bddf3", + "thirdFormat": "ass", + "state": { + "sameUpdate": true, + "sameCues": true, + "times": [ + [ + 1, + 3 + ], + [ + 2, + 4 + ], + [ + 2.25, + 2.75 + ] + ], + "legacyPayload": "
A & safe
", + "firstText": "
A & safe
", + "counts": [ + 1, + 1, + 1 + ] + }, + "scope": "Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals" + } + } + ] + } + ], + "checks": [ + { + "path": "refactor/.cache/multi-ass-before-initial-unit.log", + "sha256": "2d16f3cc21cc203fefa579ef97312aa94d97c87689b628e5784e107276accf9c" + }, + { + "path": "refactor/.cache/multi-ass-before-unit.log", + "sha256": "805a81dc9aa1cb20b2c0c75037e2cec17518fd042de565c09ba1f5744bff8f0f" + }, + { + "path": "refactor/.cache/multi-ass-tests.log", + "sha256": "ad5b64cb5a1c2ccd34291f74e39d7213f983e1d74ddeb9fb673f31dfa65e67ef" + }, + { + "path": "refactor/.cache/multi-ass-main-unit.log", + "sha256": "2606a93b4877b87dc342a93741ac517c054838ea3ef890e9556c244e22a72cc3" + }, + { + "path": "refactor/.cache/multi-ass-legacy-unit.log", + "sha256": "65db583973c33e906702f926ea215c52679691c22de5d7248e713bace0ad5ba9" + }, + { + "path": "refactor/.cache/multi-ass-lint.log", + "sha256": "ec8ab914ad0251dbe20d6c900d8a6fc96411f11583ddff74ac966396a9965ac5" + }, + { + "path": "refactor/.cache/multi-ass-build.log", + "sha256": "90f3f0279a611304ab75bae8867fecf06efd36f7d155ad7e0eb99bae44995efb" + }, + { + "path": "refactor/.cache/multi-ass-ci.log", + "sha256": "3aad0331b93bede3474c1d3bc9c7e2746ae76f4fe5cc3f012e22f025bee696d8" + }, + { + "path": "refactor/.cache/multi-ass-final-checks.log", + "sha256": "01f40e1e66415642bc933a72935934c6da9de0422ef259665a5d8601fd853e1e", + "exitCode": 0 + }, + { + "path": "refactor/.cache/multi-ass-risk-closure-incomplete.log", + "sha256": "9a1e1593163b7ff29f92af566bebfd239b59ae7ce97b40cd46c39c49554a25c3", + "exitCode": 1 + } + ], + "limits": [ + "The initial raw HTML entity mismatch was a fixture issue; corrected frozen regression has6 main failures and7 legacy/baseline passes before the fix.", + "Full plugin315 pass; each targeted main/legacy file has15 checks, including frozen-host and private-adapter tests as well as actual plugin registration/selection.", + "Source/main/legacy each21 browser cases pass on three desktop engines. This does not establish physical-device or remote-CI acceptance.", + "Native VTT conversion of previously supported ASS Dialogue content; no new ASS layout or animation support.", + "Old WebKit source-seek issue remains open; package05/06 and release gates remain incomplete." + ], + "registryNote": "Initial closure validation rejected missing explicit resolutionEvidence/resolutionRationale. Both were supplied with actual test evidence; validator rules stayed unchanged and final checks passed." +} diff --git a/refactor/changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md b/refactor/changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md new file mode 100644 index 000000000..67fe027b1 --- /dev/null +++ b/refactor/changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md @@ -0,0 +1,74 @@ +# PKG-MULTI-SUB-11: preserve ASS conversion on old core main + +## Reproduction + +Frozen npm core5.1.2 has different converter behavior across its own files. +For a plain Dialogue cue, main returns +`WEBVTT 1 00:00:01.250 --> 00:00:03.750 Plain`; the source and legacy file retain +the required header, identifier, timing and payload line breaks. The plugin's +VTT parser consequently sees no ASS cue from main. These are actual verified +tarball members, not simulated converters or version-number assumptions. + +The new unit regression initially passes seven baseline/legacy assertions and +fails six main-converter samples. The initial raw-string text assertion also +compared an encoded ampersand with an equivalent literal ampersand; that fixture +failure is retained separately. Canonical HTML comparison fixes the test's text +representation comparison without weakening cue timing/count or semantic markup. +The actual browser regression with the previous plugin artifact fails because +only two of three VTT/SRT/ASS cues load on5.1.2. + +## Implementation and compatibility + +`src/ass-conversion.ts` is a private adapter called after request decoding. It +calls the existing host converter once. Valid output takes the normal path. +For output with the old collapsed header/timing shape, the adapter reconstructs +the old converter's supported Dialogue fields, timestamp formatting and text +cleanup, then compares the entire collapsed output. Only an exact match receives +the missing structural newlines. Custom output, comments, empty results and +nonmatching malformed conversions retain their original value; exceptions still +reject registration and release owned resources. + +This small duplicate of the old supported Dialogue projection is intentional: +importing runtime code from the new core would make the plugin depend on that +core version. It is not a second general ASS parser. Keep its complete-output +comparison and frozen source/legacy parity tests when editing it. Do not extend +accepted ASS syntax or modify cue timing under this repair task. + +The host's shared utility is never patched, called with a probe or replaced. +The factory, methods, event order, public types, declarations, global/ESM/CJS +entries, vendor bytes and package versions stay unchanged. No dependency or +lockfile changes are needed. The new test joins existing Yarn unit/package +commands; all three package formats and docs copies are normally rebuilt. + +## Validation and limits + +Exact inputs, hashes, archived reports and commands are in +[the validation index](../baselines/multiple-subtitles-ass-validation.json). + +- Plugin suite315/315: the new15 tests include actual old main/legacy converters, + source parity, independent timing, Unicode, inline markup, ASS line breaks, + overlapping cues, long hours, comma text, custom/empty output and failure cleanup. +- The same targeted15-test file passes with actual main and legacy plugin input. + Its frozen-host and private-adapter checks are identified separately from + full-plugin registration/selection cases in the source. +- Source browser21/21 and actual-main browser21/21. The matrix spans + core5.1.2/5.1.7/5.3.0/5.4.0/candidate in Chromium, Firefox and Windows WebKit. + It checks UTF16 input, explicit type override, native cue times/text, multiline + and semantic HTML, overlapping translation, selection/reset and clearing. + The old-host regression now uses an actual third ASS track on both5.1 cores. +- Actual legacy browser21/21. Strict TypeScript,37 declarations with zero drift, + root and changed-script lint, CI50 and plan/risk validation pass; root lint keeps + the existing generated-doc warning. Detailed evidence is retained in the index. + +Close only MULTI-SUB-ASS-01 after these checks. This is conversion into VTT, not +full ASS positioning/animation/rendering, physical-device evidence, remote CI or +package publication. The old WebKit source-seek failure reopened in task10 stays +open and is not rerun or waived by this targeted conversion matrix. Tasks05/06 +and the wider refactor remain incomplete. + +## Maintenance and rollback + +The package architecture and test READMEs describe the adapter and replay commands. +Revert this task and rebuild the plugin to restore prior behavior; preserve the +frozen converter evidence so the known old-main failure cannot be mistaken for +successful ASS compatibility. No push or publication is included. diff --git a/refactor/plan.md b/refactor/plan.md index 2ccd958f8..602674741 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 251 项,范围 22 个包及工作区/示例。 -状态:todo 56 / doing 17 / blocked 0 / done 178 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 55 / doing 17 / blocked 0 / done 179 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -232,7 +232,7 @@ | PKG-MULTI-SUB-06 | artplayer-plugin-multiple-subtitles
验证分发并同步文档 | PKG-MULTI-SUB-05, ENG-07, CORE-SUBTITLE-OFFSET-01, PKG-MULTI-SUB-09, PKG-MULTI-SUB-10, PKG-MULTI-SUB-11 | multiple.subtitles.js、parser 来源许可及打包边界 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo | | PKG-MULTI-SUB-09 | artplayer-plugin-multiple-subtitles, artplayer
定位旧核心切源寻址与字幕显示边界 | PKG-MULTI-SUB-08, CORE-19 | 无插件及旧新插件对照、真实事件轨迹、原切源失败归因和组合测试顺序 | 保留旧核心立即寻址观察,候选首次寻址与正常字幕组合通过;不把原生时钟失败误归为字幕丢失 | M | done | | PKG-MULTI-SUB-10 | artplayer-plugin-multiple-subtitles
适配旧核心的多条活动字幕渲染 | PKG-MULTI-SUB-08, PKG-MULTI-SUB-09 | 独立旧宿主显示适配、原生异步字幕重叠回归和兼容边界说明 | 旧5.1.2/5.1.7复现先红后绿;保持原生cue对象/时间、旧事件标量和方法身份;多格式显示及清理通过,保留其他组合和设备缺口 | M | done | -| PKG-MULTI-SUB-11 | artplayer-plugin-multiple-subtitles
处理旧发布核心 ASS 转换器丢失换行 | PKG-MULTI-SUB-10 | 冻结5.1.2发布/源码转换差异,兼容转换适配与真实ASS字幕回归 | 保持已有效转换行为,旧发布ASS缺陷有先红后绿与实际产物证据;不得将VTT替代夹具算作ASS通过 | M | todo | +| PKG-MULTI-SUB-11 | artplayer-plugin-multiple-subtitles
处理旧发布核心 ASS 转换器丢失换行 | PKG-MULTI-SUB-10 | 冻结5.1.2发布/源码转换差异,兼容转换适配与真实ASS字幕回归 | 保持已有效转换行为,旧发布ASS缺陷有先红后绿与实际产物证据;不得将VTT替代夹具算作ASS通过 | M | done | ## 5 包迁移:artplayer-plugin-jassub @@ -652,3 +652,4 @@ - CORE-SUBTITLE-OFFSET-01: [记录](changes/2026-09-14-CORE-SUBTITLE-OFFSET-01-paused.md) [记录](baselines/subtitle-offset-validation.json) - PKG-MULTI-SUB-09: [记录](changes/2026-09-14-PKG-MULTI-SUB-09-switch-order.md) [记录](baselines/multiple-subtitles-switch-validation.json) - PKG-MULTI-SUB-10: [记录](changes/2026-09-14-PKG-MULTI-SUB-10-legacy-captions.md) [记录](baselines/multiple-subtitles-legacy-validation.json) +- PKG-MULTI-SUB-11: [记录](changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md) [记录](baselines/multiple-subtitles-ass-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index 999890e12..30c142fcb 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,19 @@ # 进度与证据 +## PKG-MULTI-SUB-11 旧发布核心 ASS 转换适配完成 + +实际5.1.2 tarball证明只有main转换器丢失模板换行,同版legacy与源码正确;新增 +ASS适配模块先调用宿主转换器一次,仅当完整输出匹配已复现损坏格式才补回结构换行。 +有效、自定义、空输出和异常保持原行为,不修改宿主utils、不依赖新核心、无新增依赖。 +冻结转换器回归修复前6失败/7通过,旧产物浏览器仅加载2/3字幕;修复后全插件315通过, +实际main/legacy各15项定向测试通过。源码/main/legacy各21项三引擎播放通过,覆盖 +5.1.2/5.1.7/5.3.0/5.4.0/候选、UTF16、多行、语义HTML、独立时间与重叠翻译、选择重置。 +两个旧宿主第三轨现在都使用真实ASS。严格TS、37声明零漂移、lint与CI50通过,三格式及 +docs副本正常重建;关闭ASS风险。见[变更](changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md) +和[验证索引](baselines/multiple-subtitles-ass-validation.json)。251项:179 done、17 doing、55 todo。 +不计作完整ASS排版、真机或远端CI验收。旧WebKit切源风险仍open;下一步继续task05 +剩余组合验收与切源诊断,随后推进分发及其他插件未完成任务。 + ## PKG-MULTI-SUB-10 旧核心多条字幕显示适配完成 插件以activeCue/activeCues能力检测选择旧subtitleUpdate或现代subtitleAfterUpdate, diff --git a/refactor/risk-table.md b/refactor/risk-table.md index 57cb7e1ce..86c2e8144 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -267,4 +267,4 @@ | MULTI-SUB-MARKUP-01 | resolved / 已复现 | Semantic cue nodes receive HTML wrappers inside their annotation fields | PKG-MULTI-SUB-08 | | MULTI-SUB-OFFSET-01 | resolved / 已复现 | Paused Firefox subtitle offset leaves out-of-time native cues active and captions visible | PKG-MULTI-SUB-05, CORE-SUBTITLE-OFFSET-01 | | MULTI-SUB-SWITCH-01 | open / 已复现 | Intermittent WebKit core5.3.0 caption loss after selected-track source switch | PKG-MULTI-SUB-05, PKG-MULTI-SUB-09 | -| MULTI-SUB-ASS-01 | open / 已复现 | Published core5.1.2 ASS converter loses required VTT line breaks | PKG-MULTI-SUB-11 | +| MULTI-SUB-ASS-01 | resolved / 已复现 | Published core5.1.2 ASS converter loses required VTT line breaks | PKG-MULTI-SUB-11 | diff --git a/refactor/risks.json b/refactor/risks.json index 8c71d9871..5d14e04f3 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "task": "BASE-07", "sourceCommit": "e01f39ff", - "updatedAt": "2026-09-14T05:23:17.398Z", + "updatedAt": "2026-09-14T05:41:02.580Z", "note": "Open means closure evidence is outstanding; source-observed and unverified are not reproduced runtime bugs. A source issue already mitigated in the workspace still requires candidate regression evidence. No accepted-with-scope item is implied by baseline task completion.", "items": [ { @@ -6064,7 +6064,7 @@ "id": "MULTI-SUB-ASS-01", "title": "Published core5.1.2 ASS converter loses required VTT line breaks", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-MULTI-SUB-11" ], @@ -6072,11 +6072,20 @@ "refactor/changes/2026-09-14-PKG-MULTI-SUB-10-legacy-captions.md", "refactor/baselines/multiple-subtitles-legacy-validation.json", "refactor/baselines/multiple-subtitles-cores.json", - "test/browser/multiple-subtitles-legacy.spec.js" + "test/browser/multiple-subtitles-legacy.spec.js", + "refactor/changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md", + "refactor/baselines/multiple-subtitles-ass-validation.json", + "test/multiple-subtitles-ass.test.js", + "test/browser/multiple-subtitles-ass.spec.js" ], - "workspaceState": "Initial three-engine regression gets only2/3 native cues on actual5.1.2 main. Tarball source assToVtt preserves newlines while published main collapses template line breaks to spaces.5.1.7 ASS renders. Current display regression uses VTT for third5.1.2 track and explicitly does not claim ASS acceptance.", + "workspaceState": "Task11 verifies actual5.1.2 main/legacy/source hashes and repairs only complete converter output matching collapsed main line breaks. Full plugin315 and actual-format targeted tests pass; source/main/legacy each21 native desktop cases pass across5 cores and3 engines. Both old-host third tracks now use ASS. Original host utils unchanged; valid/custom output and exceptions preserved. Full ASS layout and physical devices are not claimed.", "compatibleResolution": "Adapt malformed old conversion in the plugin while preserving valid existing converter output and public API.", - "closureCriteria": "Frozen actual-output reproduction, compatible conversion fix and real ASS display verified on old/current hosts and shippable formats." + "closureCriteria": "Frozen actual-output reproduction, compatible conversion fix and real ASS display verified on old/current hosts and shippable formats.", + "resolutionEvidence": [ + "refactor/changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md", + "refactor/baselines/multiple-subtitles-ass-validation.json" + ], + "resolutionRationale": "Frozen main output fails before repair while same-version legacy/source remain correct. Exact-output repair preserves valid/custom output and exception ownership.315 package checks, actual main/legacy unit cases and source/main/legacy each21 native desktop cases prove this specific conversion defect fixed, without claiming full ASS layout, device or source-seek acceptance." } ] } diff --git a/refactor/tasks.json b/refactor/tasks.json index c9a849b88..f300c38cb 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -5212,11 +5212,14 @@ "dependsOn": [ "PKG-MULTI-SUB-10" ], - "status": "todo", + "status": "done", "risk": "M", "deliverable": "冻结5.1.2发布/源码转换差异,兼容转换适配与真实ASS字幕回归", "acceptance": "保持已有效转换行为,旧发布ASS缺陷有先红后绿与实际产物证据;不得将VTT替代夹具算作ASS通过", - "evidence": [] + "evidence": [ + "changes/2026-09-14-PKG-MULTI-SUB-11-ass-conversion.md", + "baselines/multiple-subtitles-ass-validation.json" + ] } ] } diff --git a/test/README.md b/test/README.md index d7d543cde..c1042325b 100644 --- a/test/README.md +++ b/test/README.md @@ -1,5 +1,12 @@ # Tests and fixture ownership +`test/multiple-subtitles-ass.test.js` verifies hashes of actual core5.1.2 main, +legacy and source before comparing conversion behavior. It covers the collapsed +main output, source-equivalent timings/text, valid/custom/empty passthrough and +converter failure cleanup through the real candidate plugin. It runs in both +`test:unit` and `test:multiple-subtitles`. This is native VTT conversion, not ASS +layout/rendering acceptance for JASSUB. + `test/multiple-subtitles-caption.test.js` checks the old-host view adapter with the actual plugin bundle: native order, original update identity/scalar event payload, escape changes, timestamp cleanup, modern-host isolation and destroy/registration diff --git a/test/browser/README.md b/test/browser/README.md index 9cc0540a9..525f42f68 100644 --- a/test/browser/README.md +++ b/test/browser/README.md @@ -270,10 +270,16 @@ the earlier hidden-preview screenshot timeout remains in the checkpoint evidence `multiple-subtitles-legacy.spec.js` uses real 5.1.2/5.1.7 cores and the candidate plugin to check three independently timed captions, VTT/SRT, semantic HTML/CSS, the original update method and native cue identities, scalar event payloads, -repeated updates, selection/reset and empty intervals. It uses ASS for the third -track on5.1.7; the5.1.2 published converter's malformed VTT remains a separate -task11 failure and is not covered by substituting a VTT fixture. Use +repeated updates, selection/reset and empty intervals. It now uses ASS for the third +track on both hosts: task11 repairs the exact5.1.2 main converter defect inside +the plugin. Task10's historical VTT substitution was not ASS acceptance. Use `ARTPLAYER_MULTIPLE_SUBTITLES_ARTIFACT` for actual main/legacy package bytes. The combination suite retains explicit published1.1/1.2 overlap-loss observations while requiring candidate captions to contain both languages. A passing historical observation is not a compatibility pass. + +`multiple-subtitles-ass.spec.js` checks UTF16 decoding, explicit ASS type selection, +multiline text/HTML, overlapping translation, selection/reset and empty intervals +on5.1.2/5.1.7/5.3.0/5.4.0/candidate. The attachment records the unchanged original +host converter output alongside actual native cues and browser/artifact hashes. +It covers desktop video/track playback, not full ASS layout or physical devices. diff --git a/test/browser/multiple-subtitles-ass.spec.js b/test/browser/multiple-subtitles-ass.spec.js new file mode 100644 index 000000000..d22f51851 --- /dev/null +++ b/test/browser/multiple-subtitles-ass.spec.js @@ -0,0 +1,58 @@ +import { Buffer } from 'node:buffer' +import { hash } from '../../refactor/scripts/releases.mjs' +import { multipleSubtitlesCandidate } from '../helpers/multiple-subtitles.js' +import { expect, test } from './fixtures.js' + +const candidate = await multipleSubtitlesCandidate() +const ass = '[Events]\r\nDialogue: 0,0:00:01.25,0:00:03.75,Default,,0,0,0,,漢字 café, comma bold\\NSecond line\r\nDialogue: 0,0:00:04.25,0:00:06.75,Default,,0,0,0,,Later cue' + +for (const core of ['published-5.1.2', 'published-5.1.7', 'published-5.3.0', 'published', 'candidate']) { + test(`Multiple subtitles ASS / ${core}: encoded multiline cues, overlapping translation and selection`, async ({ page, browser }, testInfo) => { + await page.route('**/test/ass-data.bin', route => route.fulfill({ contentType: 'application/octet-stream', body: Buffer.from(ass, 'utf16le') })) + await page.route('**/test/ass-translation.vtt', route => route.fulfill({ contentType: 'text/vtt', body: 'WEBVTT\n\n00:02.000 --> 00:04.000\nTranslation\n' })) + await page.goto(`/test/player.html?core=${core}`) + await page.addScriptTag({ content: candidate.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: 'ass', url: '/test/ass-data.bin', type: 'ass', encoding: 'utf-16le' }, + { name: 'translation', url: '/test/ass-translation.vtt' }, + ] })) + }) + await expect.poll(() => page.evaluate(() => window.art.template.$track.track.cues?.length)).toBe(3) + const native = await page.evaluate(() => Array.from(window.art.template.$track.track.cues, cue => ({ start: cue.startTime, end: cue.endTime, text: cue.getCueAsHTML().textContent }))) + expect(native.map(cue => [cue.start, cue.end])).toEqual([[1.25, 3.75], [2, 4], [4.25, 6.75]]) + expect(native[0].text).toContain('漢字 café, comma bold') + expect(native[0].text).toContain('Second line') + await page.locator('#play').click() + await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1) + await page.locator('#pause').click() + async function seek(time) { + await page.evaluate(time => new Promise((resolve) => { + window.art.once('video:seeked', resolve) + window.art.seek = time + }), time) + } + await seek(1.5) + await expect(page.locator('.art-subtitle-ass b')).toHaveText('bold') + await expect(page.locator('.art-subtitle')).toContainText('Second line') + await expect(page.locator('.art-subtitle-translation')).toHaveCount(0) + await seek(2.5) + await expect(page.locator('.art-subtitle-ass b')).toBeVisible() + await expect(page.locator('.art-subtitle-translation')).toHaveText('Translation') + await page.evaluate(() => window.art.plugins.multipleSubtitles.tracks(['translation'])) + await expect(page.locator('.art-subtitle-ass')).toHaveCount(0) + await expect(page.locator('.art-subtitle-translation')).toHaveText('Translation') + await page.evaluate(() => window.art.plugins.multipleSubtitles.reset()) + await expect(page.locator('.art-subtitle-ass b')).toBeVisible() + await seek(5) + await expect(page.locator('.art-subtitle-ass')).toHaveText('Later cue') + await expect(page.locator('.art-subtitle-translation')).toHaveCount(0) + await seek(7.5) + await expect(page.locator('.art-subtitle-ass, .art-subtitle-translation')).toHaveCount(0) + const rawHostOutput = await page.evaluate(ass => window.Artplayer.utils.assToVtt(ass), ass) + await testInfo.attach('ass-conversion', { contentType: 'application/json', body: JSON.stringify({ core, browser: browser.version(), pluginSha256: hash(candidate.code), sourceSha256: hash(Buffer.from(ass, 'utf16le')), rawHostOutput, native, scope: 'Desktop native playback with UTF16 ASS, explicit type override, independently timed multiline/semantic captions, overlap, selection/reset and clearing; original core converter is unchanged.' }) }) + await page.evaluate(() => window.art.destroy()) + }) +} diff --git a/test/browser/multiple-subtitles-legacy.spec.js b/test/browser/multiple-subtitles-legacy.spec.js index ad300ee5e..80b9791ca 100644 --- a/test/browser/multiple-subtitles-legacy.spec.js +++ b/test/browser/multiple-subtitles-legacy.spec.js @@ -9,7 +9,6 @@ for (const core of ['published-5.1.2', 'published-5.1.7']) { await page.route('**/test/legacy-a.vtt', route => route.fulfill({ contentType: 'text/vtt', body: 'WEBVTT\n\n00:01.000 --> 00:03.000\nA & safe\n' })) await page.route('**/test/legacy-b.srt', route => route.fulfill({ contentType: 'text/plain', body: '1\n00:00:02,000 --> 00:00:04,000\nB\n' })) await page.route('**/test/legacy-c.ass', route => route.fulfill({ contentType: 'text/plain', body: 'Dialogue: 0,0:00:02.25,0:00:02.75,Default,,0,0,0,,C' })) - await page.route('**/test/legacy-c.vtt', route => route.fulfill({ contentType: 'text/vtt', body: 'WEBVTT\n\n00:02.250 --> 00:02.750\nC\n' })) await page.goto(`/test/player.html?core=${core}`) await page.addScriptTag({ content: candidate.code }) await page.addStyleTag({ content: '.art-subtitle-a { color: rgb(255, 0, 0); } .art-subtitle-b { color: rgb(0, 0, 255); }' }) @@ -20,7 +19,7 @@ for (const core of ['published-5.1.2', 'published-5.1.7']) { window.art.on('subtitleUpdate', text => window.legacyPayloads.push(text)) }) await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true) - const thirdFormat = core === 'published-5.1.2' ? 'vtt' : 'ass' + const thirdFormat = 'ass' await page.evaluate(async (thirdFormat) => { await window.art.plugins.add(window.artplayerPluginMultipleSubtitles({ subtitles: [{ name: 'a', url: '/test/legacy-a.vtt' }, { name: 'b', url: '/test/legacy-b.srt' }, { name: 'c', url: `/test/legacy-c.${thirdFormat}` }] })) }, thirdFormat) @@ -70,7 +69,7 @@ for (const core of ['published-5.1.2', 'published-5.1.7']) { await expect(page.locator('.art-subtitle-c')).toHaveText('C') await seek(4.5) await expect(page.locator('.art-subtitle-a, .art-subtitle-b, .art-subtitle-c')).toHaveCount(0) - await testInfo.attach('legacy-captions', { contentType: 'application/json', body: JSON.stringify({ core, browser: browser.version(), pluginSha256: hash(candidate.code), thirdFormat, state, scope: 'Native VTT/SRT overlaps (ASS additionally on 5.1.7), semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals', limitation: '5.1.2 published ASS converter loses required VTT line breaks; tracked separately in PKG-MULTI-SUB-11.' }) }) + await testInfo.attach('legacy-captions', { contentType: 'application/json', body: JSON.stringify({ core, browser: browser.version(), pluginSha256: hash(candidate.code), thirdFormat, state, scope: 'Native VTT/SRT/ASS overlaps on both old cores, semantic HTML/CSS, legacy scalar event payload, original host method and cue identities, repeated update/selection/reset/empty intervals' }) }) await page.evaluate(() => window.art.destroy()) }) } diff --git a/test/multiple-subtitles-ass.test.js b/test/multiple-subtitles-ass.test.js new file mode 100644 index 000000000..fc2e81aef --- /dev/null +++ b/test/multiple-subtitles-ass.test.js @@ -0,0 +1,109 @@ +import assert from 'node:assert/strict' +import { Buffer } from 'node:buffer' +import fs from 'node:fs' +// eslint-disable-next-line test/no-import-node-test -- Frozen converter regressions use the repository Node runner. +import test from 'node:test' +import vm from 'node:vm' +import { parseHTML } from 'linkedom' +import { ensureArchive, hash, readMember } from '../refactor/scripts/releases.mjs' +import { loadModules } from './helpers/load.js' +import { multipleSubtitlesCandidate, multipleSubtitlesEnvironment } from './helpers/multiple-subtitles.js' + +const release = JSON.parse(fs.readFileSync('refactor/baselines/multiple-subtitles-cores.json')).releases.find(item => item.version === '5.1.2') +const archive = await ensureArchive(release) +function read(member) { + const bytes = readMember(archive, member) + assert.equal(hash(bytes), release.files[member]) + return bytes.toString() +} +const published = ['main', 'legacy'].map((name) => { + const module = { exports: {} } + vm.runInNewContext(read(`package/dist/artplayer${name === 'main' ? '' : '.legacy'}.js`), { module, exports: module.exports }, { timeout: 5000 }) + return { name, utils: (module.exports.default || module.exports).utils } +}) +const oldSource = await import(`data:text/javascript;base64,${Buffer.from(read('package/src/utils/subtitle.js')).toString('base64')}`) +const candidate = await multipleSubtitlesCandidate() +const { parseTracks, assToVtt, convertAss } = await loadModules({ + parseTracks: { file: 'packages/artplayer-plugin-multiple-subtitles/src/merge', name: 'parseTracks' }, + assToVtt: { file: 'packages/artplayer/src/utils/subtitle', name: 'assToVtt' }, + convertAss: 'packages/artplayer-plugin-multiple-subtitles/src/ass-conversion', +}) +const dialogue = (text, start = '0:00:01.25', end = '0:00:03.75') => `Dialogue: 0,${start},${end},Default,,0,0,0,,${text}` +const html = text => parseHTML(`${text}`).document.body.innerHTML +const samples = [ + dialogue('Plain'), + dialogue('漢字 café & bold'), + dialogue('{\\i1}Italic{\\i0}\\N Second line '), + `${dialogue('First, comma')}\r\n${dialogue('Second', '0:00:02.50', '0:00:04.25')}`, + dialogue('Hour', '123:04:05.67', '124:05:06.78'), + `[Events]\nComment: not a caption\n${dialogue('Literal 2 00:00:02.000 --> 00:00:03.000 payload')}\n`, +] + +test('Multiple subtitles ASS adapter preserves custom, valid, empty and nonmatching converter output exactly', () => { + for (const converted of [ + '', + 'WEBVTT ', + 'WEBVTT\n\n', + 'Custom result', + oldSource.assToVtt(samples[0]), + 'WEBVTT 1 00:00:01.250 --> 00:00:03.750 Changed by user', + 'WEBVTT 1 00:00:01.250 --> 00:00:03.750 Plain\n\n', + ]) { + let calls = 0 + const actual = convertAss(samples[0], (input) => { + calls++ + assert.equal(input, samples[0]) + return converted + }) + assert.equal(calls, 1) + assert.equal(actual, converted) + } + for (const text of ['', '[Events]\nComment: no dialogue', dialogue('Invalid decimal', '0:00:01x25')]) { + const converted = published[0].utils.assToVtt(text) + assert.equal(convertAss(text, published[0].utils.assToVtt), converted) + } +}) + +test('Multiple subtitles ASS adapter propagates converter failure without mounting resources', async () => { + const error = new Error('Custom conversion failed') + let calls = 0 + const env = multipleSubtitlesEnvironment(candidate, { + responses: { 'a.ass': samples[0] }, + coreUtils: { assToVtt() { + calls++ + throw error + } }, + }) + await assert.rejects(env.factory({ subtitles: [{ name: 'ass', url: 'a.ass' }] })(env.art), value => value === error) + assert.equal(calls, 1) + assert.equal(env.initialized.length, 0) + assert.equal(env.liveBlobs.size, 0) + assert.equal(env.listeners.get('destroy').size, 0) +}) + +test('Multiple subtitles frozen5.1.2 main loses VTT line breaks while legacy and source retain them', () => { + assert.equal(published[0].utils.assToVtt(dialogue('Plain')), 'WEBVTT 1 00:00:01.250 --> 00:00:03.750 Plain') + for (const sample of samples) { + assert.equal(published[1].utils.assToVtt(sample), oldSource.assToVtt(sample)) + assert.equal(assToVtt(sample), oldSource.assToVtt(sample)) + } +}) + +for (const { name, utils } of published) { + for (const [index, sample] of samples.entries()) { + test(`Multiple subtitles ASS5.1.2 ${name} sample${index}: preserve source cue times and text`, async () => { + const env = multipleSubtitlesEnvironment(candidate, { coreUtils: utils, responses: { 'a.ass': sample } }) + const result = await env.factory({ subtitles: [{ name: 'ass', url: 'a.ass' }] })(env.art) + const expected = parseTracks([oldSource.assToVtt(sample)], [{ name: 'ass' }])[0].cues.map(cue => [cue.startTime, cue.endTime, html(cue.text)]) + const actual = () => env.latestText().then(vtt => parseTracks([env.utils.unescape(vtt)], [{ name: 'ass' }])[0].cues.map(cue => [cue.startTime, cue.endTime, html(cue.text.replace(/
|<\/div>/g, ''))])) + assert.deepEqual(await actual(), expected) + result.tracks(['ass']) + assert.deepEqual(await actual(), expected) + result.reset() + assert.deepEqual(await actual(), expected) + assert.equal(env.requests.length, 1) + env.emit('destroy') + assert.equal(env.liveBlobs.size, 0) + }) + } +}