From 0b2efb07f1262cef9a2347339fb122e6758e0234 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Sun, 13 Sep 2026 18:35:28 +0800 Subject: [PATCH] refactor(danmuku): [PKG-DANMUKU-03] own input loading parsing and configuration --- docs/compiled/artplayer-plugin-danmuku.js | 2 +- .../artplayer-plugin-danmuku.legacy.js | 2 +- docs/compiled/artplayer-plugin-danmuku.mjs | 500 +- package.json | 4 +- .../artplayer-plugin-danmuku/ARCHITECTURE.md | 77 + packages/artplayer-plugin-danmuku/README.md | 22 + .../dist/artplayer-plugin-danmuku.js | 2 +- .../dist/artplayer-plugin-danmuku.legacy.js | 2 +- .../dist/artplayer-plugin-danmuku.mjs | 500 +- .../src/bilibili-parser.js | 56 + .../artplayer-plugin-danmuku/src/bilibili.js | 213 +- .../artplayer-plugin-danmuku/src/config.js | 89 + .../artplayer-plugin-danmuku/src/danmuku.js | 165 +- .../artplayer-plugin-danmuku/src/index.js | 6 +- .../artplayer-plugin-danmuku/src/input.js | 88 + .../baselines/danmuku-input-validation.json | 6771 +++++++++++++++++ .../2026-09-13-PKG-DANMUKU-03-input.md | 73 + refactor/plan.md | 7 +- refactor/progress.md | 11 + refactor/risk-table.md | 4 +- refactor/risks.json | 42 +- refactor/tasks.json | 9 +- test/browser/danmuku-input.spec.js | 211 + test/danmuku-input.test.js | 355 + test/danmuku-parser.test.js | 403 + test/helpers/danmuku-candidate.js | 56 + 26 files changed, 9167 insertions(+), 503 deletions(-) create mode 100644 packages/artplayer-plugin-danmuku/ARCHITECTURE.md create mode 100644 packages/artplayer-plugin-danmuku/src/bilibili-parser.js create mode 100644 packages/artplayer-plugin-danmuku/src/config.js create mode 100644 packages/artplayer-plugin-danmuku/src/input.js create mode 100644 refactor/baselines/danmuku-input-validation.json create mode 100644 refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md create mode 100644 test/browser/danmuku-input.spec.js create mode 100644 test/danmuku-input.test.js create mode 100644 test/danmuku-parser.test.js create mode 100644 test/helpers/danmuku-candidate.js diff --git a/docs/compiled/artplayer-plugin-danmuku.js b/docs/compiled/artplayer-plugin-danmuku.js index 7869ed8ba..e1a89fc1a 100644 --- a/docs/compiled/artplayer-plugin-danmuku.js +++ b/docs/compiled/artplayer-plugin-danmuku.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDanmuku=e(),define(function(){return t.artplayerPluginDanmuku})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDanmuku=e()}(this,function(){"use strict";function t(t){switch(t){case 1:case 2:case 3:default:return 0;case 4:return 2;case 5:return 1}}function e(e){if("string"!==typeof e)return[];const i=e.matchAll(/]*?p="(?

[^"]+)"[^>]*>(?.*?)<\/d>/gs);return Array.from(i).map(e=>{const i=e.groups.p.split(",");if(i.length>=8){return{text:e.groups.text.trim().replaceAll(""",'"').replaceAll("'","'").replaceAll("<","<").replaceAll(">",">").replaceAll("&","&"),time:Number(i[0]),mode:t(Number(i[1])),fontSize:Number(i[2]),color:`#${Number(i[3]).toString(16)}`,timestamp:Number(i[4]),pool:Number(i[5]),userID:i[6],rowID:Number(i[7])}}return null}).filter(Boolean)}function i({data:t}){const{xml:i,id:a}=t;if(!a||!i)return;const n=e(i);globalThis.postMessage({danmus:n,id:a})}function a(){const a=`\n ${t.toString()}\n ${e.toString()}\n onmessage = ${i.toString()}\n `,n=new Blob([a],{type:"application/javascript"});return new Worker(URL.createObjectURL(n))}const n='/*!\n * artplayer-plugin-danmuku.js v5.3.0\n * Github: https://github.com/zhw2590582/ArtPlayer\n * (c) 2017-2026 Harvey Zhao\n * Released under the MIT License.\n */\nfunction t({target:t,visibles:e,clientWidth:i,clientHeight:h,marginBottom:n,marginTop:o,antiOverlap:s}){const r=h-n,p=e.filter(e=>e.mode===t.mode&&e.top<=r).sort((t,e)=>t.top-e.top);if(0===p.length)return 2===t.mode?r-t.height:o;if(p.unshift({type:"top",top:0,left:0,right:0,height:o,width:i,speed:0,distance:i}),p.push({type:"bottom",top:r,left:0,right:0,height:n,width:i,speed:0,distance:i}),2===t.mode)for(let g=p.length-2;g>=0;g-=1){const e=p[g],i=p[g+1],h=e.top+e.height;if(i.top-h>=t.height)return i.top-t.height}else for(let g=1;g=t.height)return h}const a=[];for(let g=1;g{const i=Math.min(...e.map(t=>t.right)),h=Math.min(...t.map(t=>t.right));return i*e.length-h*t.length});break;case 1:case 2:a.sort((t,e)=>{const i=Math.max(...e.map(t=>t.width));return Math.max(...t.map(t=>t.width))*t.length-i*e.length})}return a[0][0].top}switch(t.mode){case 0:{const e=a.find(e=>e.every(e=>{if(ie.time}));return e&&e[0]?e[0].top:void 0}case 1:case 2:return}}onmessage=e=>{const{data:i}=e;if(!i.id||!i.type)return;const h=(0,{getDanmuTop:t}[i.type])(i);globalThis.postMessage({result:h,id:i.id})};\n',s="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",n],{type:"text/javascript;charset=utf-8"});function o(t){let e;try{if(e=s&&(self.URL||self.webkitURL).createObjectURL(s),!e)throw"";const i=new Worker(e,{type:"module",name:t?.name});return i.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(e)}),i}catch(i){return new Worker("data:text/javascript;charset=utf-8,"+encodeURIComponent(n),{type:"module",name:t?.name})}}class r{constructor(t,e){const{constructor:i,template:a}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=a.$danmuku,this.$player=a.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.option=r.option,this.states={wait:[],ready:[],emit:[],stop:[]},this.config(e,!0),this.worker=new o,this.start=this.start.bind(this),this.stop=this.stop.bind(this),this.reset=this.reset.bind(this),this.resize=this.resize.bind(this),this.destroy=this.destroy.bind(this),t.on("video:play",this.start),t.on("video:playing",this.start),t.on("video:pause",this.stop),t.on("video:waiting",this.stop),t.on("destroy",this.destroy),t.on("resize",this.resize),this.load()}static get option(){return{danmuku:[],speed:5,margin:[10,"25%"],opacity:1,color:"#FFFFFF",mode:0,modes:[0,1,2],fontSize:25,antiOverlap:!0,synchronousPlayback:!1,mount:void 0,heatmap:!1,width:512,points:[],filter:()=>!0,beforeEmit:()=>!0,beforeVisible:()=>!0,visible:!0,emitter:!0,maxLength:200,lockTime:5,theme:"dark",OPACITY:{},FONT_SIZE:{},MARGIN:{},SPEED:{},COLOR:[]}}static get scheme(){return{danmuku:"array|function|string",speed:"number",margin:"array",opacity:"number",color:"string",mode:"number",modes:"array",fontSize:"number|string",antiOverlap:"boolean",synchronousPlayback:"boolean",mount:"?htmldivelement|string",heatmap:"object|boolean",width:"number",points:"array",filter:"function",beforeEmit:"function",beforeVisible:"function",visible:"boolean",emitter:"boolean",maxLength:"number",lockTime:"number",theme:"string",OPACITY:"object",FONT_SIZE:"object",MARGIN:"object",SPEED:"object",COLOR:"array"}}static get cssText(){return'\n user-select: none;\n position: absolute;\n white-space: pre;\n pointer-events: none;\n perspective: 500px;\n display: inline-block;\n will-change: transform;\n font-weight: normal;\n line-height: 1.125;\n visibility: hidden;\n font-family: SimHei, "Microsoft JhengHei", Arial, Helvetica, sans-serif;\n text-shadow: rgb(0, 0, 0) 1px 0px 1px, rgb(0, 0, 0) 0px 1px 1px, rgb(0, 0, 0) 0px -1px 1px, rgb(0, 0, 0) -1px 0px 1px;\n '}get isRotate(){return this.art.plugins?.autoOrientation?.state}get marginTop(){const{clamp:t}=this.utils,e=this.option.margin[0],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return r.option.margin[0]}get marginBottom(){const{clamp:t}=this.utils,e=this.option.margin[1],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return r.option.margin[1]}get fontSize(){const{clamp:t}=this.utils,{clientHeight:e}=this.$player,i=this.option.fontSize;if("number"===typeof i)return Math.round(t(i,12,e));if("string"===typeof i&&i.endsWith("%")){const a=Number.parseFloat(i)/100;return Math.round(t(e*a,12,e))}return r.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=r.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){const{currentTime:t}=this.art,e=[];return this.filter("ready",t=>e.push(t)),this.filter("wait",i=>{t+.1>=i.time&&i.time>=t-.1&&e.push(i)}),e}get visibles(){const t=[],{clientWidth:e}=this.$player,i=this.getLeft(this.$player);return this.filter("emit",a=>{const n=a.$ref.offsetTop,s=this.getLeft(a.$ref)-i,o=a.$ref.clientHeight,r=a.$ref.clientWidth,l=s+r,d=e-l,p=l/a.$restTime,h={};h.top=n,h.left=s,h.height=o,h.width=r,h.right=d,h.speed=p,h.distance=l,h.time=a.$restTime,h.mode=a.mode,t.push(h)}),t}get speed(){return this.option.synchronousPlayback&&this.art.playbackRate?this.option.speed/Number(this.art.playbackRate):this.option.speed}async load(t){const{errorHandle:i}=this.utils;let n=[];const s=t||this.option.danmuku;try{"function"===typeof s?n=await s():s instanceof Promise?n=await s:"string"===typeof s?n=await(o=s,new Promise(async t=>{const i=await fetch(o),n=await i.text();try{const e=a();e.onmessage=i=>{const{danmus:a,id:n}=i.data;n&&a&&(t(a),e.terminate())},e.postMessage({xml:n,id:Date.now()})}catch(s){console.error("Error parsing Bilibili Danmu:",s);const i=e(n);t(i)}})):Array.isArray(s)&&(n=s),i(Array.isArray(n),"Danmuku need return an array as result"),void 0===t&&(this.reset(),this.queue=[],this.states={wait:[],ready:[],emit:[],stop:[]},this.$refs=[],this.$danmuku.textContent="");for(let t=0;tJSON.stringify(this.option[e])!==JSON.stringify(t[e]))||e?(this.option=Object.assign({},r.option,this.option,t),this.validator(this.option,r.scheme),this.option.mode=i(this.option.mode,0,2),this.option.speed=i(this.option.speed,1,10),this.option.opacity=i(this.option.opacity,0,1),this.option.lockTime=i(this.option.lockTime,1,60),this.option.maxLength=i(this.option.maxLength,1,1e3),this.option.mount=this.option.mount||a,t.fontSize&&this.reset(),this.option.visible?this.show():this.hide(),this.art.emit("artplayerPluginDanmuku:config",this.option),this):this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return new Promise(e=>{t.id=Date.now(),this.worker.postMessage(t),this.worker.onmessage=i=>{const{data:a}=i;a.id===t.id&&e(a)}})}filter(t,e){const i=this.states[t]||[];for(let a=0;ae!==t),t.$state=e,t.$ref&&(t.$ref.dataset.state=e),this.states[e].push(t)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=r.cssText,t.$ref.style.visibility="hidden",t.$ref.style.marginLeft="0px",t.$ref.style.transform="translateX(0px)",t.$ref.style.transition="transform 0s linear 0s",this.$refs.push(t.$ref),t.$ref=null)}update(){const{setStyles:t}=this.utils;return this.timer=window.requestAnimationFrame(async()=>{if(this.art.playing&&!this.isHide){this.filter("emit",t=>{const e=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=e,t.$lastStartTime=Date.now(),t.$restTime<=0&&this.makeWait(t)});const e=this.readys;for(let i=0;i{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.filter("emit",e=>{switch(e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.left=`${t}px`,e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}})}continue(){const{clientWidth:t}=this.$player;return this.filter("stop",e=>{switch(this.setState(e,"emit"),e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}}),this}suspend(){const{clientWidth:t}=this.$player;return this.filter("emit",e=>{switch(this.setState(e,"stop"),e.mode){case 0:{const i=t-(this.getLeft(e.$ref)-this.getLeft(this.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}stop(){return this.isStop=!0,this.suspend(),window.cancelAnimationFrame(this.timer),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){return this.isStop=!1,this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start"),this}reset(){return this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this}show(){return this.isHide=!1,this.$danmuku.style.opacity=1,this.option.visible=!0,this.art.emit("artplayerPluginDanmuku:show"),this}hide(){return this.isHide=!0,this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this}destroy(){this.stop(),this.worker.terminate(),this.art.off("video:play",this.start),this.art.off("video:playing",this.start),this.art.off("video:pause",this.stop),this.art.off("video:waiting",this.stop),this.art.off("resize",this.reset),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}const l={map:(t,e,i,a,n)=>(t-e)*(n-a)/(i-e)+a,range(t,e,i){const a=Math.round(t/i)*i;return Array.from({length:Math.floor((e-t)/i)},(t,e)=>e*i+a)}};function d(t,e,i){const{query:a}=t.constructor.utils;t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},mounted(n){let s=null,o=null;function r(r=[]){if(s=null,o=null,n.innerHTML="",!t.duration||t.option.isLive)return;const d={w:n.offsetWidth,h:n.offsetHeight},p={xMin:0,xMax:d.w,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*d.h),sampling:Math.floor(d.w/100),smoothing:.2,flattening:.2};"object"===typeof i&&Object.assign(p,i);let h=[];if(Array.isArray(r)&&r.length)h=[...r];else{const i=t.duration/d.w;for(let t=0;t<=d.w;t+=p.sampling){const a=e.queue.filter(({time:e})=>e>t*i&&e<=(t+p.sampling)*i).length;h.push([t,a])}}if(0===h.length)return;const u=h[h.length-1],c=u[0],m=u[1];c!==d.w&&h.push([d.w,m]);const g=h.map(t=>t[1]),f=(Math.min(...g)+Math.max(...g))/2;for(let t=0;tf?1+p.scale:1-p.scale)+p.minHeight}const y=(t,e,i,a)=>{const n=function(t,e){const i=e[0]-t[0],a=e[1]-t[1];return{length:Math.sqrt(i**2+a**2),angle:Math.atan2(a,i)}}(e||t,i||t),s=l.map(Math.cos(n.angle)*p.flattening,0,1,1,0),o=n.angle*s+(a?Math.PI:0),r=n.length*p.smoothing;return[t[0]+Math.cos(o)*r,t[1]+Math.sin(o)*r]},v=h.map(t=>[l.map(t[0],p.xMin,p.xMax,0,d.w),l.map(t[1],p.yMin,p.yMax,d.h,0)]).reduce((t,e,i,a)=>0===i?`M ${a[a.length-1][0]},${d.h} L ${e[0]},${d.h} L ${e[0]},${e[1]}`:`${t} ${((t,e,i)=>{const a=y(i[e-1],i[e-2],t),n=y(t,i[e-1],i[e+1],!0),s=e===i.length-1?" z":"";return`C ${a[0]},${a[1]} ${n[0]},${n[1]} ${t[0]},${t[1]}${s}`})(e,i,a)}`,"");n.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,s=a("#heatmap-start",n),o=a("#heatmap-stop",n),s.setAttribute("offset",100*t.played+"%"),o.setAttribute("offset",100*t.played+"%")}t.on("video:timeupdate",()=>{s&&o&&(s.setAttribute("offset",100*t.played+"%"),o.setAttribute("offset",100*t.played+"%"))}),t.on("setBar",(t,e)=>{s&&o&&"played"===t&&(s.setAttribute("offset",100*e+"%"),o.setAttribute("offset",100*e+"%"))}),t.on("ready",()=>r()),t.on("resize",()=>r()),t.on("artplayerPluginDanmuku:loaded",()=>r()),t.on("artplayerPluginDanmuku:points",t=>r(t))}})}const p='',h='',u='',c='',m='',g='',f='',y='',v='',k='',$='',x='';class w{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:a}=t.template;i(a,"display","flex"),this.template={$controlsCenter:a,$mount:a,$danmuku:null,$toggle:null,$config:null,$configPanel:null,$configModes:null,$style:null,$stylePanel:null,$styleModes:null,$colors:null,$opacitySlider:null,$opacityValue:null,$marginSlider:null,$marginValue:null,$fontSizeSlider:null,$fontSizeValue:null,$speedSlider:null,$speedValue:null,$input:null,$send:null},this.slider={opacity:null,margin:null,fontSize:null,speed:null},this.emitting=!1,this.isLock=!1,this.timer=null,this.createTemplate(),this.createSliders(),this.createEvents(),this.mount(this.option.mount),t.on("resize",()=>this.resize()),t.on("fullscreen",t=>this.onFullscreen(t)),t.on("fullscreenWeb",t=>this.onFullscreen(t)),t.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),t.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}static get icons(){return{$on:$,$off:k,$config:u,$style:x,$mode_0_off:c,$mode_0_on:m,$mode_1_off:g,$mode_1_on:f,$mode_2_off:y,$mode_2_on:v,$check_on:h,$check_off:p}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){const{option:t}=this;return`\n

\n ${$}${k}\n
\n
\n ${u}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${c}${m}\n
\u6eda\u52a8
\n
\n
\n ${g}${f}\n
\u9876\u90e8
\n
\n
\n ${y}${v}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${h}${p}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${h}${p}\n \u540c\u6b65\u89c6\u9891\u901f\u5ea6\n
\n
\n
\n \u4e0d\u900f\u660e\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n \u663e\u793a\u533a\u57df\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u5b57\u53f7\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u901f\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n
\n
\n
\n
\n ${x}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${m}\n
\u6eda\u52a8
\n
\n
\n ${f}\n
\u9876\u90e8
\n
\n
\n ${v}\n
\u5e95\u90e8
\n
\n
\n
\n
\n \u989c\u8272\n
\n ${this.COLOR.map(t=>`
`).join("")}\n
\n
\n
\n
\n
\n \n
\u53d1\u9001
\n
\n `}get OPACITY(){return{min:0,max:100,steps:[],...this.option.OPACITY}}get FONT_SIZE(){return{min:12,max:120,steps:[],...this.option.FONT_SIZE}}get MARGIN(){return{min:0,max:3,steps:[{name:"1/4",value:[10,"75%"]},{name:"\u534a\u5c4f",value:[10,"50%"]},{name:"3/4",value:[10,"25%"]},{name:"\u6ee1\u5c4f",value:[10,10]}],...this.option.MARGIN}}get SPEED(){return{min:0,max:4,steps:[{name:"\u6781\u6162",value:10},{name:"\u8f83\u6162",value:7.5,hide:!0},{name:"\u9002\u4e2d",value:5},{name:"\u8f83\u5feb",value:2.5,hide:!0},{name:"\u6781\u5feb",value:1}],...this.option.SPEED}}get COLOR(){return this.option.COLOR.length?this.option.COLOR:["#FE0302","#FF7204","#FFAA02","#FFD302","#FFFF00","#A0EE00","#00CD00","#019899","#4266BE","#89D5FF","#CC0273","#222222","#9B9B9B","#FFFFFF"]}query(t){const{query:e}=this.utils,{$danmuku:i}=this.template;return e(t,i)}append(t,e){const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){const{$player:i}=this.art.template,{$mount:a}=this.template;i.dataset[t]=e,this.outside&&(a.dataset[t]=e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,this.template.$danmuku=i,this.template.$toggle=this.query(".apd-toggle"),this.template.$config=this.query(".apd-config"),this.template.$configPanel=this.query(".apd-config-panel"),this.template.$configModes=this.query(".apd-config-mode .apd-modes"),this.template.$style=this.query(".apd-style"),this.template.$stylePanel=this.query(".apd-style-panel"),this.template.$styleModes=this.query(".apd-style-mode .apd-modes"),this.template.$colors=this.query(".apd-colors"),this.template.$antiOverlap=this.query(".apd-anti-overlap"),this.template.$syncVideo=this.query(".apd-sync-video"),this.template.$opacitySlider=this.query(".apd-config-opacity .apd-slider"),this.template.$opacityValue=this.query(".apd-config-opacity .apd-value"),this.template.$marginSlider=this.query(".apd-config-margin .apd-slider"),this.template.$marginValue=this.query(".apd-config-margin .apd-value"),this.template.$fontSizeSlider=this.query(".apd-config-fontSize .apd-slider"),this.template.$fontSizeValue=this.query(".apd-config-fontSize .apd-value"),this.template.$speedSlider=this.query(".apd-config-speed .apd-slider"),this.template.$speedValue=this.query(".apd-config-speed .apd-value"),this.template.$input=this.query(".apd-input"),this.template.$send=this.query(".apd-send");const{$toggle:a}=this.template;this.art.on("artplayerPluginDanmuku:show",()=>{e(a,"\u5173\u95ed\u5f39\u5e55")}),this.art.on("artplayerPluginDanmuku:hide",()=>{e(a,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:a,$antiOverlap:n,$syncVideo:s,$send:o,$input:r}=this.template;this.art.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.art.proxy(e,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.option.modes.includes(i)?this.danmuku.config({modes:this.option.modes.filter(t=>t!==i)}):this.danmuku.config({modes:[...this.option.modes,i]}),this.reset()}),this.art.proxy(n,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.art.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.art.proxy(i,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.danmuku.config({mode:i}),this.reset()}),this.art.proxy(a,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.art.proxy(o,"click",()=>this.emit()),this.art.proxy(r,"keypress",t=>{"Enter"===t.key&&(t.preventDefault(),this.emit())})}createSliders(){this.slider.opacity=this.createSlider({...this.OPACITY,container:this.template.$opacitySlider,findIndex:()=>Math.round(100*this.option.opacity),onChange:t=>{const{$opacityValue:e}=this.template;e.textContent=`${t}%`,this.danmuku.config({opacity:t/100})}}),this.slider.margin=this.createSlider({...this.MARGIN,container:this.template.$marginSlider,findIndex:()=>this.MARGIN.steps.findIndex(t=>t.value[0]===this.option.margin[0]&&t.value[1]===this.option.margin[1]),onChange:t=>{const e=this.MARGIN.steps[t];if(!e)return;const{$marginValue:i}=this.template;i.textContent=e.name,this.danmuku.config({margin:e.value})}}),this.slider.fontSize=this.createSlider({...this.FONT_SIZE,container:this.template.$fontSizeSlider,findIndex:()=>this.danmuku.fontSize,onChange:t=>{const{$fontSizeValue:e}=this.template;e.textContent=`${t}px`,t!==this.danmuku.fontSize&&this.danmuku.config({fontSize:t})}}),this.slider.speed=this.createSlider({...this.SPEED,container:this.template.$speedSlider,findIndex:()=>this.SPEED.steps.findIndex(t=>t.value===this.option.speed),onChange:t=>{const e=this.SPEED.steps[t];if(!e)return;const{$speedValue:i}=this.template;i.textContent=e.name,this.danmuku.config({speed:e.value})}})}createSlider({min:t,max:e,container:i,findIndex:a,onChange:n,steps:s=[]}){const{query:o,clamp:r,setStyle:l}=this.utils;l(i,"touch-action","none"),i.innerHTML=`\n
\n
\n ${s.map(()=>'
').join("")}\n
\n
\n
\n
\n
\n ${s.map(t=>t.hide?"":`
${t.name}
`).join("")}\n
\n `;const d=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i=a()){if(ie)return;const o=(i-t)/(e-t);d.style.left=100*o+"%",0===s.length&&(p.style.width=d.style.left),n(i)}function c(a){const{top:n,height:s,left:o,width:l}=i.getBoundingClientRect();if(this.art.isRotate){const i=r(a.clientY-n,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=r(a.clientX-o,0,l);u(Math.round(i/l*(e-t)+t))}}return this.art.proxy(i,"click",t=>{c.call(this,t)}),this.art.proxy(i,"pointerdown",t=>{h=0===t.button}),this.art.on("document:pointermove",t=>{h&&c.call(this,t)}),this.art.on("document:pointerup",t=>{h&&(h=!1,c.call(this,t))}),{reset:u}}onFullscreen(t){const{$danmuku:e,$controlsCenter:i,$mount:a}=this.template;this.outside?t?this.append(i,e):this.append(a,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){const{$player:i}=this.art.template,a=t.getBoundingClientRect(),n=e.getBoundingClientRect(),s=i.getBoundingClientRect(),o=n.width/2-a.width/2,r=s.left-(a.left-o),l=a.right+o-s.right;e.style.left=r>0?`${-o+r}px`:l>0?-o-l+"px":-o+"px"}async emit(){const{$input:t}=this.template,e=t.value.trim();if(!e.length)return;if(this.isLock)return;if(this.emitting)return;const i={text:e,mode:this.option.mode,color:this.option.color,time:this.art.currentTime};try{this.emitting=!0;const e=await this.option.beforeEmit(i);if(this.emitting=!1,!0!==e)return;i.border=!0,delete i.time,this.danmuku.emit(i),t.value="",this.lock()}catch(a){console.error("Error emitting danmuku:",a),this.emitting=!1}}lock(){const{addClass:t}=this.utils,{$send:e}=this.template;this.isLock=!0;let i=this.option.lockTime;e.textContent=i,t(e,"apd-lock");const a=()=>{this.timer=setTimeout(()=>{0===i?this.unlock():(i-=1,e.textContent=i,a())},1e3)};a()}unlock(){const{removeClass:t}=this.utils,{$send:e}=this.template;clearTimeout(this.timer),this.isLock=!1,e.textContent="\u53d1\u9001",t(e,"apd-lock")}resize(){if(this.outside)return;if(this.art.fullscreen)return;if(this.art.fullscreenWeb)return;const{$player:t,$controlsCenter:e}=this.art.template,{$danmuku:i}=this.template;this.art.widtht.dataset.color===this.option.color.toUpperCase());s&&t(s,"apd-active"),e(i,this.option.visible?"\u5173\u95ed\u5f39\u5e55":"\u6253\u5f00\u5f39\u5e55"),this.resize()}mount(t){const{errorHandle:e}=this.utils,i="string"===typeof t?document.querySelector(t):t;e(i,`Can not find the mount point: ${t}`),this.append(i,this.template.$danmuku),this.template.$mount=i,this.reset()}}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku";let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(e)}):(document.head||document.documentElement).appendChild(e)),e.textContent='.artplayer-plugin-danmuku{display:flex;position:relative;z-index:99;align-items:center;justify-content:center;font-size:12px;height:32px;width:100%;color:#fff;font-weight:300;flex-shrink:0;gap:10px}.artplayer-plugin-danmuku .apd-icon{cursor:pointer;opacity:.75;transition:all .2s ease;fill:#fff}.artplayer-plugin-danmuku .apd-icon:hover{opacity:1}.artplayer-plugin-danmuku .apd-config{display:flex;position:relative}.artplayer-plugin-danmuku .apd-config .apd-config-panel{position:absolute;bottom:24px;left:0;width:320px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-config-mode,.artplayer-plugin-danmuku .apd-config-slider,.artplayer-plugin-danmuku .apd-config-other,.artplayer-plugin-danmuku .apd-style-mode{margin-bottom:15px}.artplayer-plugin-danmuku .apd-modes{display:flex;align-items:center;margin-top:5px;gap:20px}.artplayer-plugin-danmuku .apd-modes .apd-mode{cursor:pointer;text-align:center}.artplayer-plugin-danmuku .apd-modes .apd-mode:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-config-slider{display:flex;align-items:center;gap:12px}.artplayer-plugin-danmuku .apd-config-slider .apd-value{width:32px;text-align:right}.artplayer-plugin-danmuku .apd-slider{position:relative;flex:1;display:flex;height:20px;align-items:center;justify-content:center;cursor:pointer}.artplayer-plugin-danmuku .apd-slider .apd-slider-line{position:relative;height:2px;width:100%;overflow:hidden;border-radius:3px;background-color:#ffffff40}.artplayer-plugin-danmuku .apd-slider .apd-slider-points{position:absolute;inset:0;display:flex;align-items:center;justify-content:space-between}.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point{width:2px;height:2px;border-radius:50%;background-color:#ffffff80}.artplayer-plugin-danmuku .apd-slider .apd-slider-progress{width:0%;height:100%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-dot{position:absolute;transform:translate(-6px);left:0%;width:12px;height:12px;border-radius:50%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps{display:flex;align-items:center;justify-content:space-between;position:absolute;bottom:-12px;width:calc(100% + 32px);color:#777}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step{flex-shrink:0;width:36px;text-align:center;scale:.95}.artplayer-plugin-danmuku .apd-config-other{display:flex;align-items:center;gap:20px}.artplayer-plugin-danmuku .apd-config-other .apd-check-off,.artplayer-plugin-danmuku .apd-config-other .apd-check-on{width:16px;height:16px}.artplayer-plugin-danmuku .apd-config-other .apd-other{display:flex;align-items:center;cursor:pointer;gap:2px}.artplayer-plugin-danmuku .apd-config-other .apd-other:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-emitter{display:flex;flex:1;align-items:center;height:100%;background-color:#ffffff40;border-radius:5px}.artplayer-plugin-danmuku .apd-style{position:relative;display:flex;align-items:center;justify-content:center}.artplayer-plugin-danmuku .apd-style .apd-style-panel{position:absolute;bottom:24px;left:0;width:200px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-colors{display:flex;flex-wrap:wrap;margin-top:5px;gap:8px}.artplayer-plugin-danmuku .apd-colors .apd-color{width:16px;height:16px;border-radius:2px;cursor:pointer}.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active{border:1px solid black;box-shadow:0 0 0 1px #fff}.artplayer-plugin-danmuku .apd-input{outline:none;height:100%;flex:1;min-width:0;width:auto;border:none;line-height:1;color:#fff;background-color:transparent}.artplayer-plugin-danmuku .apd-input::placeholder{color:#ffffff80}.artplayer-plugin-danmuku .apd-send{display:flex;align-items:center;justify-content:center;height:100%;width:60px;flex-shrink:0;cursor:pointer;text-shadow:none;border-top-right-radius:5px;border-bottom-right-radius:5px;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-send.apd-lock{cursor:not-allowed;color:#666;background-color:#e7e7e7}.art-controls-center .apd-emitter{width:260px;flex:none}.art-fullscreen .artplayer-plugin-danmuku,.art-fullscreen-web .artplayer-plugin-danmuku{height:38px;gap:16px}.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on{width:28px;height:28px}.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter{width:400px;flex:none}.art-video-player>.artplayer-plugin-danmuku{position:absolute;left:0;right:0;bottom:-40px;padding:0 10px}.art-video-player:has(>.artplayer-plugin-danmuku){margin-bottom:40px}[data-danmuku-emitter=false] .apd-emitter{display:none!important}[data-danmuku-emitter=false] .art-controls-center .artplayer-plugin-danmuku{justify-content:flex-end;gap:18px}[data-danmuku-emitter=false].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,[data-danmuku-emitter=false].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku{gap:24px}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-icon{fill:#333}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-emitter{background-color:#f1f2f3}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input{color:#000}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input::placeholder{color:#0000004d}[data-danmuku-visible=false] .apd-toggle-off{display:block}[data-danmuku-visible=false] .apd-toggle-on,[data-danmuku-visible=true] .apd-toggle-off{display:none}[data-danmuku-visible=true] .apd-toggle-on{display:block}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-on{display:none}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-off,[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-on{display:block}[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-off,[data-danmuku-sync-video=false] .apd-sync-video .apd-check-on{display:none}[data-danmuku-sync-video=false] .apd-sync-video .apd-check-off,[data-danmuku-sync-video=true] .apd-sync-video .apd-check-on{display:block}[data-danmuku-sync-video=true] .apd-sync-video .apd-check-off{display:none}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-off{display:block}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-on{display:none}[data-danmuku-mode0=false] .art-danmuku [data-mode="0"]{opacity:0!important}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-off{display:none}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-on{display:block}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"]{color:#00a1d6}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"] path{fill:#00a1d6}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-off{display:block}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-on{display:none}[data-danmuku-mode1=false] .art-danmuku [data-mode="1"]{opacity:0!important}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-off{display:none}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-on{display:block}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"]{color:#00a1d6}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"] path{fill:#00a1d6}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-off{display:block}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-on{display:none}[data-danmuku-mode2=false] .art-danmuku [data-mode="2"]{opacity:0!important}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-off{display:none}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-on{display:block}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"]{color:#00a1d6}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"] path{fill:#00a1d6}'}function b(t){return e=>{const i=new r(e,t),a=new w(e,i);return i.option.heatmap&&d(e,i,i.option.heatmap),{name:"artplayerPluginDanmuku",emit:i.emit.bind(i),load:i.load.bind(i),config:i.config.bind(i),hide:i.hide.bind(i),show:i.show.bind(i),reset:i.reset.bind(i),mount:a.mount.bind(a),get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return b.icons=w.icons,b}); +!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDanmuku=e(),define(function(){return t.artplayerPluginDanmuku})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDanmuku=e()}(this,function(){"use strict";function t(t){return t instanceof Promise||"[object Promise]"===Object.prototype.toString.call(t)}function e(t){switch(t){case 1:case 2:case 3:default:return 0;case 4:return 2;case 5:return 1}}function i(t){if("string"!==typeof t)return[];const i=t.matchAll(/]*?p="(?

[^"]+)"[^>]*>(?.*?)<\/d>/gs);return Array.from(i).map(t=>{const i=t.groups.p.split(",");if(i.length>=8){return{text:t.groups.text.trim().replaceAll(""",'"').replaceAll("'","'").replaceAll("<","<").replaceAll(">",">").replaceAll("&","&"),time:Number(i[0]),mode:e(Number(i[1])),fontSize:Number(i[2]),color:`#${Number(i[3]).toString(16)}`,timestamp:Number(i[4]),pool:Number(i[5]),userID:i[6],rowID:Number(i[7])}}return null}).filter(Boolean)}function a({data:t}){const{xml:e,id:a}=t,n=i(e);globalThis.postMessage({danmus:n,id:a})}let n=0;function s(t,{signal:s,onCancel:o}={}){return new Promise((r,l)=>{let d,p,h,u=!1;const c=++n,m=()=>g(void 0,function(t){if(void 0!==t?.reason)return t.reason;const e=new Error("Bilibili Danmu loading cancelled");return e.name="AbortError",e}(s));function f(){let t,e=!1;try{if(d){const t=d;d=void 0,t.onmessage=null,t.onerror=null,t.onmessageerror=null,t.terminate()}}catch(i){t=i,e=!0}if(void 0!==p){const a=p;p=void 0;try{URL.revokeObjectURL(a)}catch(i){e||(t=i),e=!0}}if(e)throw t}function g(t,e){if(u)return;u=!0;let i=arguments.length>1;s?.removeEventListener("abort",m),h?.();try{f()}catch(a){i||(e=a),i=!0}i?l(e):r(t)}function y(t,e){if(!u){console.error("Error parsing Bilibili Danmu:",e);try{g(i(t))}catch(a){g(void 0,a)}}}s?.aborted?m():(s?.addEventListener("abort",m),h=o?.(m),u?h?.():async function(){const n=s?await fetch(t,{signal:s}):await fetch(t);if(u)return;const o=await n.text();if(!u)try{const t=`\n ${e.toString()}\n ${i.toString()}\n onmessage = ${a.toString()}\n `;if(p=URL.createObjectURL(new Blob([t],{type:"application/javascript"})),u)return void f();if(d=new Worker(p),u)return void f();d.onmessage=t=>{if(u||t.data?.id!==c)return;const{danmus:e}=t.data;Array.isArray(e)?g(e):g(void 0,new Error("Invalid Bilibili Danmu worker response"))},d.onerror=t=>{t.preventDefault?.(),y(o,t.error||t)},d.onmessageerror=t=>y(o,t.error||t),d.postMessage({xml:o,id:c})}catch(r){y(o,r)}}().catch(t=>g(void 0,t)))})}const o=new WeakMap,r=Symbol("cancelled danmuku input");function l(t){return o.has(t)||o.set(t,{closed:!1,tasks:new Set,emitting:[]}),o.get(t)}function d(t){const e=l(t),i=e.emitting[e.emitting.length-1];return!e.closed&&!t.art.isDestroy&&(!i||i.active())}function p(t){const e=l(t);e.closed=!0;for(const i of[...e.tasks])i.cancel()}const h='/*!\n * artplayer-plugin-danmuku.js v5.3.0\n * Github: https://github.com/zhw2590582/ArtPlayer\n * (c) 2017-2026 Harvey Zhao\n * Released under the MIT License.\n */\nfunction t({target:t,visibles:e,clientWidth:i,clientHeight:h,marginBottom:n,marginTop:o,antiOverlap:s}){const r=h-n,p=e.filter(e=>e.mode===t.mode&&e.top<=r).sort((t,e)=>t.top-e.top);if(0===p.length)return 2===t.mode?r-t.height:o;if(p.unshift({type:"top",top:0,left:0,right:0,height:o,width:i,speed:0,distance:i}),p.push({type:"bottom",top:r,left:0,right:0,height:n,width:i,speed:0,distance:i}),2===t.mode)for(let g=p.length-2;g>=0;g-=1){const e=p[g],i=p[g+1],h=e.top+e.height;if(i.top-h>=t.height)return i.top-t.height}else for(let g=1;g=t.height)return h}const a=[];for(let g=1;g{const i=Math.min(...e.map(t=>t.right)),h=Math.min(...t.map(t=>t.right));return i*e.length-h*t.length});break;case 1:case 2:a.sort((t,e)=>{const i=Math.max(...e.map(t=>t.width));return Math.max(...t.map(t=>t.width))*t.length-i*e.length})}return a[0][0].top}switch(t.mode){case 0:{const e=a.find(e=>e.every(e=>{if(ie.time}));return e&&e[0]?e[0].top:void 0}case 1:case 2:return}}onmessage=e=>{const{data:i}=e;if(!i.id||!i.type)return;const h=(0,{getDanmuTop:t}[i.type])(i);globalThis.postMessage({result:h,id:i.id})};\n',u="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",h],{type:"text/javascript;charset=utf-8"});function c(t){let e;try{if(e=u&&(self.URL||self.webkitURL).createObjectURL(u),!e)throw"";const i=new Worker(e,{type:"module",name:t?.name});return i.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(e)}),i}catch(i){return new Worker("data:text/javascript;charset=utf-8,"+encodeURIComponent(h),{type:"module",name:t?.name})}}class m{constructor(t,e){const{constructor:i,template:a}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=a.$danmuku,this.$player=a.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.worker=null,this.option=m.option,this.states={wait:[],ready:[],emit:[],stop:[]},this.start=this.start.bind(this),this.stop=this.stop.bind(this),this.reset=this.reset.bind(this),this.resize=this.resize.bind(this),this.destroy=this.destroy.bind(this),d(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!d(this))return;this.worker=new c}catch(n){throw t.off("destroy",this.destroy),p(this),n}t.on("video:play",this.start),t.on("video:playing",this.start),t.on("video:pause",this.stop),t.on("video:waiting",this.stop),t.on("resize",this.resize),this.load().catch(t=>console.warn("Failed to load initial danmuku:",t))}static get option(){return{danmuku:[],speed:5,margin:[10,"25%"],opacity:1,color:"#FFFFFF",mode:0,modes:[0,1,2],fontSize:25,antiOverlap:!0,synchronousPlayback:!1,mount:void 0,heatmap:!1,width:512,points:[],filter:()=>!0,beforeEmit:()=>!0,beforeVisible:()=>!0,visible:!0,emitter:!0,maxLength:200,lockTime:5,theme:"dark",OPACITY:{},FONT_SIZE:{},MARGIN:{},SPEED:{},COLOR:[]}}static get scheme(){return{danmuku:"array|function|string|promise",speed:"number",margin:"array",opacity:"number",color:"string",mode:"number",modes:"array",fontSize:"number|string",antiOverlap:"boolean",synchronousPlayback:"boolean",mount:"?htmldivelement|string",heatmap:"object|boolean",width:"number",points:"array",filter:"function",beforeEmit:"function",beforeVisible:"function",visible:"boolean",emitter:"boolean",maxLength:"number",lockTime:"number",theme:"string",OPACITY:"object",FONT_SIZE:"object",MARGIN:"object",SPEED:"object",COLOR:"array"}}static get cssText(){return'\n user-select: none;\n position: absolute;\n white-space: pre;\n pointer-events: none;\n perspective: 500px;\n display: inline-block;\n will-change: transform;\n font-weight: normal;\n line-height: 1.125;\n visibility: hidden;\n font-family: SimHei, "Microsoft JhengHei", Arial, Helvetica, sans-serif;\n text-shadow: rgb(0, 0, 0) 1px 0px 1px, rgb(0, 0, 0) 0px 1px 1px, rgb(0, 0, 0) 0px -1px 1px, rgb(0, 0, 0) -1px 0px 1px;\n '}get isRotate(){return this.art.plugins?.autoOrientation?.state}get marginTop(){const{clamp:t}=this.utils,e=this.option.margin[0],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return m.option.margin[0]}get marginBottom(){const{clamp:t}=this.utils,e=this.option.margin[1],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return m.option.margin[1]}get fontSize(){const{clamp:t}=this.utils,{clientHeight:e}=this.$player,i=this.option.fontSize;if("number"===typeof i)return Math.round(t(i,12,e));if("string"===typeof i&&i.endsWith("%")){const a=Number.parseFloat(i)/100;return Math.round(t(e*a,12,e))}return m.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=m.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){const{currentTime:t}=this.art,e=[];return this.filter("ready",t=>e.push(t)),this.filter("wait",i=>{t+.1>=i.time&&i.time>=t-.1&&e.push(i)}),e}get visibles(){const t=[],{clientWidth:e}=this.$player,i=this.getLeft(this.$player);return this.filter("emit",a=>{const n=a.$ref.offsetTop,s=this.getLeft(a.$ref)-i,o=a.$ref.clientHeight,r=a.$ref.clientWidth,l=s+r,d=e-l,p=l/a.$restTime,h={};h.top=n,h.left=s,h.height=o,h.width=r,h.right=d,h.speed=p,h.distance=l,h.time=a.$restTime,h.mode=a.mode,t.push(h)}),t}get speed(){return this.option.synchronousPlayback&&this.art.playbackRate?this.option.speed/Number(this.art.playbackRate):this.option.speed}async load(e){const{errorHandle:i}=this.utils,a=function(t,e){const i=l(t);if(e)for(const r of[...i.tasks])r.replace&&r.cancel();let a,n=i.closed||t.art.isDestroy;const s=new Promise(t=>a=t),o=new Set,d="function"===typeof AbortController?new AbortController:void 0,p={replace:e,signal:d?.signal,active:()=>!n&&!i.closed&&!t.art.isDestroy,onCancel:t=>p.active()?(o.add(t),()=>o.delete(t)):(t(),()=>{}),cancel(){if(!n){n=!0,a(r),d?.abort();for(const t of[...o])t();o.clear()}},wait:t=>Promise.race([t,s]),emit(e){i.emitting.push(p);try{return t.emit(e)}finally{i.emitting.pop()}},finish(){i.tasks.delete(p),o.clear()}};return n?a(r):i.tasks.add(p),p}(this,void 0===e);try{if(!a.active())return this;const n=function(e,i){return"function"===typeof e?{asynchronous:!0,value:e()}:t(e)?{asynchronous:!0,value:e}:"string"===typeof e?{asynchronous:!0,value:s(e,i)}:{asynchronous:!1,value:Array.isArray(e)?e:[]}}(e||this.option.danmuku,a),o=n.asynchronous?await a.wait(n.value):n.value;if(!a.active())return this;if(i(Array.isArray(o),"Danmuku need return an array as result"),void 0===e){if(this.reset(),!a.active())return this;this.queue=[],this.states={wait:[],ready:[],emit:[],stop:[]},this.$refs=[],this.$danmuku.textContent=""}for(let t=0;t{const i=s[e],a=o[e];return"function"===typeof i||"function"===typeof a||t(i)||t(a)?i!==a:JSON.stringify(i)!==JSON.stringify(a)}))&&!i)return this;const r=function(t,e,{defaults:i,validate:a,clamp:n,mount:s}){const o=Object.assign({},i,t,e);return a(o),o.mode=n(o.mode,0,2),o.speed=n(o.speed,1,10),o.opacity=n(o.opacity,0,1),o.lockTime=n(o.lockTime,1,60),o.maxLength=n(o.maxLength,1,1e3),o.mount=o.mount||s,o}(this.option,e,{defaults:m.option,validate:t=>this.validator(t,m.scheme),clamp:a,mount:n});return d(this)?(this.option=r,e.fontSize&&(this.reset(),!d(this))||(this.option.visible?this.show():this.hide(),d(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this):this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return new Promise(e=>{t.id=Date.now(),this.worker.postMessage(t),this.worker.onmessage=i=>{const{data:a}=i;a.id===t.id&&e(a)}})}filter(t,e){const i=this.states[t]||[];for(let a=0;ae!==t),t.$state=e,t.$ref&&(t.$ref.dataset.state=e),this.states[e].push(t)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=m.cssText,t.$ref.style.visibility="hidden",t.$ref.style.marginLeft="0px",t.$ref.style.transform="translateX(0px)",t.$ref.style.transition="transform 0s linear 0s",this.$refs.push(t.$ref),t.$ref=null)}update(){const{setStyles:t}=this.utils;return this.timer=window.requestAnimationFrame(async()=>{if(this.art.playing&&!this.isHide){this.filter("emit",t=>{const e=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=e,t.$lastStartTime=Date.now(),t.$restTime<=0&&this.makeWait(t)});const e=this.readys;for(let i=0;i{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.filter("emit",e=>{switch(e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.left=`${t}px`,e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}})}continue(){const{clientWidth:t}=this.$player;return this.filter("stop",e=>{switch(this.setState(e,"emit"),e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}}),this}suspend(){const{clientWidth:t}=this.$player;return this.filter("emit",e=>{switch(this.setState(e,"stop"),e.mode){case 0:{const i=t-(this.getLeft(e.$ref)-this.getLeft(this.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}stop(){return this.isStop=!0,this.suspend(),window.cancelAnimationFrame(this.timer),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){return this.isStop=!1,this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start"),this}reset(){return this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this}show(){return this.isHide=!1,this.$danmuku.style.opacity=1,this.option.visible=!0,this.art.emit("artplayerPluginDanmuku:show"),this}hide(){return this.isHide=!0,this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this}destroy(){p(this),this.stop(),this.worker?.terminate(),this.art.off("video:play",this.start),this.art.off("video:playing",this.start),this.art.off("video:pause",this.stop),this.art.off("video:waiting",this.stop),this.art.off("resize",this.resize),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}const f={map:(t,e,i,a,n)=>(t-e)*(n-a)/(i-e)+a,range(t,e,i){const a=Math.round(t/i)*i;return Array.from({length:Math.floor((e-t)/i)},(t,e)=>e*i+a)}};function g(t,e,i){const{query:a}=t.constructor.utils;t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},mounted(n){let s=null,o=null;function r(r=[]){if(s=null,o=null,n.innerHTML="",!t.duration||t.option.isLive)return;const l={w:n.offsetWidth,h:n.offsetHeight},d={xMin:0,xMax:l.w,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*l.h),sampling:Math.floor(l.w/100),smoothing:.2,flattening:.2};"object"===typeof i&&Object.assign(d,i);let p=[];if(Array.isArray(r)&&r.length)p=[...r];else{const i=t.duration/l.w;for(let t=0;t<=l.w;t+=d.sampling){const a=e.queue.filter(({time:e})=>e>t*i&&e<=(t+d.sampling)*i).length;p.push([t,a])}}if(0===p.length)return;const h=p[p.length-1],u=h[0],c=h[1];u!==l.w&&p.push([l.w,c]);const m=p.map(t=>t[1]),g=(Math.min(...m)+Math.max(...m))/2;for(let t=0;tg?1+d.scale:1-d.scale)+d.minHeight}const y=(t,e,i,a)=>{const n=function(t,e){const i=e[0]-t[0],a=e[1]-t[1];return{length:Math.sqrt(i**2+a**2),angle:Math.atan2(a,i)}}(e||t,i||t),s=f.map(Math.cos(n.angle)*d.flattening,0,1,1,0),o=n.angle*s+(a?Math.PI:0),r=n.length*d.smoothing;return[t[0]+Math.cos(o)*r,t[1]+Math.sin(o)*r]},v=p.map(t=>[f.map(t[0],d.xMin,d.xMax,0,l.w),f.map(t[1],d.yMin,d.yMax,l.h,0)]).reduce((t,e,i,a)=>0===i?`M ${a[a.length-1][0]},${l.h} L ${e[0]},${l.h} L ${e[0]},${e[1]}`:`${t} ${((t,e,i)=>{const a=y(i[e-1],i[e-2],t),n=y(t,i[e-1],i[e+1],!0),s=e===i.length-1?" z":"";return`C ${a[0]},${a[1]} ${n[0]},${n[1]} ${t[0]},${t[1]}${s}`})(e,i,a)}`,"");n.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,s=a("#heatmap-start",n),o=a("#heatmap-stop",n),s.setAttribute("offset",100*t.played+"%"),o.setAttribute("offset",100*t.played+"%")}t.on("video:timeupdate",()=>{s&&o&&(s.setAttribute("offset",100*t.played+"%"),o.setAttribute("offset",100*t.played+"%"))}),t.on("setBar",(t,e)=>{s&&o&&"played"===t&&(s.setAttribute("offset",100*e+"%"),o.setAttribute("offset",100*e+"%"))}),t.on("ready",()=>r()),t.on("resize",()=>r()),t.on("artplayerPluginDanmuku:loaded",()=>r()),t.on("artplayerPluginDanmuku:points",t=>r(t))}})}const y='',v='',k='',$='',w='',x='',b='',S='',M='',z='',E='',C='';class T{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:a}=t.template;i(a,"display","flex"),this.template={$controlsCenter:a,$mount:a,$danmuku:null,$toggle:null,$config:null,$configPanel:null,$configModes:null,$style:null,$stylePanel:null,$styleModes:null,$colors:null,$opacitySlider:null,$opacityValue:null,$marginSlider:null,$marginValue:null,$fontSizeSlider:null,$fontSizeValue:null,$speedSlider:null,$speedValue:null,$input:null,$send:null},this.slider={opacity:null,margin:null,fontSize:null,speed:null},this.emitting=!1,this.isLock=!1,this.timer=null,this.createTemplate(),this.createSliders(),this.createEvents(),this.mount(this.option.mount),t.on("resize",()=>this.resize()),t.on("fullscreen",t=>this.onFullscreen(t)),t.on("fullscreenWeb",t=>this.onFullscreen(t)),t.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),t.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}static get icons(){return{$on:E,$off:z,$config:k,$style:C,$mode_0_off:$,$mode_0_on:w,$mode_1_off:x,$mode_1_on:b,$mode_2_off:S,$mode_2_on:M,$check_on:v,$check_off:y}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){const{option:t}=this;return`\n

\n ${E}${z}\n
\n
\n ${k}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${$}${w}\n
\u6eda\u52a8
\n
\n
\n ${x}${b}\n
\u9876\u90e8
\n
\n
\n ${S}${M}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${v}${y}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${v}${y}\n \u540c\u6b65\u89c6\u9891\u901f\u5ea6\n
\n
\n
\n \u4e0d\u900f\u660e\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n \u663e\u793a\u533a\u57df\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u5b57\u53f7\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u901f\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n
\n
\n
\n
\n ${C}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${w}\n
\u6eda\u52a8
\n
\n
\n ${b}\n
\u9876\u90e8
\n
\n
\n ${M}\n
\u5e95\u90e8
\n
\n
\n
\n
\n \u989c\u8272\n
\n ${this.COLOR.map(t=>`
`).join("")}\n
\n
\n
\n
\n
\n \n
\u53d1\u9001
\n
\n `}get OPACITY(){return{min:0,max:100,steps:[],...this.option.OPACITY}}get FONT_SIZE(){return{min:12,max:120,steps:[],...this.option.FONT_SIZE}}get MARGIN(){return{min:0,max:3,steps:[{name:"1/4",value:[10,"75%"]},{name:"\u534a\u5c4f",value:[10,"50%"]},{name:"3/4",value:[10,"25%"]},{name:"\u6ee1\u5c4f",value:[10,10]}],...this.option.MARGIN}}get SPEED(){return{min:0,max:4,steps:[{name:"\u6781\u6162",value:10},{name:"\u8f83\u6162",value:7.5,hide:!0},{name:"\u9002\u4e2d",value:5},{name:"\u8f83\u5feb",value:2.5,hide:!0},{name:"\u6781\u5feb",value:1}],...this.option.SPEED}}get COLOR(){return this.option.COLOR.length?this.option.COLOR:["#FE0302","#FF7204","#FFAA02","#FFD302","#FFFF00","#A0EE00","#00CD00","#019899","#4266BE","#89D5FF","#CC0273","#222222","#9B9B9B","#FFFFFF"]}query(t){const{query:e}=this.utils,{$danmuku:i}=this.template;return e(t,i)}append(t,e){const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){const{$player:i}=this.art.template,{$mount:a}=this.template;i.dataset[t]=e,this.outside&&(a.dataset[t]=e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,this.template.$danmuku=i,this.template.$toggle=this.query(".apd-toggle"),this.template.$config=this.query(".apd-config"),this.template.$configPanel=this.query(".apd-config-panel"),this.template.$configModes=this.query(".apd-config-mode .apd-modes"),this.template.$style=this.query(".apd-style"),this.template.$stylePanel=this.query(".apd-style-panel"),this.template.$styleModes=this.query(".apd-style-mode .apd-modes"),this.template.$colors=this.query(".apd-colors"),this.template.$antiOverlap=this.query(".apd-anti-overlap"),this.template.$syncVideo=this.query(".apd-sync-video"),this.template.$opacitySlider=this.query(".apd-config-opacity .apd-slider"),this.template.$opacityValue=this.query(".apd-config-opacity .apd-value"),this.template.$marginSlider=this.query(".apd-config-margin .apd-slider"),this.template.$marginValue=this.query(".apd-config-margin .apd-value"),this.template.$fontSizeSlider=this.query(".apd-config-fontSize .apd-slider"),this.template.$fontSizeValue=this.query(".apd-config-fontSize .apd-value"),this.template.$speedSlider=this.query(".apd-config-speed .apd-slider"),this.template.$speedValue=this.query(".apd-config-speed .apd-value"),this.template.$input=this.query(".apd-input"),this.template.$send=this.query(".apd-send");const{$toggle:a}=this.template;this.art.on("artplayerPluginDanmuku:show",()=>{e(a,"\u5173\u95ed\u5f39\u5e55")}),this.art.on("artplayerPluginDanmuku:hide",()=>{e(a,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:a,$antiOverlap:n,$syncVideo:s,$send:o,$input:r}=this.template;this.art.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.art.proxy(e,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.option.modes.includes(i)?this.danmuku.config({modes:this.option.modes.filter(t=>t!==i)}):this.danmuku.config({modes:[...this.option.modes,i]}),this.reset()}),this.art.proxy(n,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.art.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.art.proxy(i,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.danmuku.config({mode:i}),this.reset()}),this.art.proxy(a,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.art.proxy(o,"click",()=>this.emit()),this.art.proxy(r,"keypress",t=>{"Enter"===t.key&&(t.preventDefault(),this.emit())})}createSliders(){this.slider.opacity=this.createSlider({...this.OPACITY,container:this.template.$opacitySlider,findIndex:()=>Math.round(100*this.option.opacity),onChange:t=>{const{$opacityValue:e}=this.template;e.textContent=`${t}%`,this.danmuku.config({opacity:t/100})}}),this.slider.margin=this.createSlider({...this.MARGIN,container:this.template.$marginSlider,findIndex:()=>this.MARGIN.steps.findIndex(t=>t.value[0]===this.option.margin[0]&&t.value[1]===this.option.margin[1]),onChange:t=>{const e=this.MARGIN.steps[t];if(!e)return;const{$marginValue:i}=this.template;i.textContent=e.name,this.danmuku.config({margin:e.value})}}),this.slider.fontSize=this.createSlider({...this.FONT_SIZE,container:this.template.$fontSizeSlider,findIndex:()=>this.danmuku.fontSize,onChange:t=>{const{$fontSizeValue:e}=this.template;e.textContent=`${t}px`,t!==this.danmuku.fontSize&&this.danmuku.config({fontSize:t})}}),this.slider.speed=this.createSlider({...this.SPEED,container:this.template.$speedSlider,findIndex:()=>this.SPEED.steps.findIndex(t=>t.value===this.option.speed),onChange:t=>{const e=this.SPEED.steps[t];if(!e)return;const{$speedValue:i}=this.template;i.textContent=e.name,this.danmuku.config({speed:e.value})}})}createSlider({min:t,max:e,container:i,findIndex:a,onChange:n,steps:s=[]}){const{query:o,clamp:r,setStyle:l}=this.utils;l(i,"touch-action","none"),i.innerHTML=`\n
\n
\n ${s.map(()=>'
').join("")}\n
\n
\n
\n
\n
\n ${s.map(t=>t.hide?"":`
${t.name}
`).join("")}\n
\n `;const d=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i=a()){if(ie)return;const o=(i-t)/(e-t);d.style.left=100*o+"%",0===s.length&&(p.style.width=d.style.left),n(i)}function c(a){const{top:n,height:s,left:o,width:l}=i.getBoundingClientRect();if(this.art.isRotate){const i=r(a.clientY-n,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=r(a.clientX-o,0,l);u(Math.round(i/l*(e-t)+t))}}return this.art.proxy(i,"click",t=>{c.call(this,t)}),this.art.proxy(i,"pointerdown",t=>{h=0===t.button}),this.art.on("document:pointermove",t=>{h&&c.call(this,t)}),this.art.on("document:pointerup",t=>{h&&(h=!1,c.call(this,t))}),{reset:u}}onFullscreen(t){const{$danmuku:e,$controlsCenter:i,$mount:a}=this.template;this.outside?t?this.append(i,e):this.append(a,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){const{$player:i}=this.art.template,a=t.getBoundingClientRect(),n=e.getBoundingClientRect(),s=i.getBoundingClientRect(),o=n.width/2-a.width/2,r=s.left-(a.left-o),l=a.right+o-s.right;e.style.left=r>0?`${-o+r}px`:l>0?-o-l+"px":-o+"px"}async emit(){const{$input:t}=this.template,e=t.value.trim();if(!e.length)return;if(this.isLock)return;if(this.emitting)return;const i={text:e,mode:this.option.mode,color:this.option.color,time:this.art.currentTime};try{this.emitting=!0;const e=await this.option.beforeEmit(i);if(this.emitting=!1,!0!==e)return;i.border=!0,delete i.time,this.danmuku.emit(i),t.value="",this.lock()}catch(a){console.error("Error emitting danmuku:",a),this.emitting=!1}}lock(){const{addClass:t}=this.utils,{$send:e}=this.template;this.isLock=!0;let i=this.option.lockTime;e.textContent=i,t(e,"apd-lock");const a=()=>{this.timer=setTimeout(()=>{0===i?this.unlock():(i-=1,e.textContent=i,a())},1e3)};a()}unlock(){const{removeClass:t}=this.utils,{$send:e}=this.template;clearTimeout(this.timer),this.isLock=!1,e.textContent="\u53d1\u9001",t(e,"apd-lock")}resize(){if(this.outside)return;if(this.art.fullscreen)return;if(this.art.fullscreenWeb)return;const{$player:t,$controlsCenter:e}=this.art.template,{$danmuku:i}=this.template;this.art.widtht.dataset.color===this.option.color.toUpperCase());s&&t(s,"apd-active"),e(i,this.option.visible?"\u5173\u95ed\u5f39\u5e55":"\u6253\u5f00\u5f39\u5e55"),this.resize()}mount(t){const{errorHandle:e}=this.utils,i="string"===typeof t?document.querySelector(t):t;e(i,`Can not find the mount point: ${t}`),this.append(i,this.template.$danmuku),this.template.$mount=i,this.reset()}}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku";let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(e)}):(document.head||document.documentElement).appendChild(e)),e.textContent='.artplayer-plugin-danmuku{display:flex;position:relative;z-index:99;align-items:center;justify-content:center;font-size:12px;height:32px;width:100%;color:#fff;font-weight:300;flex-shrink:0;gap:10px}.artplayer-plugin-danmuku .apd-icon{cursor:pointer;opacity:.75;transition:all .2s ease;fill:#fff}.artplayer-plugin-danmuku .apd-icon:hover{opacity:1}.artplayer-plugin-danmuku .apd-config{display:flex;position:relative}.artplayer-plugin-danmuku .apd-config .apd-config-panel{position:absolute;bottom:24px;left:0;width:320px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-config-mode,.artplayer-plugin-danmuku .apd-config-slider,.artplayer-plugin-danmuku .apd-config-other,.artplayer-plugin-danmuku .apd-style-mode{margin-bottom:15px}.artplayer-plugin-danmuku .apd-modes{display:flex;align-items:center;margin-top:5px;gap:20px}.artplayer-plugin-danmuku .apd-modes .apd-mode{cursor:pointer;text-align:center}.artplayer-plugin-danmuku .apd-modes .apd-mode:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-config-slider{display:flex;align-items:center;gap:12px}.artplayer-plugin-danmuku .apd-config-slider .apd-value{width:32px;text-align:right}.artplayer-plugin-danmuku .apd-slider{position:relative;flex:1;display:flex;height:20px;align-items:center;justify-content:center;cursor:pointer}.artplayer-plugin-danmuku .apd-slider .apd-slider-line{position:relative;height:2px;width:100%;overflow:hidden;border-radius:3px;background-color:#ffffff40}.artplayer-plugin-danmuku .apd-slider .apd-slider-points{position:absolute;inset:0;display:flex;align-items:center;justify-content:space-between}.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point{width:2px;height:2px;border-radius:50%;background-color:#ffffff80}.artplayer-plugin-danmuku .apd-slider .apd-slider-progress{width:0%;height:100%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-dot{position:absolute;transform:translate(-6px);left:0%;width:12px;height:12px;border-radius:50%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps{display:flex;align-items:center;justify-content:space-between;position:absolute;bottom:-12px;width:calc(100% + 32px);color:#777}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step{flex-shrink:0;width:36px;text-align:center;scale:.95}.artplayer-plugin-danmuku .apd-config-other{display:flex;align-items:center;gap:20px}.artplayer-plugin-danmuku .apd-config-other .apd-check-off,.artplayer-plugin-danmuku .apd-config-other .apd-check-on{width:16px;height:16px}.artplayer-plugin-danmuku .apd-config-other .apd-other{display:flex;align-items:center;cursor:pointer;gap:2px}.artplayer-plugin-danmuku .apd-config-other .apd-other:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-emitter{display:flex;flex:1;align-items:center;height:100%;background-color:#ffffff40;border-radius:5px}.artplayer-plugin-danmuku .apd-style{position:relative;display:flex;align-items:center;justify-content:center}.artplayer-plugin-danmuku .apd-style .apd-style-panel{position:absolute;bottom:24px;left:0;width:200px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-colors{display:flex;flex-wrap:wrap;margin-top:5px;gap:8px}.artplayer-plugin-danmuku .apd-colors .apd-color{width:16px;height:16px;border-radius:2px;cursor:pointer}.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active{border:1px solid black;box-shadow:0 0 0 1px #fff}.artplayer-plugin-danmuku .apd-input{outline:none;height:100%;flex:1;min-width:0;width:auto;border:none;line-height:1;color:#fff;background-color:transparent}.artplayer-plugin-danmuku .apd-input::placeholder{color:#ffffff80}.artplayer-plugin-danmuku .apd-send{display:flex;align-items:center;justify-content:center;height:100%;width:60px;flex-shrink:0;cursor:pointer;text-shadow:none;border-top-right-radius:5px;border-bottom-right-radius:5px;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-send.apd-lock{cursor:not-allowed;color:#666;background-color:#e7e7e7}.art-controls-center .apd-emitter{width:260px;flex:none}.art-fullscreen .artplayer-plugin-danmuku,.art-fullscreen-web .artplayer-plugin-danmuku{height:38px;gap:16px}.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on{width:28px;height:28px}.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter{width:400px;flex:none}.art-video-player>.artplayer-plugin-danmuku{position:absolute;left:0;right:0;bottom:-40px;padding:0 10px}.art-video-player:has(>.artplayer-plugin-danmuku){margin-bottom:40px}[data-danmuku-emitter=false] .apd-emitter{display:none!important}[data-danmuku-emitter=false] .art-controls-center .artplayer-plugin-danmuku{justify-content:flex-end;gap:18px}[data-danmuku-emitter=false].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,[data-danmuku-emitter=false].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku{gap:24px}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-icon{fill:#333}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-emitter{background-color:#f1f2f3}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input{color:#000}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input::placeholder{color:#0000004d}[data-danmuku-visible=false] .apd-toggle-off{display:block}[data-danmuku-visible=false] .apd-toggle-on,[data-danmuku-visible=true] .apd-toggle-off{display:none}[data-danmuku-visible=true] .apd-toggle-on{display:block}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-on{display:none}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-off,[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-on{display:block}[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-off,[data-danmuku-sync-video=false] .apd-sync-video .apd-check-on{display:none}[data-danmuku-sync-video=false] .apd-sync-video .apd-check-off,[data-danmuku-sync-video=true] .apd-sync-video .apd-check-on{display:block}[data-danmuku-sync-video=true] .apd-sync-video .apd-check-off{display:none}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-off{display:block}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-on{display:none}[data-danmuku-mode0=false] .art-danmuku [data-mode="0"]{opacity:0!important}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-off{display:none}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-on{display:block}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"]{color:#00a1d6}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"] path{fill:#00a1d6}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-off{display:block}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-on{display:none}[data-danmuku-mode1=false] .art-danmuku [data-mode="1"]{opacity:0!important}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-off{display:none}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-on{display:block}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"]{color:#00a1d6}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"] path{fill:#00a1d6}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-off{display:block}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-on{display:none}[data-danmuku-mode2=false] .art-danmuku [data-mode="2"]{opacity:0!important}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-off{display:none}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-on{display:block}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"]{color:#00a1d6}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"] path{fill:#00a1d6}'}function F(t){return e=>{const i=new m(e,t),a=e.isDestroy?void 0:new T(e,i);return!e.isDestroy&&i.option.heatmap&&g(e,i,i.option.heatmap),{name:"artplayerPluginDanmuku",emit:i.emit.bind(i),load:i.load.bind(i),config:i.config.bind(i),hide:i.hide.bind(i),show:i.show.bind(i),reset:i.reset.bind(i),mount:a?a.mount.bind(a):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return F.icons=T.icons,F}); diff --git a/docs/compiled/artplayer-plugin-danmuku.legacy.js b/docs/compiled/artplayer-plugin-danmuku.legacy.js index 83a1130ab..db0f66315 100644 --- a/docs/compiled/artplayer-plugin-danmuku.legacy.js +++ b/docs/compiled/artplayer-plugin-danmuku.legacy.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDanmuku=e(),define(function(){return t.artplayerPluginDanmuku})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDanmuku=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.defineProperties,i=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,o=Math.pow,r=(e,i,a)=>i in e?t(e,i,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[i]=a,l=(t,e)=>{for(var i in e||(e={}))n.call(e,i)&&r(t,i,e[i]);if(a)for(var i of a(e))s.call(e,i)&&r(t,i,e[i]);return t},d=(t,a)=>e(t,i(a)),p=(t,e,i)=>new Promise((a,n)=>{var s=t=>{try{r(i.next(t))}catch(e){n(e)}},o=t=>{try{r(i.throw(t))}catch(e){n(e)}},r=t=>t.done?a(t.value):Promise.resolve(t.value).then(s,o);r((i=i.apply(t,e)).next())});function h(t){switch(t){case 1:case 2:case 3:default:return 0;case 4:return 2;case 5:return 1}}function u(t){if("string"!==typeof t)return[];const e=new RegExp(']*?p="(?

[^"]+)"[^>]*>(?.*?)<\\/d>',"gs"),i=t.matchAll(e);return Array.from(i).map(t=>{const e=t.groups.p.split(",");if(e.length>=8){return{text:t.groups.text.trim().replaceAll(""",'"').replaceAll("'","'").replaceAll("<","<").replaceAll(">",">").replaceAll("&","&"),time:Number(e[0]),mode:h(Number(e[1])),fontSize:Number(e[2]),color:`#${Number(e[3]).toString(16)}`,timestamp:Number(e[4]),pool:Number(e[5]),userID:e[6],rowID:Number(e[7])}}return null}).filter(Boolean)}function c({data:t}){const{xml:e,id:i}=t;if(!i||!e)return;const a=u(e);globalThis.postMessage({danmus:a,id:i})}function m(){const t=`\n ${h.toString()}\n ${u.toString()}\n onmessage = ${c.toString()}\n `,e=new Blob([t],{type:"application/javascript"});return new Worker(URL.createObjectURL(e))}const f='/*!\n * artplayer-plugin-danmuku.js v5.3.0\n * Github: https://github.com/zhw2590582/ArtPlayer\n * (c) 2017-2026 Harvey Zhao\n * Released under the MIT License.\n */\nfunction t({target:t,visibles:e,clientWidth:i,clientHeight:h,marginBottom:n,marginTop:o,antiOverlap:s}){const r=h-n,p=e.filter(e=>e.mode===t.mode&&e.top<=r).sort((t,e)=>t.top-e.top);if(0===p.length)return 2===t.mode?r-t.height:o;if(p.unshift({type:"top",top:0,left:0,right:0,height:o,width:i,speed:0,distance:i}),p.push({type:"bottom",top:r,left:0,right:0,height:n,width:i,speed:0,distance:i}),2===t.mode)for(let g=p.length-2;g>=0;g-=1){const e=p[g],i=p[g+1],h=e.top+e.height;if(i.top-h>=t.height)return i.top-t.height}else for(let g=1;g=t.height)return h}const a=[];for(let g=1;g{const i=Math.min(...e.map(t=>t.right)),h=Math.min(...t.map(t=>t.right));return i*e.length-h*t.length});break;case 1:case 2:a.sort((t,e)=>{const i=Math.max(...e.map(t=>t.width));return Math.max(...t.map(t=>t.width))*t.length-i*e.length})}return a[0][0].top}switch(t.mode){case 0:{const e=a.find(e=>e.every(e=>{if(ie.time}));return e&&e[0]?e[0].top:void 0}case 1:case 2:return}}onmessage=e=>{const{data:i}=e;if(!i.id||!i.type)return;const h=(0,{getDanmuTop:t}[i.type])(i);globalThis.postMessage({result:h,id:i.id})};\n',g="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",f],{type:"text/javascript;charset=utf-8"});function y(t){let e;try{if(e=g&&(self.URL||self.webkitURL).createObjectURL(g),!e)throw"";const i=new Worker(e,{type:"module",name:null==t?void 0:t.name});return i.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(e)}),i}catch(i){return new Worker("data:text/javascript;charset=utf-8,"+encodeURIComponent(f),{type:"module",name:null==t?void 0:t.name})}}class v{constructor(t,e){const{constructor:i,template:a}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=a.$danmuku,this.$player=a.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.option=v.option,this.states={wait:[],ready:[],emit:[],stop:[]},this.config(e,!0),this.worker=new y,this.start=this.start.bind(this),this.stop=this.stop.bind(this),this.reset=this.reset.bind(this),this.resize=this.resize.bind(this),this.destroy=this.destroy.bind(this),t.on("video:play",this.start),t.on("video:playing",this.start),t.on("video:pause",this.stop),t.on("video:waiting",this.stop),t.on("destroy",this.destroy),t.on("resize",this.resize),this.load()}static get option(){return{danmuku:[],speed:5,margin:[10,"25%"],opacity:1,color:"#FFFFFF",mode:0,modes:[0,1,2],fontSize:25,antiOverlap:!0,synchronousPlayback:!1,mount:void 0,heatmap:!1,width:512,points:[],filter:()=>!0,beforeEmit:()=>!0,beforeVisible:()=>!0,visible:!0,emitter:!0,maxLength:200,lockTime:5,theme:"dark",OPACITY:{},FONT_SIZE:{},MARGIN:{},SPEED:{},COLOR:[]}}static get scheme(){return{danmuku:"array|function|string",speed:"number",margin:"array",opacity:"number",color:"string",mode:"number",modes:"array",fontSize:"number|string",antiOverlap:"boolean",synchronousPlayback:"boolean",mount:"?htmldivelement|string",heatmap:"object|boolean",width:"number",points:"array",filter:"function",beforeEmit:"function",beforeVisible:"function",visible:"boolean",emitter:"boolean",maxLength:"number",lockTime:"number",theme:"string",OPACITY:"object",FONT_SIZE:"object",MARGIN:"object",SPEED:"object",COLOR:"array"}}static get cssText(){return'\n user-select: none;\n position: absolute;\n white-space: pre;\n pointer-events: none;\n perspective: 500px;\n display: inline-block;\n will-change: transform;\n font-weight: normal;\n line-height: 1.125;\n visibility: hidden;\n font-family: SimHei, "Microsoft JhengHei", Arial, Helvetica, sans-serif;\n text-shadow: rgb(0, 0, 0) 1px 0px 1px, rgb(0, 0, 0) 0px 1px 1px, rgb(0, 0, 0) 0px -1px 1px, rgb(0, 0, 0) -1px 0px 1px;\n '}get isRotate(){var t,e;return null==(e=null==(t=this.art.plugins)?void 0:t.autoOrientation)?void 0:e.state}get marginTop(){const{clamp:t}=this.utils,e=this.option.margin[0],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return v.option.margin[0]}get marginBottom(){const{clamp:t}=this.utils,e=this.option.margin[1],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return v.option.margin[1]}get fontSize(){const{clamp:t}=this.utils,{clientHeight:e}=this.$player,i=this.option.fontSize;if("number"===typeof i)return Math.round(t(i,12,e));if("string"===typeof i&&i.endsWith("%")){const a=Number.parseFloat(i)/100;return Math.round(t(e*a,12,e))}return v.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=v.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){const{currentTime:t}=this.art,e=[];return this.filter("ready",t=>e.push(t)),this.filter("wait",i=>{t+.1>=i.time&&i.time>=t-.1&&e.push(i)}),e}get visibles(){const t=[],{clientWidth:e}=this.$player,i=this.getLeft(this.$player);return this.filter("emit",a=>{const n=a.$ref.offsetTop,s=this.getLeft(a.$ref)-i,o=a.$ref.clientHeight,r=a.$ref.clientWidth,l=s+r,d=e-l,p=l/a.$restTime,h={};h.top=n,h.left=s,h.height=o,h.width=r,h.right=d,h.speed=p,h.distance=l,h.time=a.$restTime,h.mode=a.mode,t.push(h)}),t}get speed(){return this.option.synchronousPlayback&&this.art.playbackRate?this.option.speed/Number(this.art.playbackRate):this.option.speed}load(t){return p(this,null,function*(){const{errorHandle:e}=this.utils;let i=[];const a=t||this.option.danmuku;try{"function"===typeof a?i=yield a():a instanceof Promise?i=yield a:"string"===typeof a?i=yield(n=a,new Promise(t=>p(null,null,function*(){const e=yield fetch(n),i=yield e.text();try{const e=m();e.onmessage=i=>{const{danmus:a,id:n}=i.data;n&&a&&(t(a),e.terminate())},e.postMessage({xml:i,id:Date.now()})}catch(a){console.error("Error parsing Bilibili Danmu:",a);const e=u(i);t(e)}}))):Array.isArray(a)&&(i=a),e(Array.isArray(i),"Danmuku need return an array as result"),void 0===t&&(this.reset(),this.queue=[],this.states={wait:[],ready:[],emit:[],stop:[]},this.$refs=[],this.$danmuku.textContent="");for(let t=0;tJSON.stringify(this.option[e])!==JSON.stringify(t[e]))||e?(this.option=Object.assign({},v.option,this.option,t),this.validator(this.option,v.scheme),this.option.mode=i(this.option.mode,0,2),this.option.speed=i(this.option.speed,1,10),this.option.opacity=i(this.option.opacity,0,1),this.option.lockTime=i(this.option.lockTime,1,60),this.option.maxLength=i(this.option.maxLength,1,1e3),this.option.mount=this.option.mount||a,t.fontSize&&this.reset(),this.option.visible?this.show():this.hide(),this.art.emit("artplayerPluginDanmuku:config",this.option),this):this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return new Promise(e=>{t.id=Date.now(),this.worker.postMessage(t),this.worker.onmessage=i=>{const{data:a}=i;a.id===t.id&&e(a)}})}filter(t,e){const i=this.states[t]||[];for(let a=0;ae!==t),t.$state=e,t.$ref&&(t.$ref.dataset.state=e),this.states[e].push(t)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=v.cssText,t.$ref.style.visibility="hidden",t.$ref.style.marginLeft="0px",t.$ref.style.transform="translateX(0px)",t.$ref.style.transition="transform 0s linear 0s",this.$refs.push(t.$ref),t.$ref=null)}update(){const{setStyles:t}=this.utils;return this.timer=window.requestAnimationFrame(()=>p(this,null,function*(){if(this.art.playing&&!this.isHide){this.filter("emit",t=>{const e=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=e,t.$lastStartTime=Date.now(),t.$restTime<=0&&this.makeWait(t)});const e=this.readys;for(let i=0;i{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.filter("emit",e=>{switch(e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.left=`${t}px`,e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}})}continue(){const{clientWidth:t}=this.$player;return this.filter("stop",e=>{switch(this.setState(e,"emit"),e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}}),this}suspend(){const{clientWidth:t}=this.$player;return this.filter("emit",e=>{switch(this.setState(e,"stop"),e.mode){case 0:{const i=t-(this.getLeft(e.$ref)-this.getLeft(this.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}stop(){return this.isStop=!0,this.suspend(),window.cancelAnimationFrame(this.timer),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){return this.isStop=!1,this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start"),this}reset(){return this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this}show(){return this.isHide=!1,this.$danmuku.style.opacity=1,this.option.visible=!0,this.art.emit("artplayerPluginDanmuku:show"),this}hide(){return this.isHide=!0,this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this}destroy(){this.stop(),this.worker.terminate(),this.art.off("video:play",this.start),this.art.off("video:playing",this.start),this.art.off("video:pause",this.stop),this.art.off("video:waiting",this.stop),this.art.off("resize",this.reset),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}const k={map:(t,e,i,a,n)=>(t-e)*(n-a)/(i-e)+a,range(t,e,i){const a=Math.round(t/i)*i;return Array.from({length:Math.floor((e-t)/i)},(t,e)=>e*i+a)}};function x(t,e,i){const{query:a}=t.constructor.utils;t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},mounted(n){let s=null,r=null;function l(l=[]){if(s=null,r=null,n.innerHTML="",!t.duration||t.option.isLive)return;const d={w:n.offsetWidth,h:n.offsetHeight},p={xMin:0,xMax:d.w,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*d.h),sampling:Math.floor(d.w/100),smoothing:.2,flattening:.2};"object"===typeof i&&Object.assign(p,i);let h=[];if(Array.isArray(l)&&l.length)h=[...l];else{const i=t.duration/d.w;for(let t=0;t<=d.w;t+=p.sampling){const a=e.queue.filter(({time:e})=>e>t*i&&e<=(t+p.sampling)*i).length;h.push([t,a])}}if(0===h.length)return;const u=h[h.length-1],c=u[0],m=u[1];c!==d.w&&h.push([d.w,m]);const f=h.map(t=>t[1]),g=(Math.min(...f)+Math.max(...f))/2;for(let t=0;tg?1+p.scale:1-p.scale)+p.minHeight}const y=(t,e,i,a)=>{const n=function(t,e){const i=e[0]-t[0],a=e[1]-t[1];return{length:Math.sqrt(o(i,2)+o(a,2)),angle:Math.atan2(a,i)}}(e||t,i||t),s=k.map(Math.cos(n.angle)*p.flattening,0,1,1,0),r=n.angle*s+(a?Math.PI:0),l=n.length*p.smoothing;return[t[0]+Math.cos(r)*l,t[1]+Math.sin(r)*l]},v=h.map(t=>[k.map(t[0],p.xMin,p.xMax,0,d.w),k.map(t[1],p.yMin,p.yMax,d.h,0)]).reduce((t,e,i,a)=>0===i?`M ${a[a.length-1][0]},${d.h} L ${e[0]},${d.h} L ${e[0]},${e[1]}`:`${t} ${((t,e,i)=>{const a=y(i[e-1],i[e-2],t),n=y(t,i[e-1],i[e+1],!0),s=e===i.length-1?" z":"";return`C ${a[0]},${a[1]} ${n[0]},${n[1]} ${t[0]},${t[1]}${s}`})(e,i,a)}`,"");n.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,s=a("#heatmap-start",n),r=a("#heatmap-stop",n),s.setAttribute("offset",100*t.played+"%"),r.setAttribute("offset",100*t.played+"%")}t.on("video:timeupdate",()=>{s&&r&&(s.setAttribute("offset",100*t.played+"%"),r.setAttribute("offset",100*t.played+"%"))}),t.on("setBar",(t,e)=>{s&&r&&"played"===t&&(s.setAttribute("offset",100*e+"%"),r.setAttribute("offset",100*e+"%"))}),t.on("ready",()=>l()),t.on("resize",()=>l()),t.on("artplayerPluginDanmuku:loaded",()=>l()),t.on("artplayerPluginDanmuku:points",t=>l(t))}})}const $='',w='',b='',S='',M='',z='',E='',T='',C='',F='',A='',P='';class D{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:a}=t.template;i(a,"display","flex"),this.template={$controlsCenter:a,$mount:a,$danmuku:null,$toggle:null,$config:null,$configPanel:null,$configModes:null,$style:null,$stylePanel:null,$styleModes:null,$colors:null,$opacitySlider:null,$opacityValue:null,$marginSlider:null,$marginValue:null,$fontSizeSlider:null,$fontSizeValue:null,$speedSlider:null,$speedValue:null,$input:null,$send:null},this.slider={opacity:null,margin:null,fontSize:null,speed:null},this.emitting=!1,this.isLock=!1,this.timer=null,this.createTemplate(),this.createSliders(),this.createEvents(),this.mount(this.option.mount),t.on("resize",()=>this.resize()),t.on("fullscreen",t=>this.onFullscreen(t)),t.on("fullscreenWeb",t=>this.onFullscreen(t)),t.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),t.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}static get icons(){return{$on:A,$off:F,$config:b,$style:P,$mode_0_off:S,$mode_0_on:M,$mode_1_off:z,$mode_1_on:E,$mode_2_off:T,$mode_2_on:C,$check_on:w,$check_off:$}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){const{option:t}=this;return`\n

\n ${A}${F}\n
\n
\n ${b}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${S}${M}\n
\u6eda\u52a8
\n
\n
\n ${z}${E}\n
\u9876\u90e8
\n
\n
\n ${T}${C}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${w}${$}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${w}${$}\n \u540c\u6b65\u89c6\u9891\u901f\u5ea6\n
\n
\n
\n \u4e0d\u900f\u660e\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n \u663e\u793a\u533a\u57df\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u5b57\u53f7\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u901f\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n
\n
\n
\n
\n ${P}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${M}\n
\u6eda\u52a8
\n
\n
\n ${E}\n
\u9876\u90e8
\n
\n
\n ${C}\n
\u5e95\u90e8
\n
\n
\n
\n
\n \u989c\u8272\n
\n ${this.COLOR.map(t=>`
`).join("")}\n
\n
\n
\n
\n
\n \n
\u53d1\u9001
\n
\n `}get OPACITY(){return l({min:0,max:100,steps:[]},this.option.OPACITY)}get FONT_SIZE(){return l({min:12,max:120,steps:[]},this.option.FONT_SIZE)}get MARGIN(){return l({min:0,max:3,steps:[{name:"1/4",value:[10,"75%"]},{name:"\u534a\u5c4f",value:[10,"50%"]},{name:"3/4",value:[10,"25%"]},{name:"\u6ee1\u5c4f",value:[10,10]}]},this.option.MARGIN)}get SPEED(){return l({min:0,max:4,steps:[{name:"\u6781\u6162",value:10},{name:"\u8f83\u6162",value:7.5,hide:!0},{name:"\u9002\u4e2d",value:5},{name:"\u8f83\u5feb",value:2.5,hide:!0},{name:"\u6781\u5feb",value:1}]},this.option.SPEED)}get COLOR(){return this.option.COLOR.length?this.option.COLOR:["#FE0302","#FF7204","#FFAA02","#FFD302","#FFFF00","#A0EE00","#00CD00","#019899","#4266BE","#89D5FF","#CC0273","#222222","#9B9B9B","#FFFFFF"]}query(t){const{query:e}=this.utils,{$danmuku:i}=this.template;return e(t,i)}append(t,e){const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){const{$player:i}=this.art.template,{$mount:a}=this.template;i.dataset[t]=e,this.outside&&(a.dataset[t]=e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,this.template.$danmuku=i,this.template.$toggle=this.query(".apd-toggle"),this.template.$config=this.query(".apd-config"),this.template.$configPanel=this.query(".apd-config-panel"),this.template.$configModes=this.query(".apd-config-mode .apd-modes"),this.template.$style=this.query(".apd-style"),this.template.$stylePanel=this.query(".apd-style-panel"),this.template.$styleModes=this.query(".apd-style-mode .apd-modes"),this.template.$colors=this.query(".apd-colors"),this.template.$antiOverlap=this.query(".apd-anti-overlap"),this.template.$syncVideo=this.query(".apd-sync-video"),this.template.$opacitySlider=this.query(".apd-config-opacity .apd-slider"),this.template.$opacityValue=this.query(".apd-config-opacity .apd-value"),this.template.$marginSlider=this.query(".apd-config-margin .apd-slider"),this.template.$marginValue=this.query(".apd-config-margin .apd-value"),this.template.$fontSizeSlider=this.query(".apd-config-fontSize .apd-slider"),this.template.$fontSizeValue=this.query(".apd-config-fontSize .apd-value"),this.template.$speedSlider=this.query(".apd-config-speed .apd-slider"),this.template.$speedValue=this.query(".apd-config-speed .apd-value"),this.template.$input=this.query(".apd-input"),this.template.$send=this.query(".apd-send");const{$toggle:a}=this.template;this.art.on("artplayerPluginDanmuku:show",()=>{e(a,"\u5173\u95ed\u5f39\u5e55")}),this.art.on("artplayerPluginDanmuku:hide",()=>{e(a,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:a,$antiOverlap:n,$syncVideo:s,$send:o,$input:r}=this.template;this.art.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.art.proxy(e,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.option.modes.includes(i)?this.danmuku.config({modes:this.option.modes.filter(t=>t!==i)}):this.danmuku.config({modes:[...this.option.modes,i]}),this.reset()}),this.art.proxy(n,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.art.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.art.proxy(i,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.danmuku.config({mode:i}),this.reset()}),this.art.proxy(a,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.art.proxy(o,"click",()=>this.emit()),this.art.proxy(r,"keypress",t=>{"Enter"===t.key&&(t.preventDefault(),this.emit())})}createSliders(){this.slider.opacity=this.createSlider(d(l({},this.OPACITY),{container:this.template.$opacitySlider,findIndex:()=>Math.round(100*this.option.opacity),onChange:t=>{const{$opacityValue:e}=this.template;e.textContent=`${t}%`,this.danmuku.config({opacity:t/100})}})),this.slider.margin=this.createSlider(d(l({},this.MARGIN),{container:this.template.$marginSlider,findIndex:()=>this.MARGIN.steps.findIndex(t=>t.value[0]===this.option.margin[0]&&t.value[1]===this.option.margin[1]),onChange:t=>{const e=this.MARGIN.steps[t];if(!e)return;const{$marginValue:i}=this.template;i.textContent=e.name,this.danmuku.config({margin:e.value})}})),this.slider.fontSize=this.createSlider(d(l({},this.FONT_SIZE),{container:this.template.$fontSizeSlider,findIndex:()=>this.danmuku.fontSize,onChange:t=>{const{$fontSizeValue:e}=this.template;e.textContent=`${t}px`,t!==this.danmuku.fontSize&&this.danmuku.config({fontSize:t})}})),this.slider.speed=this.createSlider(d(l({},this.SPEED),{container:this.template.$speedSlider,findIndex:()=>this.SPEED.steps.findIndex(t=>t.value===this.option.speed),onChange:t=>{const e=this.SPEED.steps[t];if(!e)return;const{$speedValue:i}=this.template;i.textContent=e.name,this.danmuku.config({speed:e.value})}}))}createSlider({min:t,max:e,container:i,findIndex:a,onChange:n,steps:s=[]}){const{query:o,clamp:r,setStyle:l}=this.utils;l(i,"touch-action","none"),i.innerHTML=`\n
\n
\n ${s.map(()=>'
').join("")}\n
\n
\n
\n
\n
\n ${s.map(t=>t.hide?"":`
${t.name}
`).join("")}\n
\n `;const d=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i=a()){if(ie)return;const o=(i-t)/(e-t);d.style.left=100*o+"%",0===s.length&&(p.style.width=d.style.left),n(i)}function c(a){const{top:n,height:s,left:o,width:l}=i.getBoundingClientRect();if(this.art.isRotate){const i=r(a.clientY-n,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=r(a.clientX-o,0,l);u(Math.round(i/l*(e-t)+t))}}return this.art.proxy(i,"click",t=>{c.call(this,t)}),this.art.proxy(i,"pointerdown",t=>{h=0===t.button}),this.art.on("document:pointermove",t=>{h&&c.call(this,t)}),this.art.on("document:pointerup",t=>{h&&(h=!1,c.call(this,t))}),{reset:u}}onFullscreen(t){const{$danmuku:e,$controlsCenter:i,$mount:a}=this.template;this.outside?t?this.append(i,e):this.append(a,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){const{$player:i}=this.art.template,a=t.getBoundingClientRect(),n=e.getBoundingClientRect(),s=i.getBoundingClientRect(),o=n.width/2-a.width/2,r=s.left-(a.left-o),l=a.right+o-s.right;e.style.left=r>0?`${-o+r}px`:l>0?-o-l+"px":-o+"px"}emit(){return p(this,null,function*(){const{$input:t}=this.template,e=t.value.trim();if(!e.length)return;if(this.isLock)return;if(this.emitting)return;const i={text:e,mode:this.option.mode,color:this.option.color,time:this.art.currentTime};try{this.emitting=!0;const e=yield this.option.beforeEmit(i);if(this.emitting=!1,!0!==e)return;i.border=!0,delete i.time,this.danmuku.emit(i),t.value="",this.lock()}catch(a){console.error("Error emitting danmuku:",a),this.emitting=!1}})}lock(){const{addClass:t}=this.utils,{$send:e}=this.template;this.isLock=!0;let i=this.option.lockTime;e.textContent=i,t(e,"apd-lock");const a=()=>{this.timer=setTimeout(()=>{0===i?this.unlock():(i-=1,e.textContent=i,a())},1e3)};a()}unlock(){const{removeClass:t}=this.utils,{$send:e}=this.template;clearTimeout(this.timer),this.isLock=!1,e.textContent="\u53d1\u9001",t(e,"apd-lock")}resize(){if(this.outside)return;if(this.art.fullscreen)return;if(this.art.fullscreenWeb)return;const{$player:t,$controlsCenter:e}=this.art.template,{$danmuku:i}=this.template;this.art.widtht.dataset.color===this.option.color.toUpperCase());s&&t(s,"apd-active"),e(i,this.option.visible?"\u5173\u95ed\u5f39\u5e55":"\u6253\u5f00\u5f39\u5e55"),this.resize()}mount(t){const{errorHandle:e}=this.utils,i="string"===typeof t?document.querySelector(t):t;e(i,`Can not find the mount point: ${t}`),this.append(i,this.template.$danmuku),this.template.$mount=i,this.reset()}}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku";let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(e)}):(document.head||document.documentElement).appendChild(e)),e.textContent='.artplayer-plugin-danmuku{display:flex;position:relative;z-index:99;align-items:center;justify-content:center;font-size:12px;height:32px;width:100%;color:#fff;font-weight:300;flex-shrink:0;gap:10px}.artplayer-plugin-danmuku .apd-icon{cursor:pointer;opacity:.75;transition:all .2s ease;fill:#fff}.artplayer-plugin-danmuku .apd-icon:hover{opacity:1}.artplayer-plugin-danmuku .apd-config{display:flex;position:relative}.artplayer-plugin-danmuku .apd-config .apd-config-panel{position:absolute;bottom:24px;left:0;width:320px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-config-mode,.artplayer-plugin-danmuku .apd-config-slider,.artplayer-plugin-danmuku .apd-config-other,.artplayer-plugin-danmuku .apd-style-mode{margin-bottom:15px}.artplayer-plugin-danmuku .apd-modes{display:flex;align-items:center;margin-top:5px;gap:20px}.artplayer-plugin-danmuku .apd-modes .apd-mode{cursor:pointer;text-align:center}.artplayer-plugin-danmuku .apd-modes .apd-mode:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-config-slider{display:flex;align-items:center;gap:12px}.artplayer-plugin-danmuku .apd-config-slider .apd-value{width:32px;text-align:right}.artplayer-plugin-danmuku .apd-slider{position:relative;flex:1;display:flex;height:20px;align-items:center;justify-content:center;cursor:pointer}.artplayer-plugin-danmuku .apd-slider .apd-slider-line{position:relative;height:2px;width:100%;overflow:hidden;border-radius:3px;background-color:#ffffff40}.artplayer-plugin-danmuku .apd-slider .apd-slider-points{position:absolute;inset:0;display:flex;align-items:center;justify-content:space-between}.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point{width:2px;height:2px;border-radius:50%;background-color:#ffffff80}.artplayer-plugin-danmuku .apd-slider .apd-slider-progress{width:0%;height:100%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-dot{position:absolute;transform:translate(-6px);left:0%;width:12px;height:12px;border-radius:50%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps{display:flex;align-items:center;justify-content:space-between;position:absolute;bottom:-12px;width:calc(100% + 32px);color:#777}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step{flex-shrink:0;width:36px;text-align:center;scale:.95}.artplayer-plugin-danmuku .apd-config-other{display:flex;align-items:center;gap:20px}.artplayer-plugin-danmuku .apd-config-other .apd-check-off,.artplayer-plugin-danmuku .apd-config-other .apd-check-on{width:16px;height:16px}.artplayer-plugin-danmuku .apd-config-other .apd-other{display:flex;align-items:center;cursor:pointer;gap:2px}.artplayer-plugin-danmuku .apd-config-other .apd-other:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-emitter{display:flex;flex:1;align-items:center;height:100%;background-color:#ffffff40;border-radius:5px}.artplayer-plugin-danmuku .apd-style{position:relative;display:flex;align-items:center;justify-content:center}.artplayer-plugin-danmuku .apd-style .apd-style-panel{position:absolute;bottom:24px;left:0;width:200px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-colors{display:flex;flex-wrap:wrap;margin-top:5px;gap:8px}.artplayer-plugin-danmuku .apd-colors .apd-color{width:16px;height:16px;border-radius:2px;cursor:pointer}.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active{border:1px solid black;box-shadow:0 0 0 1px #fff}.artplayer-plugin-danmuku .apd-input{outline:none;height:100%;flex:1;min-width:0;width:auto;border:none;line-height:1;color:#fff;background-color:transparent}.artplayer-plugin-danmuku .apd-input::placeholder{color:#ffffff80}.artplayer-plugin-danmuku .apd-send{display:flex;align-items:center;justify-content:center;height:100%;width:60px;flex-shrink:0;cursor:pointer;text-shadow:none;border-top-right-radius:5px;border-bottom-right-radius:5px;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-send.apd-lock{cursor:not-allowed;color:#666;background-color:#e7e7e7}.art-controls-center .apd-emitter{width:260px;flex:none}.art-fullscreen .artplayer-plugin-danmuku,.art-fullscreen-web .artplayer-plugin-danmuku{height:38px;gap:16px}.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on{width:28px;height:28px}.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter{width:400px;flex:none}.art-video-player>.artplayer-plugin-danmuku{position:absolute;left:0;right:0;bottom:-40px;padding:0 10px}.art-video-player:has(>.artplayer-plugin-danmuku){margin-bottom:40px}[data-danmuku-emitter=false] .apd-emitter{display:none!important}[data-danmuku-emitter=false] .art-controls-center .artplayer-plugin-danmuku{justify-content:flex-end;gap:18px}[data-danmuku-emitter=false].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,[data-danmuku-emitter=false].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku{gap:24px}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-icon{fill:#333}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-emitter{background-color:#f1f2f3}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input{color:#000}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input::placeholder{color:#0000004d}[data-danmuku-visible=false] .apd-toggle-off{display:block}[data-danmuku-visible=false] .apd-toggle-on,[data-danmuku-visible=true] .apd-toggle-off{display:none}[data-danmuku-visible=true] .apd-toggle-on{display:block}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-on{display:none}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-off,[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-on{display:block}[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-off,[data-danmuku-sync-video=false] .apd-sync-video .apd-check-on{display:none}[data-danmuku-sync-video=false] .apd-sync-video .apd-check-off,[data-danmuku-sync-video=true] .apd-sync-video .apd-check-on{display:block}[data-danmuku-sync-video=true] .apd-sync-video .apd-check-off{display:none}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-off{display:block}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-on{display:none}[data-danmuku-mode0=false] .art-danmuku [data-mode="0"]{opacity:0!important}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-off{display:none}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-on{display:block}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"]{color:#00a1d6}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"] path{fill:#00a1d6}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-off{display:block}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-on{display:none}[data-danmuku-mode1=false] .art-danmuku [data-mode="1"]{opacity:0!important}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-off{display:none}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-on{display:block}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"]{color:#00a1d6}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"] path{fill:#00a1d6}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-off{display:block}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-on{display:none}[data-danmuku-mode2=false] .art-danmuku [data-mode="2"]{opacity:0!important}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-off{display:none}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-on{display:block}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"]{color:#00a1d6}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"] path{fill:#00a1d6}'}function O(t){return e=>{const i=new v(e,t),a=new D(e,i);return i.option.heatmap&&x(e,i,i.option.heatmap),{name:"artplayerPluginDanmuku",emit:i.emit.bind(i),load:i.load.bind(i),config:i.config.bind(i),hide:i.hide.bind(i),show:i.show.bind(i),reset:i.reset.bind(i),mount:a.mount.bind(a),get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return O.icons=D.icons,O}); +!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDanmuku=e(),define(function(){return t.artplayerPluginDanmuku})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDanmuku=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.defineProperties,i=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,o=Math.pow,r=(e,i,a)=>i in e?t(e,i,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[i]=a,l=(t,e)=>{for(var i in e||(e={}))n.call(e,i)&&r(t,i,e[i]);if(a)for(var i of a(e))s.call(e,i)&&r(t,i,e[i]);return t},d=(t,a)=>e(t,i(a)),p=(t,e,i)=>new Promise((a,n)=>{var s=t=>{try{r(i.next(t))}catch(e){n(e)}},o=t=>{try{r(i.throw(t))}catch(e){n(e)}},r=t=>t.done?a(t.value):Promise.resolve(t.value).then(s,o);r((i=i.apply(t,e)).next())});function h(t){return t instanceof Promise||"[object Promise]"===Object.prototype.toString.call(t)}function u(t){switch(t){case 1:case 2:case 3:default:return 0;case 4:return 2;case 5:return 1}}function c(t){if("string"!==typeof t)return[];const e=new RegExp(']*?p="(?

[^"]+)"[^>]*>(?.*?)<\\/d>',"gs"),i=t.matchAll(e);return Array.from(i).map(t=>{const e=t.groups.p.split(",");if(e.length>=8){return{text:t.groups.text.trim().replaceAll(""",'"').replaceAll("'","'").replaceAll("<","<").replaceAll(">",">").replaceAll("&","&"),time:Number(e[0]),mode:u(Number(e[1])),fontSize:Number(e[2]),color:`#${Number(e[3]).toString(16)}`,timestamp:Number(e[4]),pool:Number(e[5]),userID:e[6],rowID:Number(e[7])}}return null}).filter(Boolean)}function m({data:t}){const{xml:e,id:i}=t,a=c(e);globalThis.postMessage({danmus:a,id:i})}let f=0;function g(t,{signal:e,onCancel:i}={}){return new Promise((a,n)=>{let s,o,r,l=!1;const d=++f,h=()=>y(void 0,function(t){if(void 0!==(null==t?void 0:t.reason))return t.reason;const e=new Error("Bilibili Danmu loading cancelled");return e.name="AbortError",e}(e));function g(){let t,e=!1;try{if(s){const t=s;s=void 0,t.onmessage=null,t.onerror=null,t.onmessageerror=null,t.terminate()}}catch(i){t=i,e=!0}if(void 0!==o){const a=o;o=void 0;try{URL.revokeObjectURL(a)}catch(i){e||(t=i),e=!0}}if(e)throw t}function y(t,i){if(l)return;l=!0;let s=arguments.length>1;null==e||e.removeEventListener("abort",h),null==r||r();try{g()}catch(o){s||(i=o),s=!0}s?n(i):a(t)}function v(t,e){if(!l){console.error("Error parsing Bilibili Danmu:",e);try{y(c(t))}catch(i){y(void 0,i)}}}(null==e?void 0:e.aborted)?h():(null==e||e.addEventListener("abort",h),r=null==i?void 0:i(h),l?null==r||r():function(){return p(this,null,function*(){const i=e?yield fetch(t,{signal:e}):yield fetch(t);if(l)return;const a=yield i.text();if(!l)try{const t=`\n ${u.toString()}\n ${c.toString()}\n onmessage = ${m.toString()}\n `;if(o=URL.createObjectURL(new Blob([t],{type:"application/javascript"})),l)return void g();if(s=new Worker(o),l)return void g();s.onmessage=t=>{var e;if(l||(null==(e=t.data)?void 0:e.id)!==d)return;const{danmus:i}=t.data;Array.isArray(i)?y(i):y(void 0,new Error("Invalid Bilibili Danmu worker response"))},s.onerror=t=>{var e;null==(e=t.preventDefault)||e.call(t),v(a,t.error||t)},s.onmessageerror=t=>v(a,t.error||t),s.postMessage({xml:a,id:d})}catch(n){v(a,n)}})}().catch(t=>y(void 0,t)))})}const y=new WeakMap,v=Symbol("cancelled danmuku input");function k(t){return y.has(t)||y.set(t,{closed:!1,tasks:new Set,emitting:[]}),y.get(t)}function x(t){const e=k(t),i=e.emitting[e.emitting.length-1];return!e.closed&&!t.art.isDestroy&&(!i||i.active())}function $(t){const e=k(t);e.closed=!0;for(const i of[...e.tasks])i.cancel()}const w='/*!\n * artplayer-plugin-danmuku.js v5.3.0\n * Github: https://github.com/zhw2590582/ArtPlayer\n * (c) 2017-2026 Harvey Zhao\n * Released under the MIT License.\n */\nfunction t({target:t,visibles:e,clientWidth:i,clientHeight:h,marginBottom:n,marginTop:o,antiOverlap:s}){const r=h-n,p=e.filter(e=>e.mode===t.mode&&e.top<=r).sort((t,e)=>t.top-e.top);if(0===p.length)return 2===t.mode?r-t.height:o;if(p.unshift({type:"top",top:0,left:0,right:0,height:o,width:i,speed:0,distance:i}),p.push({type:"bottom",top:r,left:0,right:0,height:n,width:i,speed:0,distance:i}),2===t.mode)for(let g=p.length-2;g>=0;g-=1){const e=p[g],i=p[g+1],h=e.top+e.height;if(i.top-h>=t.height)return i.top-t.height}else for(let g=1;g=t.height)return h}const a=[];for(let g=1;g{const i=Math.min(...e.map(t=>t.right)),h=Math.min(...t.map(t=>t.right));return i*e.length-h*t.length});break;case 1:case 2:a.sort((t,e)=>{const i=Math.max(...e.map(t=>t.width));return Math.max(...t.map(t=>t.width))*t.length-i*e.length})}return a[0][0].top}switch(t.mode){case 0:{const e=a.find(e=>e.every(e=>{if(ie.time}));return e&&e[0]?e[0].top:void 0}case 1:case 2:return}}onmessage=e=>{const{data:i}=e;if(!i.id||!i.type)return;const h=(0,{getDanmuTop:t}[i.type])(i);globalThis.postMessage({result:h,id:i.id})};\n',b="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",w],{type:"text/javascript;charset=utf-8"});function S(t){let e;try{if(e=b&&(self.URL||self.webkitURL).createObjectURL(b),!e)throw"";const i=new Worker(e,{type:"module",name:null==t?void 0:t.name});return i.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(e)}),i}catch(i){return new Worker("data:text/javascript;charset=utf-8,"+encodeURIComponent(w),{type:"module",name:null==t?void 0:t.name})}}class M{constructor(t,e){const{constructor:i,template:a}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=a.$danmuku,this.$player=a.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.worker=null,this.option=M.option,this.states={wait:[],ready:[],emit:[],stop:[]},this.start=this.start.bind(this),this.stop=this.stop.bind(this),this.reset=this.reset.bind(this),this.resize=this.resize.bind(this),this.destroy=this.destroy.bind(this),x(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!x(this))return;this.worker=new S}catch(n){throw t.off("destroy",this.destroy),$(this),n}t.on("video:play",this.start),t.on("video:playing",this.start),t.on("video:pause",this.stop),t.on("video:waiting",this.stop),t.on("resize",this.resize),this.load().catch(t=>console.warn("Failed to load initial danmuku:",t))}static get option(){return{danmuku:[],speed:5,margin:[10,"25%"],opacity:1,color:"#FFFFFF",mode:0,modes:[0,1,2],fontSize:25,antiOverlap:!0,synchronousPlayback:!1,mount:void 0,heatmap:!1,width:512,points:[],filter:()=>!0,beforeEmit:()=>!0,beforeVisible:()=>!0,visible:!0,emitter:!0,maxLength:200,lockTime:5,theme:"dark",OPACITY:{},FONT_SIZE:{},MARGIN:{},SPEED:{},COLOR:[]}}static get scheme(){return{danmuku:"array|function|string|promise",speed:"number",margin:"array",opacity:"number",color:"string",mode:"number",modes:"array",fontSize:"number|string",antiOverlap:"boolean",synchronousPlayback:"boolean",mount:"?htmldivelement|string",heatmap:"object|boolean",width:"number",points:"array",filter:"function",beforeEmit:"function",beforeVisible:"function",visible:"boolean",emitter:"boolean",maxLength:"number",lockTime:"number",theme:"string",OPACITY:"object",FONT_SIZE:"object",MARGIN:"object",SPEED:"object",COLOR:"array"}}static get cssText(){return'\n user-select: none;\n position: absolute;\n white-space: pre;\n pointer-events: none;\n perspective: 500px;\n display: inline-block;\n will-change: transform;\n font-weight: normal;\n line-height: 1.125;\n visibility: hidden;\n font-family: SimHei, "Microsoft JhengHei", Arial, Helvetica, sans-serif;\n text-shadow: rgb(0, 0, 0) 1px 0px 1px, rgb(0, 0, 0) 0px 1px 1px, rgb(0, 0, 0) 0px -1px 1px, rgb(0, 0, 0) -1px 0px 1px;\n '}get isRotate(){var t,e;return null==(e=null==(t=this.art.plugins)?void 0:t.autoOrientation)?void 0:e.state}get marginTop(){const{clamp:t}=this.utils,e=this.option.margin[0],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return M.option.margin[0]}get marginBottom(){const{clamp:t}=this.utils,e=this.option.margin[1],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return M.option.margin[1]}get fontSize(){const{clamp:t}=this.utils,{clientHeight:e}=this.$player,i=this.option.fontSize;if("number"===typeof i)return Math.round(t(i,12,e));if("string"===typeof i&&i.endsWith("%")){const a=Number.parseFloat(i)/100;return Math.round(t(e*a,12,e))}return M.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=M.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){const{currentTime:t}=this.art,e=[];return this.filter("ready",t=>e.push(t)),this.filter("wait",i=>{t+.1>=i.time&&i.time>=t-.1&&e.push(i)}),e}get visibles(){const t=[],{clientWidth:e}=this.$player,i=this.getLeft(this.$player);return this.filter("emit",a=>{const n=a.$ref.offsetTop,s=this.getLeft(a.$ref)-i,o=a.$ref.clientHeight,r=a.$ref.clientWidth,l=s+r,d=e-l,p=l/a.$restTime,h={};h.top=n,h.left=s,h.height=o,h.width=r,h.right=d,h.speed=p,h.distance=l,h.time=a.$restTime,h.mode=a.mode,t.push(h)}),t}get speed(){return this.option.synchronousPlayback&&this.art.playbackRate?this.option.speed/Number(this.art.playbackRate):this.option.speed}load(t){return p(this,null,function*(){const{errorHandle:e}=this.utils,i=function(t,e){const i=k(t);if(e)for(const d of[...i.tasks])d.replace&&d.cancel();let a,n=i.closed||t.art.isDestroy;const s=new Promise(t=>a=t),o=new Set,r="function"===typeof AbortController?new AbortController:void 0,l={replace:e,signal:null==r?void 0:r.signal,active:()=>!n&&!i.closed&&!t.art.isDestroy,onCancel:t=>l.active()?(o.add(t),()=>o.delete(t)):(t(),()=>{}),cancel(){if(!n){n=!0,a(v),null==r||r.abort();for(const t of[...o])t();o.clear()}},wait:t=>Promise.race([t,s]),emit(e){i.emitting.push(l);try{return t.emit(e)}finally{i.emitting.pop()}},finish(){i.tasks.delete(l),o.clear()}};return n?a(v):i.tasks.add(l),l}(this,void 0===t);try{if(!i.active())return this;const a=function(t,e){return"function"===typeof t?{asynchronous:!0,value:t()}:h(t)?{asynchronous:!0,value:t}:"string"===typeof t?{asynchronous:!0,value:g(t,e)}:{asynchronous:!1,value:Array.isArray(t)?t:[]}}(t||this.option.danmuku,i),n=a.asynchronous?yield i.wait(a.value):a.value;if(!i.active())return this;if(e(Array.isArray(n),"Danmuku need return an array as result"),void 0===t){if(this.reset(),!i.active())return this;this.queue=[],this.states={wait:[],ready:[],emit:[],stop:[]},this.$refs=[],this.$danmuku.textContent=""}for(let t=0;t{const e=n[t],i=s[t];return"function"===typeof e||"function"===typeof i||h(e)||h(i)?e!==i:JSON.stringify(e)!==JSON.stringify(i)}))&&!e)return this;const o=function(t,e,{defaults:i,validate:a,clamp:n,mount:s}){const o=Object.assign({},i,t,e);return a(o),o.mode=n(o.mode,0,2),o.speed=n(o.speed,1,10),o.opacity=n(o.opacity,0,1),o.lockTime=n(o.lockTime,1,60),o.maxLength=n(o.maxLength,1,1e3),o.mount=o.mount||s,o}(this.option,t,{defaults:M.option,validate:t=>this.validator(t,M.scheme),clamp:i,mount:a});return x(this)?(this.option=o,t.fontSize&&(this.reset(),!x(this))||(this.option.visible?this.show():this.hide(),x(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this):this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return new Promise(e=>{t.id=Date.now(),this.worker.postMessage(t),this.worker.onmessage=i=>{const{data:a}=i;a.id===t.id&&e(a)}})}filter(t,e){const i=this.states[t]||[];for(let a=0;ae!==t),t.$state=e,t.$ref&&(t.$ref.dataset.state=e),this.states[e].push(t)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=M.cssText,t.$ref.style.visibility="hidden",t.$ref.style.marginLeft="0px",t.$ref.style.transform="translateX(0px)",t.$ref.style.transition="transform 0s linear 0s",this.$refs.push(t.$ref),t.$ref=null)}update(){const{setStyles:t}=this.utils;return this.timer=window.requestAnimationFrame(()=>p(this,null,function*(){if(this.art.playing&&!this.isHide){this.filter("emit",t=>{const e=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=e,t.$lastStartTime=Date.now(),t.$restTime<=0&&this.makeWait(t)});const e=this.readys;for(let i=0;i{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.filter("emit",e=>{switch(e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.left=`${t}px`,e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}})}continue(){const{clientWidth:t}=this.$player;return this.filter("stop",e=>{switch(this.setState(e,"emit"),e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}}),this}suspend(){const{clientWidth:t}=this.$player;return this.filter("emit",e=>{switch(this.setState(e,"stop"),e.mode){case 0:{const i=t-(this.getLeft(e.$ref)-this.getLeft(this.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}stop(){return this.isStop=!0,this.suspend(),window.cancelAnimationFrame(this.timer),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){return this.isStop=!1,this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start"),this}reset(){return this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this}show(){return this.isHide=!1,this.$danmuku.style.opacity=1,this.option.visible=!0,this.art.emit("artplayerPluginDanmuku:show"),this}hide(){return this.isHide=!0,this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this}destroy(){var t;$(this),this.stop(),null==(t=this.worker)||t.terminate(),this.art.off("video:play",this.start),this.art.off("video:playing",this.start),this.art.off("video:pause",this.stop),this.art.off("video:waiting",this.stop),this.art.off("resize",this.resize),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}const z={map:(t,e,i,a,n)=>(t-e)*(n-a)/(i-e)+a,range(t,e,i){const a=Math.round(t/i)*i;return Array.from({length:Math.floor((e-t)/i)},(t,e)=>e*i+a)}};function E(t,e,i){const{query:a}=t.constructor.utils;t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},mounted(n){let s=null,r=null;function l(l=[]){if(s=null,r=null,n.innerHTML="",!t.duration||t.option.isLive)return;const d={w:n.offsetWidth,h:n.offsetHeight},p={xMin:0,xMax:d.w,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*d.h),sampling:Math.floor(d.w/100),smoothing:.2,flattening:.2};"object"===typeof i&&Object.assign(p,i);let h=[];if(Array.isArray(l)&&l.length)h=[...l];else{const i=t.duration/d.w;for(let t=0;t<=d.w;t+=p.sampling){const a=e.queue.filter(({time:e})=>e>t*i&&e<=(t+p.sampling)*i).length;h.push([t,a])}}if(0===h.length)return;const u=h[h.length-1],c=u[0],m=u[1];c!==d.w&&h.push([d.w,m]);const f=h.map(t=>t[1]),g=(Math.min(...f)+Math.max(...f))/2;for(let t=0;tg?1+p.scale:1-p.scale)+p.minHeight}const y=(t,e,i,a)=>{const n=function(t,e){const i=e[0]-t[0],a=e[1]-t[1];return{length:Math.sqrt(o(i,2)+o(a,2)),angle:Math.atan2(a,i)}}(e||t,i||t),s=z.map(Math.cos(n.angle)*p.flattening,0,1,1,0),r=n.angle*s+(a?Math.PI:0),l=n.length*p.smoothing;return[t[0]+Math.cos(r)*l,t[1]+Math.sin(r)*l]},v=h.map(t=>[z.map(t[0],p.xMin,p.xMax,0,d.w),z.map(t[1],p.yMin,p.yMax,d.h,0)]).reduce((t,e,i,a)=>0===i?`M ${a[a.length-1][0]},${d.h} L ${e[0]},${d.h} L ${e[0]},${e[1]}`:`${t} ${((t,e,i)=>{const a=y(i[e-1],i[e-2],t),n=y(t,i[e-1],i[e+1],!0),s=e===i.length-1?" z":"";return`C ${a[0]},${a[1]} ${n[0]},${n[1]} ${t[0]},${t[1]}${s}`})(e,i,a)}`,"");n.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,s=a("#heatmap-start",n),r=a("#heatmap-stop",n),s.setAttribute("offset",100*t.played+"%"),r.setAttribute("offset",100*t.played+"%")}t.on("video:timeupdate",()=>{s&&r&&(s.setAttribute("offset",100*t.played+"%"),r.setAttribute("offset",100*t.played+"%"))}),t.on("setBar",(t,e)=>{s&&r&&"played"===t&&(s.setAttribute("offset",100*e+"%"),r.setAttribute("offset",100*e+"%"))}),t.on("ready",()=>l()),t.on("resize",()=>l()),t.on("artplayerPluginDanmuku:loaded",()=>l()),t.on("artplayerPluginDanmuku:points",t=>l(t))}})}const C='',T='',F='',A='',D='',P='',L='',O='',H='',V='',j='',R='';class B{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:a}=t.template;i(a,"display","flex"),this.template={$controlsCenter:a,$mount:a,$danmuku:null,$toggle:null,$config:null,$configPanel:null,$configModes:null,$style:null,$stylePanel:null,$styleModes:null,$colors:null,$opacitySlider:null,$opacityValue:null,$marginSlider:null,$marginValue:null,$fontSizeSlider:null,$fontSizeValue:null,$speedSlider:null,$speedValue:null,$input:null,$send:null},this.slider={opacity:null,margin:null,fontSize:null,speed:null},this.emitting=!1,this.isLock=!1,this.timer=null,this.createTemplate(),this.createSliders(),this.createEvents(),this.mount(this.option.mount),t.on("resize",()=>this.resize()),t.on("fullscreen",t=>this.onFullscreen(t)),t.on("fullscreenWeb",t=>this.onFullscreen(t)),t.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),t.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}static get icons(){return{$on:j,$off:V,$config:F,$style:R,$mode_0_off:A,$mode_0_on:D,$mode_1_off:P,$mode_1_on:L,$mode_2_off:O,$mode_2_on:H,$check_on:T,$check_off:C}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){const{option:t}=this;return`\n

\n ${j}${V}\n
\n
\n ${F}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${A}${D}\n
\u6eda\u52a8
\n
\n
\n ${P}${L}\n
\u9876\u90e8
\n
\n
\n ${O}${H}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${T}${C}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${T}${C}\n \u540c\u6b65\u89c6\u9891\u901f\u5ea6\n
\n
\n
\n \u4e0d\u900f\u660e\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n \u663e\u793a\u533a\u57df\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u5b57\u53f7\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u901f\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n
\n
\n
\n
\n ${R}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${D}\n
\u6eda\u52a8
\n
\n
\n ${L}\n
\u9876\u90e8
\n
\n
\n ${H}\n
\u5e95\u90e8
\n
\n
\n
\n
\n \u989c\u8272\n
\n ${this.COLOR.map(t=>`
`).join("")}\n
\n
\n
\n
\n
\n \n
\u53d1\u9001
\n
\n `}get OPACITY(){return l({min:0,max:100,steps:[]},this.option.OPACITY)}get FONT_SIZE(){return l({min:12,max:120,steps:[]},this.option.FONT_SIZE)}get MARGIN(){return l({min:0,max:3,steps:[{name:"1/4",value:[10,"75%"]},{name:"\u534a\u5c4f",value:[10,"50%"]},{name:"3/4",value:[10,"25%"]},{name:"\u6ee1\u5c4f",value:[10,10]}]},this.option.MARGIN)}get SPEED(){return l({min:0,max:4,steps:[{name:"\u6781\u6162",value:10},{name:"\u8f83\u6162",value:7.5,hide:!0},{name:"\u9002\u4e2d",value:5},{name:"\u8f83\u5feb",value:2.5,hide:!0},{name:"\u6781\u5feb",value:1}]},this.option.SPEED)}get COLOR(){return this.option.COLOR.length?this.option.COLOR:["#FE0302","#FF7204","#FFAA02","#FFD302","#FFFF00","#A0EE00","#00CD00","#019899","#4266BE","#89D5FF","#CC0273","#222222","#9B9B9B","#FFFFFF"]}query(t){const{query:e}=this.utils,{$danmuku:i}=this.template;return e(t,i)}append(t,e){const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){const{$player:i}=this.art.template,{$mount:a}=this.template;i.dataset[t]=e,this.outside&&(a.dataset[t]=e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,this.template.$danmuku=i,this.template.$toggle=this.query(".apd-toggle"),this.template.$config=this.query(".apd-config"),this.template.$configPanel=this.query(".apd-config-panel"),this.template.$configModes=this.query(".apd-config-mode .apd-modes"),this.template.$style=this.query(".apd-style"),this.template.$stylePanel=this.query(".apd-style-panel"),this.template.$styleModes=this.query(".apd-style-mode .apd-modes"),this.template.$colors=this.query(".apd-colors"),this.template.$antiOverlap=this.query(".apd-anti-overlap"),this.template.$syncVideo=this.query(".apd-sync-video"),this.template.$opacitySlider=this.query(".apd-config-opacity .apd-slider"),this.template.$opacityValue=this.query(".apd-config-opacity .apd-value"),this.template.$marginSlider=this.query(".apd-config-margin .apd-slider"),this.template.$marginValue=this.query(".apd-config-margin .apd-value"),this.template.$fontSizeSlider=this.query(".apd-config-fontSize .apd-slider"),this.template.$fontSizeValue=this.query(".apd-config-fontSize .apd-value"),this.template.$speedSlider=this.query(".apd-config-speed .apd-slider"),this.template.$speedValue=this.query(".apd-config-speed .apd-value"),this.template.$input=this.query(".apd-input"),this.template.$send=this.query(".apd-send");const{$toggle:a}=this.template;this.art.on("artplayerPluginDanmuku:show",()=>{e(a,"\u5173\u95ed\u5f39\u5e55")}),this.art.on("artplayerPluginDanmuku:hide",()=>{e(a,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:a,$antiOverlap:n,$syncVideo:s,$send:o,$input:r}=this.template;this.art.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.art.proxy(e,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.option.modes.includes(i)?this.danmuku.config({modes:this.option.modes.filter(t=>t!==i)}):this.danmuku.config({modes:[...this.option.modes,i]}),this.reset()}),this.art.proxy(n,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.art.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.art.proxy(i,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.danmuku.config({mode:i}),this.reset()}),this.art.proxy(a,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.art.proxy(o,"click",()=>this.emit()),this.art.proxy(r,"keypress",t=>{"Enter"===t.key&&(t.preventDefault(),this.emit())})}createSliders(){this.slider.opacity=this.createSlider(d(l({},this.OPACITY),{container:this.template.$opacitySlider,findIndex:()=>Math.round(100*this.option.opacity),onChange:t=>{const{$opacityValue:e}=this.template;e.textContent=`${t}%`,this.danmuku.config({opacity:t/100})}})),this.slider.margin=this.createSlider(d(l({},this.MARGIN),{container:this.template.$marginSlider,findIndex:()=>this.MARGIN.steps.findIndex(t=>t.value[0]===this.option.margin[0]&&t.value[1]===this.option.margin[1]),onChange:t=>{const e=this.MARGIN.steps[t];if(!e)return;const{$marginValue:i}=this.template;i.textContent=e.name,this.danmuku.config({margin:e.value})}})),this.slider.fontSize=this.createSlider(d(l({},this.FONT_SIZE),{container:this.template.$fontSizeSlider,findIndex:()=>this.danmuku.fontSize,onChange:t=>{const{$fontSizeValue:e}=this.template;e.textContent=`${t}px`,t!==this.danmuku.fontSize&&this.danmuku.config({fontSize:t})}})),this.slider.speed=this.createSlider(d(l({},this.SPEED),{container:this.template.$speedSlider,findIndex:()=>this.SPEED.steps.findIndex(t=>t.value===this.option.speed),onChange:t=>{const e=this.SPEED.steps[t];if(!e)return;const{$speedValue:i}=this.template;i.textContent=e.name,this.danmuku.config({speed:e.value})}}))}createSlider({min:t,max:e,container:i,findIndex:a,onChange:n,steps:s=[]}){const{query:o,clamp:r,setStyle:l}=this.utils;l(i,"touch-action","none"),i.innerHTML=`\n
\n
\n ${s.map(()=>'
').join("")}\n
\n
\n
\n
\n
\n ${s.map(t=>t.hide?"":`
${t.name}
`).join("")}\n
\n `;const d=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i=a()){if(ie)return;const o=(i-t)/(e-t);d.style.left=100*o+"%",0===s.length&&(p.style.width=d.style.left),n(i)}function c(a){const{top:n,height:s,left:o,width:l}=i.getBoundingClientRect();if(this.art.isRotate){const i=r(a.clientY-n,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=r(a.clientX-o,0,l);u(Math.round(i/l*(e-t)+t))}}return this.art.proxy(i,"click",t=>{c.call(this,t)}),this.art.proxy(i,"pointerdown",t=>{h=0===t.button}),this.art.on("document:pointermove",t=>{h&&c.call(this,t)}),this.art.on("document:pointerup",t=>{h&&(h=!1,c.call(this,t))}),{reset:u}}onFullscreen(t){const{$danmuku:e,$controlsCenter:i,$mount:a}=this.template;this.outside?t?this.append(i,e):this.append(a,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){const{$player:i}=this.art.template,a=t.getBoundingClientRect(),n=e.getBoundingClientRect(),s=i.getBoundingClientRect(),o=n.width/2-a.width/2,r=s.left-(a.left-o),l=a.right+o-s.right;e.style.left=r>0?`${-o+r}px`:l>0?-o-l+"px":-o+"px"}emit(){return p(this,null,function*(){const{$input:t}=this.template,e=t.value.trim();if(!e.length)return;if(this.isLock)return;if(this.emitting)return;const i={text:e,mode:this.option.mode,color:this.option.color,time:this.art.currentTime};try{this.emitting=!0;const e=yield this.option.beforeEmit(i);if(this.emitting=!1,!0!==e)return;i.border=!0,delete i.time,this.danmuku.emit(i),t.value="",this.lock()}catch(a){console.error("Error emitting danmuku:",a),this.emitting=!1}})}lock(){const{addClass:t}=this.utils,{$send:e}=this.template;this.isLock=!0;let i=this.option.lockTime;e.textContent=i,t(e,"apd-lock");const a=()=>{this.timer=setTimeout(()=>{0===i?this.unlock():(i-=1,e.textContent=i,a())},1e3)};a()}unlock(){const{removeClass:t}=this.utils,{$send:e}=this.template;clearTimeout(this.timer),this.isLock=!1,e.textContent="\u53d1\u9001",t(e,"apd-lock")}resize(){if(this.outside)return;if(this.art.fullscreen)return;if(this.art.fullscreenWeb)return;const{$player:t,$controlsCenter:e}=this.art.template,{$danmuku:i}=this.template;this.art.widtht.dataset.color===this.option.color.toUpperCase());s&&t(s,"apd-active"),e(i,this.option.visible?"\u5173\u95ed\u5f39\u5e55":"\u6253\u5f00\u5f39\u5e55"),this.resize()}mount(t){const{errorHandle:e}=this.utils,i="string"===typeof t?document.querySelector(t):t;e(i,`Can not find the mount point: ${t}`),this.append(i,this.template.$danmuku),this.template.$mount=i,this.reset()}}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku";let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(e)}):(document.head||document.documentElement).appendChild(e)),e.textContent='.artplayer-plugin-danmuku{display:flex;position:relative;z-index:99;align-items:center;justify-content:center;font-size:12px;height:32px;width:100%;color:#fff;font-weight:300;flex-shrink:0;gap:10px}.artplayer-plugin-danmuku .apd-icon{cursor:pointer;opacity:.75;transition:all .2s ease;fill:#fff}.artplayer-plugin-danmuku .apd-icon:hover{opacity:1}.artplayer-plugin-danmuku .apd-config{display:flex;position:relative}.artplayer-plugin-danmuku .apd-config .apd-config-panel{position:absolute;bottom:24px;left:0;width:320px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-config-mode,.artplayer-plugin-danmuku .apd-config-slider,.artplayer-plugin-danmuku .apd-config-other,.artplayer-plugin-danmuku .apd-style-mode{margin-bottom:15px}.artplayer-plugin-danmuku .apd-modes{display:flex;align-items:center;margin-top:5px;gap:20px}.artplayer-plugin-danmuku .apd-modes .apd-mode{cursor:pointer;text-align:center}.artplayer-plugin-danmuku .apd-modes .apd-mode:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-config-slider{display:flex;align-items:center;gap:12px}.artplayer-plugin-danmuku .apd-config-slider .apd-value{width:32px;text-align:right}.artplayer-plugin-danmuku .apd-slider{position:relative;flex:1;display:flex;height:20px;align-items:center;justify-content:center;cursor:pointer}.artplayer-plugin-danmuku .apd-slider .apd-slider-line{position:relative;height:2px;width:100%;overflow:hidden;border-radius:3px;background-color:#ffffff40}.artplayer-plugin-danmuku .apd-slider .apd-slider-points{position:absolute;inset:0;display:flex;align-items:center;justify-content:space-between}.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point{width:2px;height:2px;border-radius:50%;background-color:#ffffff80}.artplayer-plugin-danmuku .apd-slider .apd-slider-progress{width:0%;height:100%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-dot{position:absolute;transform:translate(-6px);left:0%;width:12px;height:12px;border-radius:50%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps{display:flex;align-items:center;justify-content:space-between;position:absolute;bottom:-12px;width:calc(100% + 32px);color:#777}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step{flex-shrink:0;width:36px;text-align:center;scale:.95}.artplayer-plugin-danmuku .apd-config-other{display:flex;align-items:center;gap:20px}.artplayer-plugin-danmuku .apd-config-other .apd-check-off,.artplayer-plugin-danmuku .apd-config-other .apd-check-on{width:16px;height:16px}.artplayer-plugin-danmuku .apd-config-other .apd-other{display:flex;align-items:center;cursor:pointer;gap:2px}.artplayer-plugin-danmuku .apd-config-other .apd-other:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-emitter{display:flex;flex:1;align-items:center;height:100%;background-color:#ffffff40;border-radius:5px}.artplayer-plugin-danmuku .apd-style{position:relative;display:flex;align-items:center;justify-content:center}.artplayer-plugin-danmuku .apd-style .apd-style-panel{position:absolute;bottom:24px;left:0;width:200px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-colors{display:flex;flex-wrap:wrap;margin-top:5px;gap:8px}.artplayer-plugin-danmuku .apd-colors .apd-color{width:16px;height:16px;border-radius:2px;cursor:pointer}.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active{border:1px solid black;box-shadow:0 0 0 1px #fff}.artplayer-plugin-danmuku .apd-input{outline:none;height:100%;flex:1;min-width:0;width:auto;border:none;line-height:1;color:#fff;background-color:transparent}.artplayer-plugin-danmuku .apd-input::placeholder{color:#ffffff80}.artplayer-plugin-danmuku .apd-send{display:flex;align-items:center;justify-content:center;height:100%;width:60px;flex-shrink:0;cursor:pointer;text-shadow:none;border-top-right-radius:5px;border-bottom-right-radius:5px;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-send.apd-lock{cursor:not-allowed;color:#666;background-color:#e7e7e7}.art-controls-center .apd-emitter{width:260px;flex:none}.art-fullscreen .artplayer-plugin-danmuku,.art-fullscreen-web .artplayer-plugin-danmuku{height:38px;gap:16px}.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on{width:28px;height:28px}.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter{width:400px;flex:none}.art-video-player>.artplayer-plugin-danmuku{position:absolute;left:0;right:0;bottom:-40px;padding:0 10px}.art-video-player:has(>.artplayer-plugin-danmuku){margin-bottom:40px}[data-danmuku-emitter=false] .apd-emitter{display:none!important}[data-danmuku-emitter=false] .art-controls-center .artplayer-plugin-danmuku{justify-content:flex-end;gap:18px}[data-danmuku-emitter=false].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,[data-danmuku-emitter=false].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku{gap:24px}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-icon{fill:#333}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-emitter{background-color:#f1f2f3}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input{color:#000}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input::placeholder{color:#0000004d}[data-danmuku-visible=false] .apd-toggle-off{display:block}[data-danmuku-visible=false] .apd-toggle-on,[data-danmuku-visible=true] .apd-toggle-off{display:none}[data-danmuku-visible=true] .apd-toggle-on{display:block}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-on{display:none}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-off,[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-on{display:block}[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-off,[data-danmuku-sync-video=false] .apd-sync-video .apd-check-on{display:none}[data-danmuku-sync-video=false] .apd-sync-video .apd-check-off,[data-danmuku-sync-video=true] .apd-sync-video .apd-check-on{display:block}[data-danmuku-sync-video=true] .apd-sync-video .apd-check-off{display:none}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-off{display:block}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-on{display:none}[data-danmuku-mode0=false] .art-danmuku [data-mode="0"]{opacity:0!important}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-off{display:none}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-on{display:block}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"]{color:#00a1d6}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"] path{fill:#00a1d6}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-off{display:block}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-on{display:none}[data-danmuku-mode1=false] .art-danmuku [data-mode="1"]{opacity:0!important}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-off{display:none}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-on{display:block}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"]{color:#00a1d6}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"] path{fill:#00a1d6}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-off{display:block}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-on{display:none}[data-danmuku-mode2=false] .art-danmuku [data-mode="2"]{opacity:0!important}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-off{display:none}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-on{display:block}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"]{color:#00a1d6}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"] path{fill:#00a1d6}'}function q(t){return e=>{const i=new M(e,t),a=e.isDestroy?void 0:new B(e,i);return!e.isDestroy&&i.option.heatmap&&E(e,i,i.option.heatmap),{name:"artplayerPluginDanmuku",emit:i.emit.bind(i),load:i.load.bind(i),config:i.config.bind(i),hide:i.hide.bind(i),show:i.show.bind(i),reset:i.reset.bind(i),mount:a?a.mount.bind(a):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return q.icons=B.icons,q}); diff --git a/docs/compiled/artplayer-plugin-danmuku.mjs b/docs/compiled/artplayer-plugin-danmuku.mjs index fac426cd8..97dbcd0db 100644 --- a/docs/compiled/artplayer-plugin-danmuku.mjs +++ b/docs/compiled/artplayer-plugin-danmuku.mjs @@ -4,6 +4,91 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ +function defaultOption() { + return { + danmuku: [], + speed: 5, + margin: [10, "25%"], + opacity: 1, + color: "#FFFFFF", + mode: 0, + modes: [0, 1, 2], + fontSize: 25, + antiOverlap: true, + synchronousPlayback: false, + mount: void 0, + heatmap: false, + width: 512, + points: [], + filter: () => true, + beforeEmit: () => true, + beforeVisible: () => true, + visible: true, + emitter: true, + maxLength: 200, + lockTime: 5, + theme: "dark", + OPACITY: {}, + FONT_SIZE: {}, + MARGIN: {}, + SPEED: {}, + COLOR: [] + }; +} +function optionScheme() { + return { + danmuku: "array|function|string|promise", + speed: "number", + margin: "array", + opacity: "number", + color: "string", + mode: "number", + modes: "array", + fontSize: "number|string", + antiOverlap: "boolean", + synchronousPlayback: "boolean", + mount: "?htmldivelement|string", + heatmap: "object|boolean", + width: "number", + points: "array", + filter: "function", + beforeEmit: "function", + beforeVisible: "function", + visible: "boolean", + emitter: "boolean", + maxLength: "number", + lockTime: "number", + theme: "string", + OPACITY: "object", + FONT_SIZE: "object", + MARGIN: "object", + SPEED: "object", + COLOR: "array" + }; +} +function isPromiseInput(value) { + return value instanceof Promise || Object.prototype.toString.call(value) === "[object Promise]"; +} +function optionChanged(previous, update) { + return Object.keys(update).some((key) => { + const before = previous[key]; + const after = update[key]; + if (typeof before === "function" || typeof after === "function" || isPromiseInput(before) || isPromiseInput(after)) + return before !== after; + return JSON.stringify(before) !== JSON.stringify(after); + }); +} +function normalizeOption(previous, update, { defaults, validate, clamp, mount }) { + const next = Object.assign({}, defaults, previous, update); + validate(next); + next.mode = clamp(next.mode, 0, 2); + next.speed = clamp(next.speed, 1, 10); + next.opacity = clamp(next.opacity, 0, 1); + next.lockTime = clamp(next.lockTime, 1, 60); + next.maxLength = clamp(next.maxLength, 1, 1e3); + next.mount = next.mount || mount; + return next; +} function getMode(key) { switch (key) { case 1: @@ -45,41 +130,219 @@ function bilibiliDanmuParseFromXml(xmlString) { } function onmessage({ data }) { const { xml, id } = data; - if (!id || !xml) - return; const danmus = bilibiliDanmuParseFromXml(xml); globalThis.postMessage({ danmus, id }); } -function createWorker() { - const workerText = ` - ${getMode.toString()} - ${bilibiliDanmuParseFromXml.toString()} - onmessage = ${onmessage.toString()} - `; - const blob2 = new Blob([workerText], { type: "application/javascript" }); - return new Worker(URL.createObjectURL(blob2)); +let nextRequest = 0; +function abortError(signal) { + if (signal?.reason !== void 0) + return signal.reason; + const error = new Error("Bilibili Danmu loading cancelled"); + error.name = "AbortError"; + return error; } -function bilibiliDanmuParseFromUrl(url) { - return new Promise(async (resolve) => { - const res = await fetch(url); - const xml = await res.text(); - try { - const worker = createWorker(); - worker.onmessage = (event) => { - const { danmus, id } = event.data; - if (!id || !danmus) - return; - resolve(danmus); - worker.terminate(); - }; - worker.postMessage({ xml, id: Date.now() }); - } catch (error) { - console.error("Error parsing Bilibili Danmu:", error); - const danmus = bilibiliDanmuParseFromXml(xml); - resolve(danmus); +function bilibiliDanmuParseFromUrl(url, { signal, onCancel } = {}) { + return new Promise((resolve, reject) => { + let settled = false; + let worker; + let workerUrl; + let releaseCancel; + const id = ++nextRequest; + const cancel = () => finish(void 0, abortError(signal)); + function releaseWorker() { + let error; + let failed = false; + try { + if (worker) { + const current = worker; + worker = void 0; + current.onmessage = null; + current.onerror = null; + current.onmessageerror = null; + current.terminate(); + } + } catch (failure) { + error = failure; + failed = true; + } + if (workerUrl !== void 0) { + const current = workerUrl; + workerUrl = void 0; + try { + URL.revokeObjectURL(current); + } catch (failure) { + if (!failed) + error = failure; + failed = true; + } + } + if (failed) + throw error; } + function finish(value, error) { + if (settled) + return; + settled = true; + let failed = arguments.length > 1; + signal?.removeEventListener("abort", cancel); + releaseCancel?.(); + try { + releaseWorker(); + } catch (cleanupError) { + if (!failed) + error = cleanupError; + failed = true; + } + if (failed) + reject(error); + else resolve(value); + } + function fallback(xml, error) { + if (settled) + return; + console.error("Error parsing Bilibili Danmu:", error); + try { + finish(bilibiliDanmuParseFromXml(xml)); + } catch (failure) { + finish(void 0, failure); + } + } + async function read() { + const response = signal ? await fetch(url, { signal }) : await fetch(url); + if (settled) + return; + const xml = await response.text(); + if (settled) + return; + try { + const workerText = ` + ${getMode.toString()} + ${bilibiliDanmuParseFromXml.toString()} + onmessage = ${onmessage.toString()} + `; + workerUrl = URL.createObjectURL(new Blob([workerText], { type: "application/javascript" })); + if (settled) { + releaseWorker(); + return; + } + worker = new Worker(workerUrl); + if (settled) { + releaseWorker(); + return; + } + worker.onmessage = (event) => { + if (settled || event.data?.id !== id) + return; + const { danmus } = event.data; + if (!Array.isArray(danmus)) { + finish(void 0, new Error("Invalid Bilibili Danmu worker response")); + return; + } + finish(danmus); + }; + worker.onerror = (event) => { + event.preventDefault?.(); + fallback(xml, event.error || event); + }; + worker.onmessageerror = (event) => fallback(xml, event.error || event); + worker.postMessage({ xml, id }); + } catch (error) { + fallback(xml, error); + } + } + if (signal?.aborted) { + cancel(); + return; + } + signal?.addEventListener("abort", cancel); + releaseCancel = onCancel?.(cancel); + if (settled) { + releaseCancel?.(); + return; + } + read().catch((error) => finish(void 0, error)); }); } +const inputs = /* @__PURE__ */ new WeakMap(); +const cancelled = /* @__PURE__ */ Symbol("cancelled danmuku input"); +function stateFor(owner) { + if (!inputs.has(owner)) + inputs.set(owner, { closed: false, tasks: /* @__PURE__ */ new Set(), emitting: [] }); + return inputs.get(owner); +} +function inputActive(owner) { + const state = stateFor(owner); + const task = state.emitting[state.emitting.length - 1]; + return !state.closed && !owner.art.isDestroy && (!task || task.active()); +} +function beginInput(owner, replace) { + const state = stateFor(owner); + if (replace) { + for (const task2 of [...state.tasks]) { + if (task2.replace) + task2.cancel(); + } + } + let stopped = state.closed || owner.art.isDestroy; + let resolveCancel; + const cancellation = new Promise((resolve) => resolveCancel = resolve); + const handlers = /* @__PURE__ */ new Set(); + const controller = typeof AbortController === "function" ? new AbortController() : void 0; + const task = { + replace, + signal: controller?.signal, + active: () => !stopped && !state.closed && !owner.art.isDestroy, + onCancel(callback) { + if (!task.active()) { + callback(); + return () => { + }; + } + handlers.add(callback); + return () => handlers.delete(callback); + }, + cancel() { + if (stopped) + return; + stopped = true; + resolveCancel(cancelled); + controller?.abort(); + for (const callback of [...handlers]) callback(); + handlers.clear(); + }, + wait: (value) => Promise.race([value, cancellation]), + emit(danmu) { + state.emitting.push(task); + try { + return owner.emit(danmu); + } finally { + state.emitting.pop(); + } + }, + finish() { + state.tasks.delete(task); + handlers.clear(); + } + }; + if (stopped) + resolveCancel(cancelled); + else state.tasks.add(task); + return task; +} +function cancelInputs(owner) { + const state = stateFor(owner); + state.closed = true; + for (const task of [...state.tasks]) task.cancel(); +} +function readInput(target, task) { + if (typeof target === "function") + return { asynchronous: true, value: target() }; + if (isPromiseInput(target)) + return { asynchronous: true, value: target }; + if (typeof target === "string") + return { asynchronous: true, value: bilibiliDanmuParseFromUrl(target, task) }; + return { asynchronous: false, value: Array.isArray(target) ? target : [] }; +} const jsContent = '/*!\n * artplayer-plugin-danmuku.js v5.3.0\n * Github: https://github.com/zhw2590582/ArtPlayer\n * (c) 2017-2026 Harvey Zhao\n * Released under the MIT License.\n */\nfunction getDanmuTop({ target, visibles, clientWidth, clientHeight, marginBottom, marginTop, antiOverlap }) {\n const maxTop = clientHeight - marginBottom;\n const danmus = visibles.filter((item) => item.mode === target.mode && item.top <= maxTop).sort((prev, next) => prev.top - next.top);\n if (danmus.length === 0) {\n if (target.mode === 2) {\n return maxTop - target.height;\n } else {\n return marginTop;\n }\n }\n danmus.unshift({\n type: "top",\n top: 0,\n left: 0,\n right: 0,\n height: marginTop,\n width: clientWidth,\n speed: 0,\n distance: clientWidth\n });\n danmus.push({\n type: "bottom",\n top: maxTop,\n left: 0,\n right: 0,\n height: marginBottom,\n width: clientWidth,\n speed: 0,\n distance: clientWidth\n });\n if (target.mode === 2) {\n for (let index = danmus.length - 2; index >= 0; index -= 1) {\n const item = danmus[index];\n const prev = danmus[index + 1];\n const itemBottom = item.top + item.height;\n const diff = prev.top - itemBottom;\n if (diff >= target.height) {\n return prev.top - target.height;\n }\n }\n } else {\n for (let index = 1; index < danmus.length; index += 1) {\n const item = danmus[index];\n const prev = danmus[index - 1];\n const prevBottom = prev.top + prev.height;\n const diff = item.top - prevBottom;\n if (diff >= target.height) {\n return prevBottom;\n }\n }\n }\n const topMap = [];\n for (let index = 1; index < danmus.length - 1; index += 1) {\n const item = danmus[index];\n if (topMap.length) {\n const last = topMap[topMap.length - 1];\n if (last[0].top === item.top) {\n last.push(item);\n } else {\n topMap.push([item]);\n }\n } else {\n topMap.push([item]);\n }\n }\n if (antiOverlap) {\n switch (target.mode) {\n case 0: {\n const result = topMap.find((list) => {\n return list.every((danmu) => {\n if (clientWidth < danmu.distance)\n return false;\n if (target.speed < danmu.speed)\n return true;\n const overlapTime = danmu.right / (target.speed - danmu.speed);\n if (overlapTime > danmu.time)\n return true;\n return false;\n });\n });\n return result && result[0] ? result[0].top : void 0;\n }\n // 静止弹幕没有重叠问题\n case 1:\n case 2:\n return void 0;\n }\n } else {\n switch (target.mode) {\n case 0:\n topMap.sort((prev, next) => {\n const nextMinRight = Math.min(...next.map((item) => item.right));\n const prevMinRight = Math.min(...prev.map((item) => item.right));\n return nextMinRight * next.length - prevMinRight * prev.length;\n });\n break;\n case 1:\n case 2:\n topMap.sort((prev, next) => {\n const nextMaxWidth = Math.max(...next.map((item) => item.width));\n const prevMaxWidth = Math.max(...prev.map((item) => item.width));\n return prevMaxWidth * prev.length - nextMaxWidth * next.length;\n });\n break;\n }\n return topMap[0][0].top;\n }\n}\nonmessage = (event) => {\n const { data } = event;\n if (!data.id || !data.type)\n return;\n const fns = { getDanmuTop };\n const fn = fns[data.type];\n const result = fn(data);\n globalThis.postMessage({\n result,\n id: data.id\n });\n};\n'; const blob = typeof self !== "undefined" && self.Blob && new Blob(["URL.revokeObjectURL(import.meta.url);", jsContent], { type: "text/javascript;charset=utf-8" }); function WorkerWrapper(options) { @@ -119,113 +382,40 @@ class Danmuku { this.isHide = false; this.timer = null; this.index = 0; + this.worker = null; this.option = Danmuku.option; this.states = { wait: [], ready: [], emit: [], stop: [] }; - this.config(option, true); - this.worker = new WorkerWrapper(); this.start = this.start.bind(this); this.stop = this.stop.bind(this); this.reset = this.reset.bind(this); this.resize = this.resize.bind(this); this.destroy = this.destroy.bind(this); + inputActive(this); + art.on("destroy", this.destroy); + try { + this.config(option, true); + if (!inputActive(this)) + return; + this.worker = new WorkerWrapper(); + } catch (error) { + art.off("destroy", this.destroy); + cancelInputs(this); + throw error; + } art.on("video:play", this.start); art.on("video:playing", this.start); art.on("video:pause", this.stop); art.on("video:waiting", this.stop); - art.on("destroy", this.destroy); art.on("resize", this.resize); - this.load(); + this.load().catch((error) => console.warn("Failed to load initial danmuku:", error)); } // 默认配置 static get option() { - return { - danmuku: [], - // 弹幕数据 - speed: 5, - // 弹幕持续时间,范围在[1 ~ 10] - margin: [10, "25%"], - // 弹幕上下边距,支持像素数字和百分比 - opacity: 1, - // 弹幕透明度,范围在[0 ~ 1] - color: "#FFFFFF", - // 默认弹幕颜色,可以被单独弹幕项覆盖 - mode: 0, - // 默认弹幕模式: 0: 滚动,1: 顶部,2: 底部 - modes: [0, 1, 2], - // 弹幕可见的模式 - fontSize: 25, - // 弹幕字体大小,支持像素数字和百分比 - antiOverlap: true, - // 弹幕是否防重叠 - synchronousPlayback: false, - // 是否同步播放速度 - mount: void 0, - // 弹幕发射器挂载点, 默认为播放器控制栏中部 - heatmap: false, - // 是否开启热力图 - width: 512, - // 当播放器宽度小于此值时,弹幕发射器置于播放器底部 - points: [], - // 热力图数据 - filter: () => true, - // 弹幕载入前的过滤器,只支持返回布尔值 - beforeEmit: () => true, - // 弹幕发送前的过滤器,支持返回 Promise - beforeVisible: () => true, - // 弹幕显示前的过滤器,支持返回 Promise - visible: true, - // 弹幕层是否可见 - emitter: true, - // 是否开启弹幕发射器 - maxLength: 200, - // 弹幕输入框最大长度, 范围在[1 ~ 1000] - lockTime: 5, - // 输入框锁定时间,范围在[1 ~ 60] - theme: "dark", - // 弹幕主题,支持 dark 和 light,只在自定义挂载时生效 - OPACITY: {}, - // 不透明度配置项 - FONT_SIZE: {}, - // 弹幕字号配置项 - MARGIN: {}, - // 显示区域配置项 - SPEED: {}, - // 弹幕速度配置项 - COLOR: [] - // 颜色列表配置项 - }; + return defaultOption(); } // 配置校验 static get scheme() { - return { - danmuku: "array|function|string", - speed: "number", - margin: "array", - opacity: "number", - color: "string", - mode: "number", - modes: "array", - fontSize: "number|string", - antiOverlap: "boolean", - synchronousPlayback: "boolean", - mount: "?htmldivelement|string", - heatmap: "object|boolean", - width: "number", - points: "array", - filter: "function", - beforeEmit: "function", - beforeVisible: "function", - visible: "boolean", - emitter: "boolean", - maxLength: "number", - lockTime: "number", - theme: "string", - OPACITY: "object", - FONT_SIZE: "object", - MARGIN: "object", - SPEED: "object", - COLOR: "array" - }; + return optionScheme(); } // 初始弹幕样式 static get cssText() { @@ -345,40 +535,48 @@ class Danmuku { // 加载弹幕 async load(danmuku) { const { errorHandle } = this.utils; - let danmus = []; - const target = danmuku || this.option.danmuku; + const task = beginInput(this, danmuku === void 0); try { - if (typeof target === "function") { - danmus = await target(); - } else if (target instanceof Promise) { - danmus = await target; - } else if (typeof target === "string") { - danmus = await bilibiliDanmuParseFromUrl(target); - } else if (Array.isArray(target)) { - danmus = target; - } + if (!task.active()) + return this; + const target = danmuku || this.option.danmuku; + const input = readInput(target, task); + const danmus = input.asynchronous ? await task.wait(input.value) : input.value; + if (!task.active()) + return this; errorHandle(Array.isArray(danmus), "Danmuku need return an array as result"); if (danmuku === void 0) { this.reset(); + if (!task.active()) + return this; this.queue = []; this.states = { wait: [], ready: [], emit: [], stop: [] }; this.$refs = []; this.$danmuku.textContent = ""; } for (let index = 0; index < danmus.length; index++) { + if (!task.active()) + return this; const danmu = danmus[index]; - await this.emit(danmu); + await task.emit(danmu); } - this.art.emit("artplayerPluginDanmuku:loaded", this.queue); + if (task.active()) + this.art.emit("artplayerPluginDanmuku:loaded", this.queue); } catch (error) { + if (!task.active()) + return this; this.art.emit("artplayerPluginDanmuku:error", error); throw error; + } finally { + task.finish(); } return this; } // 把原始弹幕转换到弹幕队列 async emit(danmu) { const { clamp } = this.utils; + if (!inputActive(this)) + return this; this.validator(danmu, { id: "?string", // 弹幕唯一标识 @@ -397,7 +595,7 @@ class Danmuku { }); if (!danmu.text.trim()) return this; - if (danmu.time) { + if (danmu.time !== void 0) { danmu.time = clamp(danmu.time, 0, Infinity); } else { danmu.time = this.art.currentTime + 0.5; @@ -415,6 +613,8 @@ class Danmuku { return this; if (!this.option.filter(danmu)) return this; + if (!inputActive(this)) + return this; const item = { ...danmu, $state: "wait", @@ -436,28 +636,30 @@ class Danmuku { config(option, isInit = false) { const { clamp } = this.utils; const { $controlsCenter } = this.art.template; - const changed = Object.keys(option).some( - (key) => JSON.stringify(this.option[key]) !== JSON.stringify(option[key]) - ); + const changed = optionChanged(this.option, option); if (!changed && !isInit) return this; - this.option = Object.assign({}, Danmuku.option, this.option, option); - this.validator(this.option, Danmuku.scheme); - this.option.mode = clamp(this.option.mode, 0, 2); - this.option.speed = clamp(this.option.speed, 1, 10); - this.option.opacity = clamp(this.option.opacity, 0, 1); - this.option.lockTime = clamp(this.option.lockTime, 1, 60); - this.option.maxLength = clamp(this.option.maxLength, 1, 1e3); - this.option.mount = this.option.mount || $controlsCenter; + const next = normalizeOption(this.option, option, { + defaults: Danmuku.option, + validate: (value) => this.validator(value, Danmuku.scheme), + clamp, + mount: $controlsCenter + }); + if (!inputActive(this)) + return this; + this.option = next; if (option.fontSize) { this.reset(); + if (!inputActive(this)) + return this; } if (this.option.visible) { this.show(); } else { this.hide(); } - this.art.emit("artplayerPluginDanmuku:config", this.option); + if (inputActive(this)) + this.art.emit("artplayerPluginDanmuku:config", this.option); return this; } // 计算DOM的left值,受到旋屏影响 @@ -688,13 +890,14 @@ class Danmuku { return this; } destroy() { + cancelInputs(this); this.stop(); - this.worker.terminate(); + this.worker?.terminate(); this.art.off("video:play", this.start); this.art.off("video:playing", this.start); this.art.off("video:pause", this.stop); this.art.off("video:waiting", this.stop); - this.art.off("resize", this.reset); + this.art.off("resize", this.resize); this.art.off("destroy", this.destroy); this.art.emit("artplayerPluginDanmuku:destroy"); } @@ -1536,8 +1739,8 @@ if (typeof document !== "undefined") { function artplayerPluginDanmuku(option) { return (art) => { const danmuku = new Danmuku(art, option); - const setting = new Setting(art, danmuku); - if (danmuku.option.heatmap) { + const setting = art.isDestroy ? void 0 : new Setting(art, danmuku); + if (!art.isDestroy && danmuku.option.heatmap) { heatmap(art, danmuku, danmuku.option.heatmap); } return { @@ -1548,7 +1751,8 @@ function artplayerPluginDanmuku(option) { hide: danmuku.hide.bind(danmuku), show: danmuku.show.bind(danmuku), reset: danmuku.reset.bind(danmuku), - mount: setting.mount.bind(setting), + mount: setting ? setting.mount.bind(setting) : () => { + }, get option() { return danmuku.option; }, diff --git a/package.json b/package.json index 6facece47..3c6d775f4 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: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-failures.test.js test/danmuku-mask-failures.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/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-frames.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-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.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/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-frames.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", @@ -100,7 +100,7 @@ "test:chromecast": "node --test test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js", "test:chromecast-types-package": "node refactor/scripts/chromecast-package-types.mjs", "test:danmuku-mask": "node --test test/danmuku-mask-failures.test.js refactor/scripts/danmuku-mask-contract.test.mjs", - "test:danmuku": "node --test test/danmuku-failures.test.js refactor/scripts/danmuku-contract.test.mjs" + "test:danmuku": "node --test test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js refactor/scripts/danmuku-contract.test.mjs" }, "browserslist": "last 1 Chrome version", "devDependencies": { diff --git a/packages/artplayer-plugin-danmuku/ARCHITECTURE.md b/packages/artplayer-plugin-danmuku/ARCHITECTURE.md new file mode 100644 index 000000000..807a016b2 --- /dev/null +++ b/packages/artplayer-plugin-danmuku/ARCHITECTURE.md @@ -0,0 +1,77 @@ +# Danmuku maintenance map + +The compatibility reference is the actual npm 5.3.0 package. Its archive, +historical declarations, earlier exports and failure evidence are indexed in +`refactor/baselines/danmuku-release.json` and `danmuku-contract.md` at the +repository root. Do not infer runtime contracts from the old declaration file. + +## Module responsibilities + +| Module | Responsibility | +| --- | --- | +| `src/index.js` | Plugin factory, facade getters, bound methods, setting and heatmap composition | +| `src/danmuku.js` | Internal instance, public method return identity, queue commits and events; scheduling is still here pending PKG-DANMUKU-04 | +| `src/config.js` | Fresh defaults, validation schema, configuration comparison and normalization | +| `src/input.js` | Input forms, replacement ownership, independent append operations, cancellation and synchronous callback reentry | +| `src/bilibili-parser.js` | Pure legacy-compatible XML fields, mode mapping and entity decoding | +| `src/bilibili.js` | Fetch and response text, one parser Worker per request, fallback, settlement and Blob URL cleanup | +| `src/worker.js` | Track placement Worker; a separate protocol from the XML parser Worker | +| `src/setting.js` | Settings and emitter UI; lifecycle restructuring follows in PKG-DANMUKU-05 | +| `src/heatmap.js` | Heatmap rendering; restructuring follows in PKG-DANMUKU-05 | + +The package is undergoing staged migration. PKG-DANMUKU-03 separates input and +configuration responsibilities in JavaScript; PKG-DANMUKU-06 converts all owned +modules and Worker messages to TypeScript and validates installed consumers. +This intermediate structure is not the final TypeScript or release acceptance. + +## Contracts to retain + +- The registrar returns its facade synchronously. `emit()` and `load()` return + Promises resolving to the internal Danmuku instance, which differs from that + facade. `config/hide/show/reset` return that same internal instance + synchronously; `mount()` returns undefined. Keep the three live getters. +- `load()` and `load(undefined)` replace only after input succeeds. + `load(target)` appends without updating `option.danmuku`. Independent appends + may complete in either order and can append after a replacement. +- Arrays enter synchronously. Empty initial input emits + `show -> config -> reset -> loaded` before registration returns. Nonempty input + inserts the first item before the first await, then awaits each `emit()` in + order. A later invalid item leaves earlier accepted items present. +- A new replacement supersedes an older replacement. Destroy cancels all input + operations. Cancelled public loads resolve to the same internal instance; + they do not emit late `loaded/error` events or write stale rows. The input + Promise itself may be uncancellable; observe its eventual rejection locally. +- Invoke a loader function without an explicit receiver. Invoke the filter + through the current option so its receiver remains that option. Check load + ownership again after callbacks and reset events, including synchronous + callbacks that start another replacement. +- Keep input normalization observable: accepted objects receive missing mode, + style and color before filtering, and the queue receives a shallow copy. + Explicit time zero is preserved; missing time defaults to current time + 0.5, + and negative time is clamped to zero. +- Function and Promise configuration changes use identity. Structurally equal + ordinary values retain the historical JSON comparison behavior. Validate and + normalize a prospective option before replacing the current option; invalid + updates cannot corrupt it. Configuration does not implicitly reload input. +- Keep XML entity order, unpadded hexadecimal colors, numeric conversions, + extension fields and existing mode mapping. An empty XML document is a valid + empty result. Fetch/text failures must reach the public load's single error + outlet and reject it; automatic initial loading observes that rejection. + +## Verification and remaining work + +`yarn test:danmuku` runs frozen historical contracts/failures and candidate input +and parser regressions. The historical helper and archives must remain frozen; +candidate tests use `test/helpers/danmuku-candidate.js` and may select built +artifacts through `ARTPLAYER_DANMUKU_ARTIFACT`. + +`test/browser/danmuku-input.spec.js` uses native video and Blob Workers against +published and candidate cores. Run its source, main and legacy variants after +the normal package build. Browser evidence distinguishes local XML/fault +fixtures from the real Bilibili service, and Windows WebKit from Safari devices. + +The older native track-overlap observation remains open until scheduler and +rendering work is verified. A passing input test is not evidence that duplicate +RAF loops, visibility continuations, settings cleanup, heatmap sampling or +long-run load behavior have been fixed. See PKG-DANMUKU-04/05/07 and the risk +register before changing completion or release status. diff --git a/packages/artplayer-plugin-danmuku/README.md b/packages/artplayer-plugin-danmuku/README.md index 73ca38b98..2eb76011e 100644 --- a/packages/artplayer-plugin-danmuku/README.md +++ b/packages/artplayer-plugin-danmuku/README.md @@ -6,6 +6,28 @@ Danmuku plugin for ArtPlayer [https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-danmuku/index.js&example=danmuku) +## Loading and configuration + +`plugin.load()` reloads the configured input and replaces the queue after input +succeeds. `plugin.load(input)` appends; concurrent append operations remain +independent. A newer replacement supersedes an unfinished older replacement. +Destroy cancels pending loads and prevents late queue changes. Cancelled loads +resolve to the same internal Danmuku instance as successful loads, without a +late `loaded` or `error` event. + +Fetch or response-text failures emit `artplayerPluginDanmuku:error` once and +reject the corresponding public `load()` Promise. Initial automatic loading +observes its rejection and logs a warning. XML Worker startup/runtime failures +use the existing parser locally and release Worker/Blob resources. + +Explicit `time: 0` is preserved. A replacement callback passed to `config()` +takes effect, and invalid configuration leaves the current option intact. +Existing method return identities, getters, input normalization and XML fields +remain compatible. Configuration changes do not automatically reload data. + +See [ARCHITECTURE.md](./ARCHITECTURE.md) for module ownership, compatibility +constraints, tests and the remaining staged TypeScript migration. + ## License MIT © Harvey Zhao diff --git a/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js b/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js index 7869ed8ba..e1a89fc1a 100644 --- a/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js +++ b/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDanmuku=e(),define(function(){return t.artplayerPluginDanmuku})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDanmuku=e()}(this,function(){"use strict";function t(t){switch(t){case 1:case 2:case 3:default:return 0;case 4:return 2;case 5:return 1}}function e(e){if("string"!==typeof e)return[];const i=e.matchAll(/]*?p="(?

[^"]+)"[^>]*>(?.*?)<\/d>/gs);return Array.from(i).map(e=>{const i=e.groups.p.split(",");if(i.length>=8){return{text:e.groups.text.trim().replaceAll(""",'"').replaceAll("'","'").replaceAll("<","<").replaceAll(">",">").replaceAll("&","&"),time:Number(i[0]),mode:t(Number(i[1])),fontSize:Number(i[2]),color:`#${Number(i[3]).toString(16)}`,timestamp:Number(i[4]),pool:Number(i[5]),userID:i[6],rowID:Number(i[7])}}return null}).filter(Boolean)}function i({data:t}){const{xml:i,id:a}=t;if(!a||!i)return;const n=e(i);globalThis.postMessage({danmus:n,id:a})}function a(){const a=`\n ${t.toString()}\n ${e.toString()}\n onmessage = ${i.toString()}\n `,n=new Blob([a],{type:"application/javascript"});return new Worker(URL.createObjectURL(n))}const n='/*!\n * artplayer-plugin-danmuku.js v5.3.0\n * Github: https://github.com/zhw2590582/ArtPlayer\n * (c) 2017-2026 Harvey Zhao\n * Released under the MIT License.\n */\nfunction t({target:t,visibles:e,clientWidth:i,clientHeight:h,marginBottom:n,marginTop:o,antiOverlap:s}){const r=h-n,p=e.filter(e=>e.mode===t.mode&&e.top<=r).sort((t,e)=>t.top-e.top);if(0===p.length)return 2===t.mode?r-t.height:o;if(p.unshift({type:"top",top:0,left:0,right:0,height:o,width:i,speed:0,distance:i}),p.push({type:"bottom",top:r,left:0,right:0,height:n,width:i,speed:0,distance:i}),2===t.mode)for(let g=p.length-2;g>=0;g-=1){const e=p[g],i=p[g+1],h=e.top+e.height;if(i.top-h>=t.height)return i.top-t.height}else for(let g=1;g=t.height)return h}const a=[];for(let g=1;g{const i=Math.min(...e.map(t=>t.right)),h=Math.min(...t.map(t=>t.right));return i*e.length-h*t.length});break;case 1:case 2:a.sort((t,e)=>{const i=Math.max(...e.map(t=>t.width));return Math.max(...t.map(t=>t.width))*t.length-i*e.length})}return a[0][0].top}switch(t.mode){case 0:{const e=a.find(e=>e.every(e=>{if(ie.time}));return e&&e[0]?e[0].top:void 0}case 1:case 2:return}}onmessage=e=>{const{data:i}=e;if(!i.id||!i.type)return;const h=(0,{getDanmuTop:t}[i.type])(i);globalThis.postMessage({result:h,id:i.id})};\n',s="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",n],{type:"text/javascript;charset=utf-8"});function o(t){let e;try{if(e=s&&(self.URL||self.webkitURL).createObjectURL(s),!e)throw"";const i=new Worker(e,{type:"module",name:t?.name});return i.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(e)}),i}catch(i){return new Worker("data:text/javascript;charset=utf-8,"+encodeURIComponent(n),{type:"module",name:t?.name})}}class r{constructor(t,e){const{constructor:i,template:a}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=a.$danmuku,this.$player=a.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.option=r.option,this.states={wait:[],ready:[],emit:[],stop:[]},this.config(e,!0),this.worker=new o,this.start=this.start.bind(this),this.stop=this.stop.bind(this),this.reset=this.reset.bind(this),this.resize=this.resize.bind(this),this.destroy=this.destroy.bind(this),t.on("video:play",this.start),t.on("video:playing",this.start),t.on("video:pause",this.stop),t.on("video:waiting",this.stop),t.on("destroy",this.destroy),t.on("resize",this.resize),this.load()}static get option(){return{danmuku:[],speed:5,margin:[10,"25%"],opacity:1,color:"#FFFFFF",mode:0,modes:[0,1,2],fontSize:25,antiOverlap:!0,synchronousPlayback:!1,mount:void 0,heatmap:!1,width:512,points:[],filter:()=>!0,beforeEmit:()=>!0,beforeVisible:()=>!0,visible:!0,emitter:!0,maxLength:200,lockTime:5,theme:"dark",OPACITY:{},FONT_SIZE:{},MARGIN:{},SPEED:{},COLOR:[]}}static get scheme(){return{danmuku:"array|function|string",speed:"number",margin:"array",opacity:"number",color:"string",mode:"number",modes:"array",fontSize:"number|string",antiOverlap:"boolean",synchronousPlayback:"boolean",mount:"?htmldivelement|string",heatmap:"object|boolean",width:"number",points:"array",filter:"function",beforeEmit:"function",beforeVisible:"function",visible:"boolean",emitter:"boolean",maxLength:"number",lockTime:"number",theme:"string",OPACITY:"object",FONT_SIZE:"object",MARGIN:"object",SPEED:"object",COLOR:"array"}}static get cssText(){return'\n user-select: none;\n position: absolute;\n white-space: pre;\n pointer-events: none;\n perspective: 500px;\n display: inline-block;\n will-change: transform;\n font-weight: normal;\n line-height: 1.125;\n visibility: hidden;\n font-family: SimHei, "Microsoft JhengHei", Arial, Helvetica, sans-serif;\n text-shadow: rgb(0, 0, 0) 1px 0px 1px, rgb(0, 0, 0) 0px 1px 1px, rgb(0, 0, 0) 0px -1px 1px, rgb(0, 0, 0) -1px 0px 1px;\n '}get isRotate(){return this.art.plugins?.autoOrientation?.state}get marginTop(){const{clamp:t}=this.utils,e=this.option.margin[0],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return r.option.margin[0]}get marginBottom(){const{clamp:t}=this.utils,e=this.option.margin[1],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return r.option.margin[1]}get fontSize(){const{clamp:t}=this.utils,{clientHeight:e}=this.$player,i=this.option.fontSize;if("number"===typeof i)return Math.round(t(i,12,e));if("string"===typeof i&&i.endsWith("%")){const a=Number.parseFloat(i)/100;return Math.round(t(e*a,12,e))}return r.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=r.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){const{currentTime:t}=this.art,e=[];return this.filter("ready",t=>e.push(t)),this.filter("wait",i=>{t+.1>=i.time&&i.time>=t-.1&&e.push(i)}),e}get visibles(){const t=[],{clientWidth:e}=this.$player,i=this.getLeft(this.$player);return this.filter("emit",a=>{const n=a.$ref.offsetTop,s=this.getLeft(a.$ref)-i,o=a.$ref.clientHeight,r=a.$ref.clientWidth,l=s+r,d=e-l,p=l/a.$restTime,h={};h.top=n,h.left=s,h.height=o,h.width=r,h.right=d,h.speed=p,h.distance=l,h.time=a.$restTime,h.mode=a.mode,t.push(h)}),t}get speed(){return this.option.synchronousPlayback&&this.art.playbackRate?this.option.speed/Number(this.art.playbackRate):this.option.speed}async load(t){const{errorHandle:i}=this.utils;let n=[];const s=t||this.option.danmuku;try{"function"===typeof s?n=await s():s instanceof Promise?n=await s:"string"===typeof s?n=await(o=s,new Promise(async t=>{const i=await fetch(o),n=await i.text();try{const e=a();e.onmessage=i=>{const{danmus:a,id:n}=i.data;n&&a&&(t(a),e.terminate())},e.postMessage({xml:n,id:Date.now()})}catch(s){console.error("Error parsing Bilibili Danmu:",s);const i=e(n);t(i)}})):Array.isArray(s)&&(n=s),i(Array.isArray(n),"Danmuku need return an array as result"),void 0===t&&(this.reset(),this.queue=[],this.states={wait:[],ready:[],emit:[],stop:[]},this.$refs=[],this.$danmuku.textContent="");for(let t=0;tJSON.stringify(this.option[e])!==JSON.stringify(t[e]))||e?(this.option=Object.assign({},r.option,this.option,t),this.validator(this.option,r.scheme),this.option.mode=i(this.option.mode,0,2),this.option.speed=i(this.option.speed,1,10),this.option.opacity=i(this.option.opacity,0,1),this.option.lockTime=i(this.option.lockTime,1,60),this.option.maxLength=i(this.option.maxLength,1,1e3),this.option.mount=this.option.mount||a,t.fontSize&&this.reset(),this.option.visible?this.show():this.hide(),this.art.emit("artplayerPluginDanmuku:config",this.option),this):this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return new Promise(e=>{t.id=Date.now(),this.worker.postMessage(t),this.worker.onmessage=i=>{const{data:a}=i;a.id===t.id&&e(a)}})}filter(t,e){const i=this.states[t]||[];for(let a=0;ae!==t),t.$state=e,t.$ref&&(t.$ref.dataset.state=e),this.states[e].push(t)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=r.cssText,t.$ref.style.visibility="hidden",t.$ref.style.marginLeft="0px",t.$ref.style.transform="translateX(0px)",t.$ref.style.transition="transform 0s linear 0s",this.$refs.push(t.$ref),t.$ref=null)}update(){const{setStyles:t}=this.utils;return this.timer=window.requestAnimationFrame(async()=>{if(this.art.playing&&!this.isHide){this.filter("emit",t=>{const e=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=e,t.$lastStartTime=Date.now(),t.$restTime<=0&&this.makeWait(t)});const e=this.readys;for(let i=0;i{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.filter("emit",e=>{switch(e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.left=`${t}px`,e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}})}continue(){const{clientWidth:t}=this.$player;return this.filter("stop",e=>{switch(this.setState(e,"emit"),e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}}),this}suspend(){const{clientWidth:t}=this.$player;return this.filter("emit",e=>{switch(this.setState(e,"stop"),e.mode){case 0:{const i=t-(this.getLeft(e.$ref)-this.getLeft(this.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}stop(){return this.isStop=!0,this.suspend(),window.cancelAnimationFrame(this.timer),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){return this.isStop=!1,this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start"),this}reset(){return this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this}show(){return this.isHide=!1,this.$danmuku.style.opacity=1,this.option.visible=!0,this.art.emit("artplayerPluginDanmuku:show"),this}hide(){return this.isHide=!0,this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this}destroy(){this.stop(),this.worker.terminate(),this.art.off("video:play",this.start),this.art.off("video:playing",this.start),this.art.off("video:pause",this.stop),this.art.off("video:waiting",this.stop),this.art.off("resize",this.reset),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}const l={map:(t,e,i,a,n)=>(t-e)*(n-a)/(i-e)+a,range(t,e,i){const a=Math.round(t/i)*i;return Array.from({length:Math.floor((e-t)/i)},(t,e)=>e*i+a)}};function d(t,e,i){const{query:a}=t.constructor.utils;t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},mounted(n){let s=null,o=null;function r(r=[]){if(s=null,o=null,n.innerHTML="",!t.duration||t.option.isLive)return;const d={w:n.offsetWidth,h:n.offsetHeight},p={xMin:0,xMax:d.w,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*d.h),sampling:Math.floor(d.w/100),smoothing:.2,flattening:.2};"object"===typeof i&&Object.assign(p,i);let h=[];if(Array.isArray(r)&&r.length)h=[...r];else{const i=t.duration/d.w;for(let t=0;t<=d.w;t+=p.sampling){const a=e.queue.filter(({time:e})=>e>t*i&&e<=(t+p.sampling)*i).length;h.push([t,a])}}if(0===h.length)return;const u=h[h.length-1],c=u[0],m=u[1];c!==d.w&&h.push([d.w,m]);const g=h.map(t=>t[1]),f=(Math.min(...g)+Math.max(...g))/2;for(let t=0;tf?1+p.scale:1-p.scale)+p.minHeight}const y=(t,e,i,a)=>{const n=function(t,e){const i=e[0]-t[0],a=e[1]-t[1];return{length:Math.sqrt(i**2+a**2),angle:Math.atan2(a,i)}}(e||t,i||t),s=l.map(Math.cos(n.angle)*p.flattening,0,1,1,0),o=n.angle*s+(a?Math.PI:0),r=n.length*p.smoothing;return[t[0]+Math.cos(o)*r,t[1]+Math.sin(o)*r]},v=h.map(t=>[l.map(t[0],p.xMin,p.xMax,0,d.w),l.map(t[1],p.yMin,p.yMax,d.h,0)]).reduce((t,e,i,a)=>0===i?`M ${a[a.length-1][0]},${d.h} L ${e[0]},${d.h} L ${e[0]},${e[1]}`:`${t} ${((t,e,i)=>{const a=y(i[e-1],i[e-2],t),n=y(t,i[e-1],i[e+1],!0),s=e===i.length-1?" z":"";return`C ${a[0]},${a[1]} ${n[0]},${n[1]} ${t[0]},${t[1]}${s}`})(e,i,a)}`,"");n.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,s=a("#heatmap-start",n),o=a("#heatmap-stop",n),s.setAttribute("offset",100*t.played+"%"),o.setAttribute("offset",100*t.played+"%")}t.on("video:timeupdate",()=>{s&&o&&(s.setAttribute("offset",100*t.played+"%"),o.setAttribute("offset",100*t.played+"%"))}),t.on("setBar",(t,e)=>{s&&o&&"played"===t&&(s.setAttribute("offset",100*e+"%"),o.setAttribute("offset",100*e+"%"))}),t.on("ready",()=>r()),t.on("resize",()=>r()),t.on("artplayerPluginDanmuku:loaded",()=>r()),t.on("artplayerPluginDanmuku:points",t=>r(t))}})}const p='',h='',u='',c='',m='',g='',f='',y='',v='',k='',$='',x='';class w{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:a}=t.template;i(a,"display","flex"),this.template={$controlsCenter:a,$mount:a,$danmuku:null,$toggle:null,$config:null,$configPanel:null,$configModes:null,$style:null,$stylePanel:null,$styleModes:null,$colors:null,$opacitySlider:null,$opacityValue:null,$marginSlider:null,$marginValue:null,$fontSizeSlider:null,$fontSizeValue:null,$speedSlider:null,$speedValue:null,$input:null,$send:null},this.slider={opacity:null,margin:null,fontSize:null,speed:null},this.emitting=!1,this.isLock=!1,this.timer=null,this.createTemplate(),this.createSliders(),this.createEvents(),this.mount(this.option.mount),t.on("resize",()=>this.resize()),t.on("fullscreen",t=>this.onFullscreen(t)),t.on("fullscreenWeb",t=>this.onFullscreen(t)),t.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),t.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}static get icons(){return{$on:$,$off:k,$config:u,$style:x,$mode_0_off:c,$mode_0_on:m,$mode_1_off:g,$mode_1_on:f,$mode_2_off:y,$mode_2_on:v,$check_on:h,$check_off:p}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){const{option:t}=this;return`\n

\n ${$}${k}\n
\n
\n ${u}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${c}${m}\n
\u6eda\u52a8
\n
\n
\n ${g}${f}\n
\u9876\u90e8
\n
\n
\n ${y}${v}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${h}${p}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${h}${p}\n \u540c\u6b65\u89c6\u9891\u901f\u5ea6\n
\n
\n
\n \u4e0d\u900f\u660e\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n \u663e\u793a\u533a\u57df\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u5b57\u53f7\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u901f\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n
\n
\n
\n
\n ${x}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${m}\n
\u6eda\u52a8
\n
\n
\n ${f}\n
\u9876\u90e8
\n
\n
\n ${v}\n
\u5e95\u90e8
\n
\n
\n
\n
\n \u989c\u8272\n
\n ${this.COLOR.map(t=>`
`).join("")}\n
\n
\n
\n
\n
\n \n
\u53d1\u9001
\n
\n `}get OPACITY(){return{min:0,max:100,steps:[],...this.option.OPACITY}}get FONT_SIZE(){return{min:12,max:120,steps:[],...this.option.FONT_SIZE}}get MARGIN(){return{min:0,max:3,steps:[{name:"1/4",value:[10,"75%"]},{name:"\u534a\u5c4f",value:[10,"50%"]},{name:"3/4",value:[10,"25%"]},{name:"\u6ee1\u5c4f",value:[10,10]}],...this.option.MARGIN}}get SPEED(){return{min:0,max:4,steps:[{name:"\u6781\u6162",value:10},{name:"\u8f83\u6162",value:7.5,hide:!0},{name:"\u9002\u4e2d",value:5},{name:"\u8f83\u5feb",value:2.5,hide:!0},{name:"\u6781\u5feb",value:1}],...this.option.SPEED}}get COLOR(){return this.option.COLOR.length?this.option.COLOR:["#FE0302","#FF7204","#FFAA02","#FFD302","#FFFF00","#A0EE00","#00CD00","#019899","#4266BE","#89D5FF","#CC0273","#222222","#9B9B9B","#FFFFFF"]}query(t){const{query:e}=this.utils,{$danmuku:i}=this.template;return e(t,i)}append(t,e){const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){const{$player:i}=this.art.template,{$mount:a}=this.template;i.dataset[t]=e,this.outside&&(a.dataset[t]=e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,this.template.$danmuku=i,this.template.$toggle=this.query(".apd-toggle"),this.template.$config=this.query(".apd-config"),this.template.$configPanel=this.query(".apd-config-panel"),this.template.$configModes=this.query(".apd-config-mode .apd-modes"),this.template.$style=this.query(".apd-style"),this.template.$stylePanel=this.query(".apd-style-panel"),this.template.$styleModes=this.query(".apd-style-mode .apd-modes"),this.template.$colors=this.query(".apd-colors"),this.template.$antiOverlap=this.query(".apd-anti-overlap"),this.template.$syncVideo=this.query(".apd-sync-video"),this.template.$opacitySlider=this.query(".apd-config-opacity .apd-slider"),this.template.$opacityValue=this.query(".apd-config-opacity .apd-value"),this.template.$marginSlider=this.query(".apd-config-margin .apd-slider"),this.template.$marginValue=this.query(".apd-config-margin .apd-value"),this.template.$fontSizeSlider=this.query(".apd-config-fontSize .apd-slider"),this.template.$fontSizeValue=this.query(".apd-config-fontSize .apd-value"),this.template.$speedSlider=this.query(".apd-config-speed .apd-slider"),this.template.$speedValue=this.query(".apd-config-speed .apd-value"),this.template.$input=this.query(".apd-input"),this.template.$send=this.query(".apd-send");const{$toggle:a}=this.template;this.art.on("artplayerPluginDanmuku:show",()=>{e(a,"\u5173\u95ed\u5f39\u5e55")}),this.art.on("artplayerPluginDanmuku:hide",()=>{e(a,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:a,$antiOverlap:n,$syncVideo:s,$send:o,$input:r}=this.template;this.art.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.art.proxy(e,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.option.modes.includes(i)?this.danmuku.config({modes:this.option.modes.filter(t=>t!==i)}):this.danmuku.config({modes:[...this.option.modes,i]}),this.reset()}),this.art.proxy(n,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.art.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.art.proxy(i,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.danmuku.config({mode:i}),this.reset()}),this.art.proxy(a,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.art.proxy(o,"click",()=>this.emit()),this.art.proxy(r,"keypress",t=>{"Enter"===t.key&&(t.preventDefault(),this.emit())})}createSliders(){this.slider.opacity=this.createSlider({...this.OPACITY,container:this.template.$opacitySlider,findIndex:()=>Math.round(100*this.option.opacity),onChange:t=>{const{$opacityValue:e}=this.template;e.textContent=`${t}%`,this.danmuku.config({opacity:t/100})}}),this.slider.margin=this.createSlider({...this.MARGIN,container:this.template.$marginSlider,findIndex:()=>this.MARGIN.steps.findIndex(t=>t.value[0]===this.option.margin[0]&&t.value[1]===this.option.margin[1]),onChange:t=>{const e=this.MARGIN.steps[t];if(!e)return;const{$marginValue:i}=this.template;i.textContent=e.name,this.danmuku.config({margin:e.value})}}),this.slider.fontSize=this.createSlider({...this.FONT_SIZE,container:this.template.$fontSizeSlider,findIndex:()=>this.danmuku.fontSize,onChange:t=>{const{$fontSizeValue:e}=this.template;e.textContent=`${t}px`,t!==this.danmuku.fontSize&&this.danmuku.config({fontSize:t})}}),this.slider.speed=this.createSlider({...this.SPEED,container:this.template.$speedSlider,findIndex:()=>this.SPEED.steps.findIndex(t=>t.value===this.option.speed),onChange:t=>{const e=this.SPEED.steps[t];if(!e)return;const{$speedValue:i}=this.template;i.textContent=e.name,this.danmuku.config({speed:e.value})}})}createSlider({min:t,max:e,container:i,findIndex:a,onChange:n,steps:s=[]}){const{query:o,clamp:r,setStyle:l}=this.utils;l(i,"touch-action","none"),i.innerHTML=`\n
\n
\n ${s.map(()=>'
').join("")}\n
\n
\n
\n
\n
\n ${s.map(t=>t.hide?"":`
${t.name}
`).join("")}\n
\n `;const d=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i=a()){if(ie)return;const o=(i-t)/(e-t);d.style.left=100*o+"%",0===s.length&&(p.style.width=d.style.left),n(i)}function c(a){const{top:n,height:s,left:o,width:l}=i.getBoundingClientRect();if(this.art.isRotate){const i=r(a.clientY-n,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=r(a.clientX-o,0,l);u(Math.round(i/l*(e-t)+t))}}return this.art.proxy(i,"click",t=>{c.call(this,t)}),this.art.proxy(i,"pointerdown",t=>{h=0===t.button}),this.art.on("document:pointermove",t=>{h&&c.call(this,t)}),this.art.on("document:pointerup",t=>{h&&(h=!1,c.call(this,t))}),{reset:u}}onFullscreen(t){const{$danmuku:e,$controlsCenter:i,$mount:a}=this.template;this.outside?t?this.append(i,e):this.append(a,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){const{$player:i}=this.art.template,a=t.getBoundingClientRect(),n=e.getBoundingClientRect(),s=i.getBoundingClientRect(),o=n.width/2-a.width/2,r=s.left-(a.left-o),l=a.right+o-s.right;e.style.left=r>0?`${-o+r}px`:l>0?-o-l+"px":-o+"px"}async emit(){const{$input:t}=this.template,e=t.value.trim();if(!e.length)return;if(this.isLock)return;if(this.emitting)return;const i={text:e,mode:this.option.mode,color:this.option.color,time:this.art.currentTime};try{this.emitting=!0;const e=await this.option.beforeEmit(i);if(this.emitting=!1,!0!==e)return;i.border=!0,delete i.time,this.danmuku.emit(i),t.value="",this.lock()}catch(a){console.error("Error emitting danmuku:",a),this.emitting=!1}}lock(){const{addClass:t}=this.utils,{$send:e}=this.template;this.isLock=!0;let i=this.option.lockTime;e.textContent=i,t(e,"apd-lock");const a=()=>{this.timer=setTimeout(()=>{0===i?this.unlock():(i-=1,e.textContent=i,a())},1e3)};a()}unlock(){const{removeClass:t}=this.utils,{$send:e}=this.template;clearTimeout(this.timer),this.isLock=!1,e.textContent="\u53d1\u9001",t(e,"apd-lock")}resize(){if(this.outside)return;if(this.art.fullscreen)return;if(this.art.fullscreenWeb)return;const{$player:t,$controlsCenter:e}=this.art.template,{$danmuku:i}=this.template;this.art.widtht.dataset.color===this.option.color.toUpperCase());s&&t(s,"apd-active"),e(i,this.option.visible?"\u5173\u95ed\u5f39\u5e55":"\u6253\u5f00\u5f39\u5e55"),this.resize()}mount(t){const{errorHandle:e}=this.utils,i="string"===typeof t?document.querySelector(t):t;e(i,`Can not find the mount point: ${t}`),this.append(i,this.template.$danmuku),this.template.$mount=i,this.reset()}}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku";let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(e)}):(document.head||document.documentElement).appendChild(e)),e.textContent='.artplayer-plugin-danmuku{display:flex;position:relative;z-index:99;align-items:center;justify-content:center;font-size:12px;height:32px;width:100%;color:#fff;font-weight:300;flex-shrink:0;gap:10px}.artplayer-plugin-danmuku .apd-icon{cursor:pointer;opacity:.75;transition:all .2s ease;fill:#fff}.artplayer-plugin-danmuku .apd-icon:hover{opacity:1}.artplayer-plugin-danmuku .apd-config{display:flex;position:relative}.artplayer-plugin-danmuku .apd-config .apd-config-panel{position:absolute;bottom:24px;left:0;width:320px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-config-mode,.artplayer-plugin-danmuku .apd-config-slider,.artplayer-plugin-danmuku .apd-config-other,.artplayer-plugin-danmuku .apd-style-mode{margin-bottom:15px}.artplayer-plugin-danmuku .apd-modes{display:flex;align-items:center;margin-top:5px;gap:20px}.artplayer-plugin-danmuku .apd-modes .apd-mode{cursor:pointer;text-align:center}.artplayer-plugin-danmuku .apd-modes .apd-mode:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-config-slider{display:flex;align-items:center;gap:12px}.artplayer-plugin-danmuku .apd-config-slider .apd-value{width:32px;text-align:right}.artplayer-plugin-danmuku .apd-slider{position:relative;flex:1;display:flex;height:20px;align-items:center;justify-content:center;cursor:pointer}.artplayer-plugin-danmuku .apd-slider .apd-slider-line{position:relative;height:2px;width:100%;overflow:hidden;border-radius:3px;background-color:#ffffff40}.artplayer-plugin-danmuku .apd-slider .apd-slider-points{position:absolute;inset:0;display:flex;align-items:center;justify-content:space-between}.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point{width:2px;height:2px;border-radius:50%;background-color:#ffffff80}.artplayer-plugin-danmuku .apd-slider .apd-slider-progress{width:0%;height:100%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-dot{position:absolute;transform:translate(-6px);left:0%;width:12px;height:12px;border-radius:50%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps{display:flex;align-items:center;justify-content:space-between;position:absolute;bottom:-12px;width:calc(100% + 32px);color:#777}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step{flex-shrink:0;width:36px;text-align:center;scale:.95}.artplayer-plugin-danmuku .apd-config-other{display:flex;align-items:center;gap:20px}.artplayer-plugin-danmuku .apd-config-other .apd-check-off,.artplayer-plugin-danmuku .apd-config-other .apd-check-on{width:16px;height:16px}.artplayer-plugin-danmuku .apd-config-other .apd-other{display:flex;align-items:center;cursor:pointer;gap:2px}.artplayer-plugin-danmuku .apd-config-other .apd-other:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-emitter{display:flex;flex:1;align-items:center;height:100%;background-color:#ffffff40;border-radius:5px}.artplayer-plugin-danmuku .apd-style{position:relative;display:flex;align-items:center;justify-content:center}.artplayer-plugin-danmuku .apd-style .apd-style-panel{position:absolute;bottom:24px;left:0;width:200px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-colors{display:flex;flex-wrap:wrap;margin-top:5px;gap:8px}.artplayer-plugin-danmuku .apd-colors .apd-color{width:16px;height:16px;border-radius:2px;cursor:pointer}.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active{border:1px solid black;box-shadow:0 0 0 1px #fff}.artplayer-plugin-danmuku .apd-input{outline:none;height:100%;flex:1;min-width:0;width:auto;border:none;line-height:1;color:#fff;background-color:transparent}.artplayer-plugin-danmuku .apd-input::placeholder{color:#ffffff80}.artplayer-plugin-danmuku .apd-send{display:flex;align-items:center;justify-content:center;height:100%;width:60px;flex-shrink:0;cursor:pointer;text-shadow:none;border-top-right-radius:5px;border-bottom-right-radius:5px;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-send.apd-lock{cursor:not-allowed;color:#666;background-color:#e7e7e7}.art-controls-center .apd-emitter{width:260px;flex:none}.art-fullscreen .artplayer-plugin-danmuku,.art-fullscreen-web .artplayer-plugin-danmuku{height:38px;gap:16px}.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on{width:28px;height:28px}.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter{width:400px;flex:none}.art-video-player>.artplayer-plugin-danmuku{position:absolute;left:0;right:0;bottom:-40px;padding:0 10px}.art-video-player:has(>.artplayer-plugin-danmuku){margin-bottom:40px}[data-danmuku-emitter=false] .apd-emitter{display:none!important}[data-danmuku-emitter=false] .art-controls-center .artplayer-plugin-danmuku{justify-content:flex-end;gap:18px}[data-danmuku-emitter=false].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,[data-danmuku-emitter=false].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku{gap:24px}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-icon{fill:#333}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-emitter{background-color:#f1f2f3}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input{color:#000}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input::placeholder{color:#0000004d}[data-danmuku-visible=false] .apd-toggle-off{display:block}[data-danmuku-visible=false] .apd-toggle-on,[data-danmuku-visible=true] .apd-toggle-off{display:none}[data-danmuku-visible=true] .apd-toggle-on{display:block}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-on{display:none}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-off,[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-on{display:block}[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-off,[data-danmuku-sync-video=false] .apd-sync-video .apd-check-on{display:none}[data-danmuku-sync-video=false] .apd-sync-video .apd-check-off,[data-danmuku-sync-video=true] .apd-sync-video .apd-check-on{display:block}[data-danmuku-sync-video=true] .apd-sync-video .apd-check-off{display:none}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-off{display:block}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-on{display:none}[data-danmuku-mode0=false] .art-danmuku [data-mode="0"]{opacity:0!important}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-off{display:none}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-on{display:block}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"]{color:#00a1d6}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"] path{fill:#00a1d6}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-off{display:block}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-on{display:none}[data-danmuku-mode1=false] .art-danmuku [data-mode="1"]{opacity:0!important}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-off{display:none}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-on{display:block}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"]{color:#00a1d6}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"] path{fill:#00a1d6}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-off{display:block}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-on{display:none}[data-danmuku-mode2=false] .art-danmuku [data-mode="2"]{opacity:0!important}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-off{display:none}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-on{display:block}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"]{color:#00a1d6}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"] path{fill:#00a1d6}'}function b(t){return e=>{const i=new r(e,t),a=new w(e,i);return i.option.heatmap&&d(e,i,i.option.heatmap),{name:"artplayerPluginDanmuku",emit:i.emit.bind(i),load:i.load.bind(i),config:i.config.bind(i),hide:i.hide.bind(i),show:i.show.bind(i),reset:i.reset.bind(i),mount:a.mount.bind(a),get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return b.icons=w.icons,b}); +!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDanmuku=e(),define(function(){return t.artplayerPluginDanmuku})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDanmuku=e()}(this,function(){"use strict";function t(t){return t instanceof Promise||"[object Promise]"===Object.prototype.toString.call(t)}function e(t){switch(t){case 1:case 2:case 3:default:return 0;case 4:return 2;case 5:return 1}}function i(t){if("string"!==typeof t)return[];const i=t.matchAll(/]*?p="(?

[^"]+)"[^>]*>(?.*?)<\/d>/gs);return Array.from(i).map(t=>{const i=t.groups.p.split(",");if(i.length>=8){return{text:t.groups.text.trim().replaceAll(""",'"').replaceAll("'","'").replaceAll("<","<").replaceAll(">",">").replaceAll("&","&"),time:Number(i[0]),mode:e(Number(i[1])),fontSize:Number(i[2]),color:`#${Number(i[3]).toString(16)}`,timestamp:Number(i[4]),pool:Number(i[5]),userID:i[6],rowID:Number(i[7])}}return null}).filter(Boolean)}function a({data:t}){const{xml:e,id:a}=t,n=i(e);globalThis.postMessage({danmus:n,id:a})}let n=0;function s(t,{signal:s,onCancel:o}={}){return new Promise((r,l)=>{let d,p,h,u=!1;const c=++n,m=()=>g(void 0,function(t){if(void 0!==t?.reason)return t.reason;const e=new Error("Bilibili Danmu loading cancelled");return e.name="AbortError",e}(s));function f(){let t,e=!1;try{if(d){const t=d;d=void 0,t.onmessage=null,t.onerror=null,t.onmessageerror=null,t.terminate()}}catch(i){t=i,e=!0}if(void 0!==p){const a=p;p=void 0;try{URL.revokeObjectURL(a)}catch(i){e||(t=i),e=!0}}if(e)throw t}function g(t,e){if(u)return;u=!0;let i=arguments.length>1;s?.removeEventListener("abort",m),h?.();try{f()}catch(a){i||(e=a),i=!0}i?l(e):r(t)}function y(t,e){if(!u){console.error("Error parsing Bilibili Danmu:",e);try{g(i(t))}catch(a){g(void 0,a)}}}s?.aborted?m():(s?.addEventListener("abort",m),h=o?.(m),u?h?.():async function(){const n=s?await fetch(t,{signal:s}):await fetch(t);if(u)return;const o=await n.text();if(!u)try{const t=`\n ${e.toString()}\n ${i.toString()}\n onmessage = ${a.toString()}\n `;if(p=URL.createObjectURL(new Blob([t],{type:"application/javascript"})),u)return void f();if(d=new Worker(p),u)return void f();d.onmessage=t=>{if(u||t.data?.id!==c)return;const{danmus:e}=t.data;Array.isArray(e)?g(e):g(void 0,new Error("Invalid Bilibili Danmu worker response"))},d.onerror=t=>{t.preventDefault?.(),y(o,t.error||t)},d.onmessageerror=t=>y(o,t.error||t),d.postMessage({xml:o,id:c})}catch(r){y(o,r)}}().catch(t=>g(void 0,t)))})}const o=new WeakMap,r=Symbol("cancelled danmuku input");function l(t){return o.has(t)||o.set(t,{closed:!1,tasks:new Set,emitting:[]}),o.get(t)}function d(t){const e=l(t),i=e.emitting[e.emitting.length-1];return!e.closed&&!t.art.isDestroy&&(!i||i.active())}function p(t){const e=l(t);e.closed=!0;for(const i of[...e.tasks])i.cancel()}const h='/*!\n * artplayer-plugin-danmuku.js v5.3.0\n * Github: https://github.com/zhw2590582/ArtPlayer\n * (c) 2017-2026 Harvey Zhao\n * Released under the MIT License.\n */\nfunction t({target:t,visibles:e,clientWidth:i,clientHeight:h,marginBottom:n,marginTop:o,antiOverlap:s}){const r=h-n,p=e.filter(e=>e.mode===t.mode&&e.top<=r).sort((t,e)=>t.top-e.top);if(0===p.length)return 2===t.mode?r-t.height:o;if(p.unshift({type:"top",top:0,left:0,right:0,height:o,width:i,speed:0,distance:i}),p.push({type:"bottom",top:r,left:0,right:0,height:n,width:i,speed:0,distance:i}),2===t.mode)for(let g=p.length-2;g>=0;g-=1){const e=p[g],i=p[g+1],h=e.top+e.height;if(i.top-h>=t.height)return i.top-t.height}else for(let g=1;g=t.height)return h}const a=[];for(let g=1;g{const i=Math.min(...e.map(t=>t.right)),h=Math.min(...t.map(t=>t.right));return i*e.length-h*t.length});break;case 1:case 2:a.sort((t,e)=>{const i=Math.max(...e.map(t=>t.width));return Math.max(...t.map(t=>t.width))*t.length-i*e.length})}return a[0][0].top}switch(t.mode){case 0:{const e=a.find(e=>e.every(e=>{if(ie.time}));return e&&e[0]?e[0].top:void 0}case 1:case 2:return}}onmessage=e=>{const{data:i}=e;if(!i.id||!i.type)return;const h=(0,{getDanmuTop:t}[i.type])(i);globalThis.postMessage({result:h,id:i.id})};\n',u="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",h],{type:"text/javascript;charset=utf-8"});function c(t){let e;try{if(e=u&&(self.URL||self.webkitURL).createObjectURL(u),!e)throw"";const i=new Worker(e,{type:"module",name:t?.name});return i.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(e)}),i}catch(i){return new Worker("data:text/javascript;charset=utf-8,"+encodeURIComponent(h),{type:"module",name:t?.name})}}class m{constructor(t,e){const{constructor:i,template:a}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=a.$danmuku,this.$player=a.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.worker=null,this.option=m.option,this.states={wait:[],ready:[],emit:[],stop:[]},this.start=this.start.bind(this),this.stop=this.stop.bind(this),this.reset=this.reset.bind(this),this.resize=this.resize.bind(this),this.destroy=this.destroy.bind(this),d(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!d(this))return;this.worker=new c}catch(n){throw t.off("destroy",this.destroy),p(this),n}t.on("video:play",this.start),t.on("video:playing",this.start),t.on("video:pause",this.stop),t.on("video:waiting",this.stop),t.on("resize",this.resize),this.load().catch(t=>console.warn("Failed to load initial danmuku:",t))}static get option(){return{danmuku:[],speed:5,margin:[10,"25%"],opacity:1,color:"#FFFFFF",mode:0,modes:[0,1,2],fontSize:25,antiOverlap:!0,synchronousPlayback:!1,mount:void 0,heatmap:!1,width:512,points:[],filter:()=>!0,beforeEmit:()=>!0,beforeVisible:()=>!0,visible:!0,emitter:!0,maxLength:200,lockTime:5,theme:"dark",OPACITY:{},FONT_SIZE:{},MARGIN:{},SPEED:{},COLOR:[]}}static get scheme(){return{danmuku:"array|function|string|promise",speed:"number",margin:"array",opacity:"number",color:"string",mode:"number",modes:"array",fontSize:"number|string",antiOverlap:"boolean",synchronousPlayback:"boolean",mount:"?htmldivelement|string",heatmap:"object|boolean",width:"number",points:"array",filter:"function",beforeEmit:"function",beforeVisible:"function",visible:"boolean",emitter:"boolean",maxLength:"number",lockTime:"number",theme:"string",OPACITY:"object",FONT_SIZE:"object",MARGIN:"object",SPEED:"object",COLOR:"array"}}static get cssText(){return'\n user-select: none;\n position: absolute;\n white-space: pre;\n pointer-events: none;\n perspective: 500px;\n display: inline-block;\n will-change: transform;\n font-weight: normal;\n line-height: 1.125;\n visibility: hidden;\n font-family: SimHei, "Microsoft JhengHei", Arial, Helvetica, sans-serif;\n text-shadow: rgb(0, 0, 0) 1px 0px 1px, rgb(0, 0, 0) 0px 1px 1px, rgb(0, 0, 0) 0px -1px 1px, rgb(0, 0, 0) -1px 0px 1px;\n '}get isRotate(){return this.art.plugins?.autoOrientation?.state}get marginTop(){const{clamp:t}=this.utils,e=this.option.margin[0],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return m.option.margin[0]}get marginBottom(){const{clamp:t}=this.utils,e=this.option.margin[1],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return m.option.margin[1]}get fontSize(){const{clamp:t}=this.utils,{clientHeight:e}=this.$player,i=this.option.fontSize;if("number"===typeof i)return Math.round(t(i,12,e));if("string"===typeof i&&i.endsWith("%")){const a=Number.parseFloat(i)/100;return Math.round(t(e*a,12,e))}return m.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=m.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){const{currentTime:t}=this.art,e=[];return this.filter("ready",t=>e.push(t)),this.filter("wait",i=>{t+.1>=i.time&&i.time>=t-.1&&e.push(i)}),e}get visibles(){const t=[],{clientWidth:e}=this.$player,i=this.getLeft(this.$player);return this.filter("emit",a=>{const n=a.$ref.offsetTop,s=this.getLeft(a.$ref)-i,o=a.$ref.clientHeight,r=a.$ref.clientWidth,l=s+r,d=e-l,p=l/a.$restTime,h={};h.top=n,h.left=s,h.height=o,h.width=r,h.right=d,h.speed=p,h.distance=l,h.time=a.$restTime,h.mode=a.mode,t.push(h)}),t}get speed(){return this.option.synchronousPlayback&&this.art.playbackRate?this.option.speed/Number(this.art.playbackRate):this.option.speed}async load(e){const{errorHandle:i}=this.utils,a=function(t,e){const i=l(t);if(e)for(const r of[...i.tasks])r.replace&&r.cancel();let a,n=i.closed||t.art.isDestroy;const s=new Promise(t=>a=t),o=new Set,d="function"===typeof AbortController?new AbortController:void 0,p={replace:e,signal:d?.signal,active:()=>!n&&!i.closed&&!t.art.isDestroy,onCancel:t=>p.active()?(o.add(t),()=>o.delete(t)):(t(),()=>{}),cancel(){if(!n){n=!0,a(r),d?.abort();for(const t of[...o])t();o.clear()}},wait:t=>Promise.race([t,s]),emit(e){i.emitting.push(p);try{return t.emit(e)}finally{i.emitting.pop()}},finish(){i.tasks.delete(p),o.clear()}};return n?a(r):i.tasks.add(p),p}(this,void 0===e);try{if(!a.active())return this;const n=function(e,i){return"function"===typeof e?{asynchronous:!0,value:e()}:t(e)?{asynchronous:!0,value:e}:"string"===typeof e?{asynchronous:!0,value:s(e,i)}:{asynchronous:!1,value:Array.isArray(e)?e:[]}}(e||this.option.danmuku,a),o=n.asynchronous?await a.wait(n.value):n.value;if(!a.active())return this;if(i(Array.isArray(o),"Danmuku need return an array as result"),void 0===e){if(this.reset(),!a.active())return this;this.queue=[],this.states={wait:[],ready:[],emit:[],stop:[]},this.$refs=[],this.$danmuku.textContent=""}for(let t=0;t{const i=s[e],a=o[e];return"function"===typeof i||"function"===typeof a||t(i)||t(a)?i!==a:JSON.stringify(i)!==JSON.stringify(a)}))&&!i)return this;const r=function(t,e,{defaults:i,validate:a,clamp:n,mount:s}){const o=Object.assign({},i,t,e);return a(o),o.mode=n(o.mode,0,2),o.speed=n(o.speed,1,10),o.opacity=n(o.opacity,0,1),o.lockTime=n(o.lockTime,1,60),o.maxLength=n(o.maxLength,1,1e3),o.mount=o.mount||s,o}(this.option,e,{defaults:m.option,validate:t=>this.validator(t,m.scheme),clamp:a,mount:n});return d(this)?(this.option=r,e.fontSize&&(this.reset(),!d(this))||(this.option.visible?this.show():this.hide(),d(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this):this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return new Promise(e=>{t.id=Date.now(),this.worker.postMessage(t),this.worker.onmessage=i=>{const{data:a}=i;a.id===t.id&&e(a)}})}filter(t,e){const i=this.states[t]||[];for(let a=0;ae!==t),t.$state=e,t.$ref&&(t.$ref.dataset.state=e),this.states[e].push(t)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=m.cssText,t.$ref.style.visibility="hidden",t.$ref.style.marginLeft="0px",t.$ref.style.transform="translateX(0px)",t.$ref.style.transition="transform 0s linear 0s",this.$refs.push(t.$ref),t.$ref=null)}update(){const{setStyles:t}=this.utils;return this.timer=window.requestAnimationFrame(async()=>{if(this.art.playing&&!this.isHide){this.filter("emit",t=>{const e=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=e,t.$lastStartTime=Date.now(),t.$restTime<=0&&this.makeWait(t)});const e=this.readys;for(let i=0;i{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.filter("emit",e=>{switch(e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.left=`${t}px`,e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}})}continue(){const{clientWidth:t}=this.$player;return this.filter("stop",e=>{switch(this.setState(e,"emit"),e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}}),this}suspend(){const{clientWidth:t}=this.$player;return this.filter("emit",e=>{switch(this.setState(e,"stop"),e.mode){case 0:{const i=t-(this.getLeft(e.$ref)-this.getLeft(this.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}stop(){return this.isStop=!0,this.suspend(),window.cancelAnimationFrame(this.timer),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){return this.isStop=!1,this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start"),this}reset(){return this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this}show(){return this.isHide=!1,this.$danmuku.style.opacity=1,this.option.visible=!0,this.art.emit("artplayerPluginDanmuku:show"),this}hide(){return this.isHide=!0,this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this}destroy(){p(this),this.stop(),this.worker?.terminate(),this.art.off("video:play",this.start),this.art.off("video:playing",this.start),this.art.off("video:pause",this.stop),this.art.off("video:waiting",this.stop),this.art.off("resize",this.resize),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}const f={map:(t,e,i,a,n)=>(t-e)*(n-a)/(i-e)+a,range(t,e,i){const a=Math.round(t/i)*i;return Array.from({length:Math.floor((e-t)/i)},(t,e)=>e*i+a)}};function g(t,e,i){const{query:a}=t.constructor.utils;t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},mounted(n){let s=null,o=null;function r(r=[]){if(s=null,o=null,n.innerHTML="",!t.duration||t.option.isLive)return;const l={w:n.offsetWidth,h:n.offsetHeight},d={xMin:0,xMax:l.w,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*l.h),sampling:Math.floor(l.w/100),smoothing:.2,flattening:.2};"object"===typeof i&&Object.assign(d,i);let p=[];if(Array.isArray(r)&&r.length)p=[...r];else{const i=t.duration/l.w;for(let t=0;t<=l.w;t+=d.sampling){const a=e.queue.filter(({time:e})=>e>t*i&&e<=(t+d.sampling)*i).length;p.push([t,a])}}if(0===p.length)return;const h=p[p.length-1],u=h[0],c=h[1];u!==l.w&&p.push([l.w,c]);const m=p.map(t=>t[1]),g=(Math.min(...m)+Math.max(...m))/2;for(let t=0;tg?1+d.scale:1-d.scale)+d.minHeight}const y=(t,e,i,a)=>{const n=function(t,e){const i=e[0]-t[0],a=e[1]-t[1];return{length:Math.sqrt(i**2+a**2),angle:Math.atan2(a,i)}}(e||t,i||t),s=f.map(Math.cos(n.angle)*d.flattening,0,1,1,0),o=n.angle*s+(a?Math.PI:0),r=n.length*d.smoothing;return[t[0]+Math.cos(o)*r,t[1]+Math.sin(o)*r]},v=p.map(t=>[f.map(t[0],d.xMin,d.xMax,0,l.w),f.map(t[1],d.yMin,d.yMax,l.h,0)]).reduce((t,e,i,a)=>0===i?`M ${a[a.length-1][0]},${l.h} L ${e[0]},${l.h} L ${e[0]},${e[1]}`:`${t} ${((t,e,i)=>{const a=y(i[e-1],i[e-2],t),n=y(t,i[e-1],i[e+1],!0),s=e===i.length-1?" z":"";return`C ${a[0]},${a[1]} ${n[0]},${n[1]} ${t[0]},${t[1]}${s}`})(e,i,a)}`,"");n.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,s=a("#heatmap-start",n),o=a("#heatmap-stop",n),s.setAttribute("offset",100*t.played+"%"),o.setAttribute("offset",100*t.played+"%")}t.on("video:timeupdate",()=>{s&&o&&(s.setAttribute("offset",100*t.played+"%"),o.setAttribute("offset",100*t.played+"%"))}),t.on("setBar",(t,e)=>{s&&o&&"played"===t&&(s.setAttribute("offset",100*e+"%"),o.setAttribute("offset",100*e+"%"))}),t.on("ready",()=>r()),t.on("resize",()=>r()),t.on("artplayerPluginDanmuku:loaded",()=>r()),t.on("artplayerPluginDanmuku:points",t=>r(t))}})}const y='',v='',k='',$='',w='',x='',b='',S='',M='',z='',E='',C='';class T{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:a}=t.template;i(a,"display","flex"),this.template={$controlsCenter:a,$mount:a,$danmuku:null,$toggle:null,$config:null,$configPanel:null,$configModes:null,$style:null,$stylePanel:null,$styleModes:null,$colors:null,$opacitySlider:null,$opacityValue:null,$marginSlider:null,$marginValue:null,$fontSizeSlider:null,$fontSizeValue:null,$speedSlider:null,$speedValue:null,$input:null,$send:null},this.slider={opacity:null,margin:null,fontSize:null,speed:null},this.emitting=!1,this.isLock=!1,this.timer=null,this.createTemplate(),this.createSliders(),this.createEvents(),this.mount(this.option.mount),t.on("resize",()=>this.resize()),t.on("fullscreen",t=>this.onFullscreen(t)),t.on("fullscreenWeb",t=>this.onFullscreen(t)),t.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),t.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}static get icons(){return{$on:E,$off:z,$config:k,$style:C,$mode_0_off:$,$mode_0_on:w,$mode_1_off:x,$mode_1_on:b,$mode_2_off:S,$mode_2_on:M,$check_on:v,$check_off:y}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){const{option:t}=this;return`\n

\n ${E}${z}\n
\n
\n ${k}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${$}${w}\n
\u6eda\u52a8
\n
\n
\n ${x}${b}\n
\u9876\u90e8
\n
\n
\n ${S}${M}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${v}${y}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${v}${y}\n \u540c\u6b65\u89c6\u9891\u901f\u5ea6\n
\n
\n
\n \u4e0d\u900f\u660e\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n \u663e\u793a\u533a\u57df\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u5b57\u53f7\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u901f\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n
\n
\n
\n
\n ${C}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${w}\n
\u6eda\u52a8
\n
\n
\n ${b}\n
\u9876\u90e8
\n
\n
\n ${M}\n
\u5e95\u90e8
\n
\n
\n
\n
\n \u989c\u8272\n
\n ${this.COLOR.map(t=>`
`).join("")}\n
\n
\n
\n
\n
\n \n
\u53d1\u9001
\n
\n `}get OPACITY(){return{min:0,max:100,steps:[],...this.option.OPACITY}}get FONT_SIZE(){return{min:12,max:120,steps:[],...this.option.FONT_SIZE}}get MARGIN(){return{min:0,max:3,steps:[{name:"1/4",value:[10,"75%"]},{name:"\u534a\u5c4f",value:[10,"50%"]},{name:"3/4",value:[10,"25%"]},{name:"\u6ee1\u5c4f",value:[10,10]}],...this.option.MARGIN}}get SPEED(){return{min:0,max:4,steps:[{name:"\u6781\u6162",value:10},{name:"\u8f83\u6162",value:7.5,hide:!0},{name:"\u9002\u4e2d",value:5},{name:"\u8f83\u5feb",value:2.5,hide:!0},{name:"\u6781\u5feb",value:1}],...this.option.SPEED}}get COLOR(){return this.option.COLOR.length?this.option.COLOR:["#FE0302","#FF7204","#FFAA02","#FFD302","#FFFF00","#A0EE00","#00CD00","#019899","#4266BE","#89D5FF","#CC0273","#222222","#9B9B9B","#FFFFFF"]}query(t){const{query:e}=this.utils,{$danmuku:i}=this.template;return e(t,i)}append(t,e){const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){const{$player:i}=this.art.template,{$mount:a}=this.template;i.dataset[t]=e,this.outside&&(a.dataset[t]=e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,this.template.$danmuku=i,this.template.$toggle=this.query(".apd-toggle"),this.template.$config=this.query(".apd-config"),this.template.$configPanel=this.query(".apd-config-panel"),this.template.$configModes=this.query(".apd-config-mode .apd-modes"),this.template.$style=this.query(".apd-style"),this.template.$stylePanel=this.query(".apd-style-panel"),this.template.$styleModes=this.query(".apd-style-mode .apd-modes"),this.template.$colors=this.query(".apd-colors"),this.template.$antiOverlap=this.query(".apd-anti-overlap"),this.template.$syncVideo=this.query(".apd-sync-video"),this.template.$opacitySlider=this.query(".apd-config-opacity .apd-slider"),this.template.$opacityValue=this.query(".apd-config-opacity .apd-value"),this.template.$marginSlider=this.query(".apd-config-margin .apd-slider"),this.template.$marginValue=this.query(".apd-config-margin .apd-value"),this.template.$fontSizeSlider=this.query(".apd-config-fontSize .apd-slider"),this.template.$fontSizeValue=this.query(".apd-config-fontSize .apd-value"),this.template.$speedSlider=this.query(".apd-config-speed .apd-slider"),this.template.$speedValue=this.query(".apd-config-speed .apd-value"),this.template.$input=this.query(".apd-input"),this.template.$send=this.query(".apd-send");const{$toggle:a}=this.template;this.art.on("artplayerPluginDanmuku:show",()=>{e(a,"\u5173\u95ed\u5f39\u5e55")}),this.art.on("artplayerPluginDanmuku:hide",()=>{e(a,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:a,$antiOverlap:n,$syncVideo:s,$send:o,$input:r}=this.template;this.art.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.art.proxy(e,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.option.modes.includes(i)?this.danmuku.config({modes:this.option.modes.filter(t=>t!==i)}):this.danmuku.config({modes:[...this.option.modes,i]}),this.reset()}),this.art.proxy(n,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.art.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.art.proxy(i,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.danmuku.config({mode:i}),this.reset()}),this.art.proxy(a,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.art.proxy(o,"click",()=>this.emit()),this.art.proxy(r,"keypress",t=>{"Enter"===t.key&&(t.preventDefault(),this.emit())})}createSliders(){this.slider.opacity=this.createSlider({...this.OPACITY,container:this.template.$opacitySlider,findIndex:()=>Math.round(100*this.option.opacity),onChange:t=>{const{$opacityValue:e}=this.template;e.textContent=`${t}%`,this.danmuku.config({opacity:t/100})}}),this.slider.margin=this.createSlider({...this.MARGIN,container:this.template.$marginSlider,findIndex:()=>this.MARGIN.steps.findIndex(t=>t.value[0]===this.option.margin[0]&&t.value[1]===this.option.margin[1]),onChange:t=>{const e=this.MARGIN.steps[t];if(!e)return;const{$marginValue:i}=this.template;i.textContent=e.name,this.danmuku.config({margin:e.value})}}),this.slider.fontSize=this.createSlider({...this.FONT_SIZE,container:this.template.$fontSizeSlider,findIndex:()=>this.danmuku.fontSize,onChange:t=>{const{$fontSizeValue:e}=this.template;e.textContent=`${t}px`,t!==this.danmuku.fontSize&&this.danmuku.config({fontSize:t})}}),this.slider.speed=this.createSlider({...this.SPEED,container:this.template.$speedSlider,findIndex:()=>this.SPEED.steps.findIndex(t=>t.value===this.option.speed),onChange:t=>{const e=this.SPEED.steps[t];if(!e)return;const{$speedValue:i}=this.template;i.textContent=e.name,this.danmuku.config({speed:e.value})}})}createSlider({min:t,max:e,container:i,findIndex:a,onChange:n,steps:s=[]}){const{query:o,clamp:r,setStyle:l}=this.utils;l(i,"touch-action","none"),i.innerHTML=`\n
\n
\n ${s.map(()=>'
').join("")}\n
\n
\n
\n
\n
\n ${s.map(t=>t.hide?"":`
${t.name}
`).join("")}\n
\n `;const d=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i=a()){if(ie)return;const o=(i-t)/(e-t);d.style.left=100*o+"%",0===s.length&&(p.style.width=d.style.left),n(i)}function c(a){const{top:n,height:s,left:o,width:l}=i.getBoundingClientRect();if(this.art.isRotate){const i=r(a.clientY-n,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=r(a.clientX-o,0,l);u(Math.round(i/l*(e-t)+t))}}return this.art.proxy(i,"click",t=>{c.call(this,t)}),this.art.proxy(i,"pointerdown",t=>{h=0===t.button}),this.art.on("document:pointermove",t=>{h&&c.call(this,t)}),this.art.on("document:pointerup",t=>{h&&(h=!1,c.call(this,t))}),{reset:u}}onFullscreen(t){const{$danmuku:e,$controlsCenter:i,$mount:a}=this.template;this.outside?t?this.append(i,e):this.append(a,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){const{$player:i}=this.art.template,a=t.getBoundingClientRect(),n=e.getBoundingClientRect(),s=i.getBoundingClientRect(),o=n.width/2-a.width/2,r=s.left-(a.left-o),l=a.right+o-s.right;e.style.left=r>0?`${-o+r}px`:l>0?-o-l+"px":-o+"px"}async emit(){const{$input:t}=this.template,e=t.value.trim();if(!e.length)return;if(this.isLock)return;if(this.emitting)return;const i={text:e,mode:this.option.mode,color:this.option.color,time:this.art.currentTime};try{this.emitting=!0;const e=await this.option.beforeEmit(i);if(this.emitting=!1,!0!==e)return;i.border=!0,delete i.time,this.danmuku.emit(i),t.value="",this.lock()}catch(a){console.error("Error emitting danmuku:",a),this.emitting=!1}}lock(){const{addClass:t}=this.utils,{$send:e}=this.template;this.isLock=!0;let i=this.option.lockTime;e.textContent=i,t(e,"apd-lock");const a=()=>{this.timer=setTimeout(()=>{0===i?this.unlock():(i-=1,e.textContent=i,a())},1e3)};a()}unlock(){const{removeClass:t}=this.utils,{$send:e}=this.template;clearTimeout(this.timer),this.isLock=!1,e.textContent="\u53d1\u9001",t(e,"apd-lock")}resize(){if(this.outside)return;if(this.art.fullscreen)return;if(this.art.fullscreenWeb)return;const{$player:t,$controlsCenter:e}=this.art.template,{$danmuku:i}=this.template;this.art.widtht.dataset.color===this.option.color.toUpperCase());s&&t(s,"apd-active"),e(i,this.option.visible?"\u5173\u95ed\u5f39\u5e55":"\u6253\u5f00\u5f39\u5e55"),this.resize()}mount(t){const{errorHandle:e}=this.utils,i="string"===typeof t?document.querySelector(t):t;e(i,`Can not find the mount point: ${t}`),this.append(i,this.template.$danmuku),this.template.$mount=i,this.reset()}}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku";let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(e)}):(document.head||document.documentElement).appendChild(e)),e.textContent='.artplayer-plugin-danmuku{display:flex;position:relative;z-index:99;align-items:center;justify-content:center;font-size:12px;height:32px;width:100%;color:#fff;font-weight:300;flex-shrink:0;gap:10px}.artplayer-plugin-danmuku .apd-icon{cursor:pointer;opacity:.75;transition:all .2s ease;fill:#fff}.artplayer-plugin-danmuku .apd-icon:hover{opacity:1}.artplayer-plugin-danmuku .apd-config{display:flex;position:relative}.artplayer-plugin-danmuku .apd-config .apd-config-panel{position:absolute;bottom:24px;left:0;width:320px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-config-mode,.artplayer-plugin-danmuku .apd-config-slider,.artplayer-plugin-danmuku .apd-config-other,.artplayer-plugin-danmuku .apd-style-mode{margin-bottom:15px}.artplayer-plugin-danmuku .apd-modes{display:flex;align-items:center;margin-top:5px;gap:20px}.artplayer-plugin-danmuku .apd-modes .apd-mode{cursor:pointer;text-align:center}.artplayer-plugin-danmuku .apd-modes .apd-mode:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-config-slider{display:flex;align-items:center;gap:12px}.artplayer-plugin-danmuku .apd-config-slider .apd-value{width:32px;text-align:right}.artplayer-plugin-danmuku .apd-slider{position:relative;flex:1;display:flex;height:20px;align-items:center;justify-content:center;cursor:pointer}.artplayer-plugin-danmuku .apd-slider .apd-slider-line{position:relative;height:2px;width:100%;overflow:hidden;border-radius:3px;background-color:#ffffff40}.artplayer-plugin-danmuku .apd-slider .apd-slider-points{position:absolute;inset:0;display:flex;align-items:center;justify-content:space-between}.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point{width:2px;height:2px;border-radius:50%;background-color:#ffffff80}.artplayer-plugin-danmuku .apd-slider .apd-slider-progress{width:0%;height:100%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-dot{position:absolute;transform:translate(-6px);left:0%;width:12px;height:12px;border-radius:50%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps{display:flex;align-items:center;justify-content:space-between;position:absolute;bottom:-12px;width:calc(100% + 32px);color:#777}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step{flex-shrink:0;width:36px;text-align:center;scale:.95}.artplayer-plugin-danmuku .apd-config-other{display:flex;align-items:center;gap:20px}.artplayer-plugin-danmuku .apd-config-other .apd-check-off,.artplayer-plugin-danmuku .apd-config-other .apd-check-on{width:16px;height:16px}.artplayer-plugin-danmuku .apd-config-other .apd-other{display:flex;align-items:center;cursor:pointer;gap:2px}.artplayer-plugin-danmuku .apd-config-other .apd-other:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-emitter{display:flex;flex:1;align-items:center;height:100%;background-color:#ffffff40;border-radius:5px}.artplayer-plugin-danmuku .apd-style{position:relative;display:flex;align-items:center;justify-content:center}.artplayer-plugin-danmuku .apd-style .apd-style-panel{position:absolute;bottom:24px;left:0;width:200px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-colors{display:flex;flex-wrap:wrap;margin-top:5px;gap:8px}.artplayer-plugin-danmuku .apd-colors .apd-color{width:16px;height:16px;border-radius:2px;cursor:pointer}.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active{border:1px solid black;box-shadow:0 0 0 1px #fff}.artplayer-plugin-danmuku .apd-input{outline:none;height:100%;flex:1;min-width:0;width:auto;border:none;line-height:1;color:#fff;background-color:transparent}.artplayer-plugin-danmuku .apd-input::placeholder{color:#ffffff80}.artplayer-plugin-danmuku .apd-send{display:flex;align-items:center;justify-content:center;height:100%;width:60px;flex-shrink:0;cursor:pointer;text-shadow:none;border-top-right-radius:5px;border-bottom-right-radius:5px;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-send.apd-lock{cursor:not-allowed;color:#666;background-color:#e7e7e7}.art-controls-center .apd-emitter{width:260px;flex:none}.art-fullscreen .artplayer-plugin-danmuku,.art-fullscreen-web .artplayer-plugin-danmuku{height:38px;gap:16px}.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on{width:28px;height:28px}.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter{width:400px;flex:none}.art-video-player>.artplayer-plugin-danmuku{position:absolute;left:0;right:0;bottom:-40px;padding:0 10px}.art-video-player:has(>.artplayer-plugin-danmuku){margin-bottom:40px}[data-danmuku-emitter=false] .apd-emitter{display:none!important}[data-danmuku-emitter=false] .art-controls-center .artplayer-plugin-danmuku{justify-content:flex-end;gap:18px}[data-danmuku-emitter=false].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,[data-danmuku-emitter=false].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku{gap:24px}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-icon{fill:#333}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-emitter{background-color:#f1f2f3}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input{color:#000}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input::placeholder{color:#0000004d}[data-danmuku-visible=false] .apd-toggle-off{display:block}[data-danmuku-visible=false] .apd-toggle-on,[data-danmuku-visible=true] .apd-toggle-off{display:none}[data-danmuku-visible=true] .apd-toggle-on{display:block}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-on{display:none}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-off,[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-on{display:block}[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-off,[data-danmuku-sync-video=false] .apd-sync-video .apd-check-on{display:none}[data-danmuku-sync-video=false] .apd-sync-video .apd-check-off,[data-danmuku-sync-video=true] .apd-sync-video .apd-check-on{display:block}[data-danmuku-sync-video=true] .apd-sync-video .apd-check-off{display:none}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-off{display:block}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-on{display:none}[data-danmuku-mode0=false] .art-danmuku [data-mode="0"]{opacity:0!important}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-off{display:none}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-on{display:block}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"]{color:#00a1d6}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"] path{fill:#00a1d6}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-off{display:block}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-on{display:none}[data-danmuku-mode1=false] .art-danmuku [data-mode="1"]{opacity:0!important}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-off{display:none}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-on{display:block}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"]{color:#00a1d6}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"] path{fill:#00a1d6}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-off{display:block}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-on{display:none}[data-danmuku-mode2=false] .art-danmuku [data-mode="2"]{opacity:0!important}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-off{display:none}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-on{display:block}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"]{color:#00a1d6}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"] path{fill:#00a1d6}'}function F(t){return e=>{const i=new m(e,t),a=e.isDestroy?void 0:new T(e,i);return!e.isDestroy&&i.option.heatmap&&g(e,i,i.option.heatmap),{name:"artplayerPluginDanmuku",emit:i.emit.bind(i),load:i.load.bind(i),config:i.config.bind(i),hide:i.hide.bind(i),show:i.show.bind(i),reset:i.reset.bind(i),mount:a?a.mount.bind(a):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return F.icons=T.icons,F}); diff --git a/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js b/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js index 83a1130ab..db0f66315 100644 --- a/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js +++ b/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js @@ -4,4 +4,4 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ -!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDanmuku=e(),define(function(){return t.artplayerPluginDanmuku})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDanmuku=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.defineProperties,i=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,o=Math.pow,r=(e,i,a)=>i in e?t(e,i,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[i]=a,l=(t,e)=>{for(var i in e||(e={}))n.call(e,i)&&r(t,i,e[i]);if(a)for(var i of a(e))s.call(e,i)&&r(t,i,e[i]);return t},d=(t,a)=>e(t,i(a)),p=(t,e,i)=>new Promise((a,n)=>{var s=t=>{try{r(i.next(t))}catch(e){n(e)}},o=t=>{try{r(i.throw(t))}catch(e){n(e)}},r=t=>t.done?a(t.value):Promise.resolve(t.value).then(s,o);r((i=i.apply(t,e)).next())});function h(t){switch(t){case 1:case 2:case 3:default:return 0;case 4:return 2;case 5:return 1}}function u(t){if("string"!==typeof t)return[];const e=new RegExp(']*?p="(?

[^"]+)"[^>]*>(?.*?)<\\/d>',"gs"),i=t.matchAll(e);return Array.from(i).map(t=>{const e=t.groups.p.split(",");if(e.length>=8){return{text:t.groups.text.trim().replaceAll(""",'"').replaceAll("'","'").replaceAll("<","<").replaceAll(">",">").replaceAll("&","&"),time:Number(e[0]),mode:h(Number(e[1])),fontSize:Number(e[2]),color:`#${Number(e[3]).toString(16)}`,timestamp:Number(e[4]),pool:Number(e[5]),userID:e[6],rowID:Number(e[7])}}return null}).filter(Boolean)}function c({data:t}){const{xml:e,id:i}=t;if(!i||!e)return;const a=u(e);globalThis.postMessage({danmus:a,id:i})}function m(){const t=`\n ${h.toString()}\n ${u.toString()}\n onmessage = ${c.toString()}\n `,e=new Blob([t],{type:"application/javascript"});return new Worker(URL.createObjectURL(e))}const f='/*!\n * artplayer-plugin-danmuku.js v5.3.0\n * Github: https://github.com/zhw2590582/ArtPlayer\n * (c) 2017-2026 Harvey Zhao\n * Released under the MIT License.\n */\nfunction t({target:t,visibles:e,clientWidth:i,clientHeight:h,marginBottom:n,marginTop:o,antiOverlap:s}){const r=h-n,p=e.filter(e=>e.mode===t.mode&&e.top<=r).sort((t,e)=>t.top-e.top);if(0===p.length)return 2===t.mode?r-t.height:o;if(p.unshift({type:"top",top:0,left:0,right:0,height:o,width:i,speed:0,distance:i}),p.push({type:"bottom",top:r,left:0,right:0,height:n,width:i,speed:0,distance:i}),2===t.mode)for(let g=p.length-2;g>=0;g-=1){const e=p[g],i=p[g+1],h=e.top+e.height;if(i.top-h>=t.height)return i.top-t.height}else for(let g=1;g=t.height)return h}const a=[];for(let g=1;g{const i=Math.min(...e.map(t=>t.right)),h=Math.min(...t.map(t=>t.right));return i*e.length-h*t.length});break;case 1:case 2:a.sort((t,e)=>{const i=Math.max(...e.map(t=>t.width));return Math.max(...t.map(t=>t.width))*t.length-i*e.length})}return a[0][0].top}switch(t.mode){case 0:{const e=a.find(e=>e.every(e=>{if(ie.time}));return e&&e[0]?e[0].top:void 0}case 1:case 2:return}}onmessage=e=>{const{data:i}=e;if(!i.id||!i.type)return;const h=(0,{getDanmuTop:t}[i.type])(i);globalThis.postMessage({result:h,id:i.id})};\n',g="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",f],{type:"text/javascript;charset=utf-8"});function y(t){let e;try{if(e=g&&(self.URL||self.webkitURL).createObjectURL(g),!e)throw"";const i=new Worker(e,{type:"module",name:null==t?void 0:t.name});return i.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(e)}),i}catch(i){return new Worker("data:text/javascript;charset=utf-8,"+encodeURIComponent(f),{type:"module",name:null==t?void 0:t.name})}}class v{constructor(t,e){const{constructor:i,template:a}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=a.$danmuku,this.$player=a.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.option=v.option,this.states={wait:[],ready:[],emit:[],stop:[]},this.config(e,!0),this.worker=new y,this.start=this.start.bind(this),this.stop=this.stop.bind(this),this.reset=this.reset.bind(this),this.resize=this.resize.bind(this),this.destroy=this.destroy.bind(this),t.on("video:play",this.start),t.on("video:playing",this.start),t.on("video:pause",this.stop),t.on("video:waiting",this.stop),t.on("destroy",this.destroy),t.on("resize",this.resize),this.load()}static get option(){return{danmuku:[],speed:5,margin:[10,"25%"],opacity:1,color:"#FFFFFF",mode:0,modes:[0,1,2],fontSize:25,antiOverlap:!0,synchronousPlayback:!1,mount:void 0,heatmap:!1,width:512,points:[],filter:()=>!0,beforeEmit:()=>!0,beforeVisible:()=>!0,visible:!0,emitter:!0,maxLength:200,lockTime:5,theme:"dark",OPACITY:{},FONT_SIZE:{},MARGIN:{},SPEED:{},COLOR:[]}}static get scheme(){return{danmuku:"array|function|string",speed:"number",margin:"array",opacity:"number",color:"string",mode:"number",modes:"array",fontSize:"number|string",antiOverlap:"boolean",synchronousPlayback:"boolean",mount:"?htmldivelement|string",heatmap:"object|boolean",width:"number",points:"array",filter:"function",beforeEmit:"function",beforeVisible:"function",visible:"boolean",emitter:"boolean",maxLength:"number",lockTime:"number",theme:"string",OPACITY:"object",FONT_SIZE:"object",MARGIN:"object",SPEED:"object",COLOR:"array"}}static get cssText(){return'\n user-select: none;\n position: absolute;\n white-space: pre;\n pointer-events: none;\n perspective: 500px;\n display: inline-block;\n will-change: transform;\n font-weight: normal;\n line-height: 1.125;\n visibility: hidden;\n font-family: SimHei, "Microsoft JhengHei", Arial, Helvetica, sans-serif;\n text-shadow: rgb(0, 0, 0) 1px 0px 1px, rgb(0, 0, 0) 0px 1px 1px, rgb(0, 0, 0) 0px -1px 1px, rgb(0, 0, 0) -1px 0px 1px;\n '}get isRotate(){var t,e;return null==(e=null==(t=this.art.plugins)?void 0:t.autoOrientation)?void 0:e.state}get marginTop(){const{clamp:t}=this.utils,e=this.option.margin[0],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return v.option.margin[0]}get marginBottom(){const{clamp:t}=this.utils,e=this.option.margin[1],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return v.option.margin[1]}get fontSize(){const{clamp:t}=this.utils,{clientHeight:e}=this.$player,i=this.option.fontSize;if("number"===typeof i)return Math.round(t(i,12,e));if("string"===typeof i&&i.endsWith("%")){const a=Number.parseFloat(i)/100;return Math.round(t(e*a,12,e))}return v.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=v.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){const{currentTime:t}=this.art,e=[];return this.filter("ready",t=>e.push(t)),this.filter("wait",i=>{t+.1>=i.time&&i.time>=t-.1&&e.push(i)}),e}get visibles(){const t=[],{clientWidth:e}=this.$player,i=this.getLeft(this.$player);return this.filter("emit",a=>{const n=a.$ref.offsetTop,s=this.getLeft(a.$ref)-i,o=a.$ref.clientHeight,r=a.$ref.clientWidth,l=s+r,d=e-l,p=l/a.$restTime,h={};h.top=n,h.left=s,h.height=o,h.width=r,h.right=d,h.speed=p,h.distance=l,h.time=a.$restTime,h.mode=a.mode,t.push(h)}),t}get speed(){return this.option.synchronousPlayback&&this.art.playbackRate?this.option.speed/Number(this.art.playbackRate):this.option.speed}load(t){return p(this,null,function*(){const{errorHandle:e}=this.utils;let i=[];const a=t||this.option.danmuku;try{"function"===typeof a?i=yield a():a instanceof Promise?i=yield a:"string"===typeof a?i=yield(n=a,new Promise(t=>p(null,null,function*(){const e=yield fetch(n),i=yield e.text();try{const e=m();e.onmessage=i=>{const{danmus:a,id:n}=i.data;n&&a&&(t(a),e.terminate())},e.postMessage({xml:i,id:Date.now()})}catch(a){console.error("Error parsing Bilibili Danmu:",a);const e=u(i);t(e)}}))):Array.isArray(a)&&(i=a),e(Array.isArray(i),"Danmuku need return an array as result"),void 0===t&&(this.reset(),this.queue=[],this.states={wait:[],ready:[],emit:[],stop:[]},this.$refs=[],this.$danmuku.textContent="");for(let t=0;tJSON.stringify(this.option[e])!==JSON.stringify(t[e]))||e?(this.option=Object.assign({},v.option,this.option,t),this.validator(this.option,v.scheme),this.option.mode=i(this.option.mode,0,2),this.option.speed=i(this.option.speed,1,10),this.option.opacity=i(this.option.opacity,0,1),this.option.lockTime=i(this.option.lockTime,1,60),this.option.maxLength=i(this.option.maxLength,1,1e3),this.option.mount=this.option.mount||a,t.fontSize&&this.reset(),this.option.visible?this.show():this.hide(),this.art.emit("artplayerPluginDanmuku:config",this.option),this):this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return new Promise(e=>{t.id=Date.now(),this.worker.postMessage(t),this.worker.onmessage=i=>{const{data:a}=i;a.id===t.id&&e(a)}})}filter(t,e){const i=this.states[t]||[];for(let a=0;ae!==t),t.$state=e,t.$ref&&(t.$ref.dataset.state=e),this.states[e].push(t)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=v.cssText,t.$ref.style.visibility="hidden",t.$ref.style.marginLeft="0px",t.$ref.style.transform="translateX(0px)",t.$ref.style.transition="transform 0s linear 0s",this.$refs.push(t.$ref),t.$ref=null)}update(){const{setStyles:t}=this.utils;return this.timer=window.requestAnimationFrame(()=>p(this,null,function*(){if(this.art.playing&&!this.isHide){this.filter("emit",t=>{const e=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=e,t.$lastStartTime=Date.now(),t.$restTime<=0&&this.makeWait(t)});const e=this.readys;for(let i=0;i{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.filter("emit",e=>{switch(e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.left=`${t}px`,e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}})}continue(){const{clientWidth:t}=this.$player;return this.filter("stop",e=>{switch(this.setState(e,"emit"),e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}}),this}suspend(){const{clientWidth:t}=this.$player;return this.filter("emit",e=>{switch(this.setState(e,"stop"),e.mode){case 0:{const i=t-(this.getLeft(e.$ref)-this.getLeft(this.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}stop(){return this.isStop=!0,this.suspend(),window.cancelAnimationFrame(this.timer),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){return this.isStop=!1,this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start"),this}reset(){return this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this}show(){return this.isHide=!1,this.$danmuku.style.opacity=1,this.option.visible=!0,this.art.emit("artplayerPluginDanmuku:show"),this}hide(){return this.isHide=!0,this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this}destroy(){this.stop(),this.worker.terminate(),this.art.off("video:play",this.start),this.art.off("video:playing",this.start),this.art.off("video:pause",this.stop),this.art.off("video:waiting",this.stop),this.art.off("resize",this.reset),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}const k={map:(t,e,i,a,n)=>(t-e)*(n-a)/(i-e)+a,range(t,e,i){const a=Math.round(t/i)*i;return Array.from({length:Math.floor((e-t)/i)},(t,e)=>e*i+a)}};function x(t,e,i){const{query:a}=t.constructor.utils;t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},mounted(n){let s=null,r=null;function l(l=[]){if(s=null,r=null,n.innerHTML="",!t.duration||t.option.isLive)return;const d={w:n.offsetWidth,h:n.offsetHeight},p={xMin:0,xMax:d.w,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*d.h),sampling:Math.floor(d.w/100),smoothing:.2,flattening:.2};"object"===typeof i&&Object.assign(p,i);let h=[];if(Array.isArray(l)&&l.length)h=[...l];else{const i=t.duration/d.w;for(let t=0;t<=d.w;t+=p.sampling){const a=e.queue.filter(({time:e})=>e>t*i&&e<=(t+p.sampling)*i).length;h.push([t,a])}}if(0===h.length)return;const u=h[h.length-1],c=u[0],m=u[1];c!==d.w&&h.push([d.w,m]);const f=h.map(t=>t[1]),g=(Math.min(...f)+Math.max(...f))/2;for(let t=0;tg?1+p.scale:1-p.scale)+p.minHeight}const y=(t,e,i,a)=>{const n=function(t,e){const i=e[0]-t[0],a=e[1]-t[1];return{length:Math.sqrt(o(i,2)+o(a,2)),angle:Math.atan2(a,i)}}(e||t,i||t),s=k.map(Math.cos(n.angle)*p.flattening,0,1,1,0),r=n.angle*s+(a?Math.PI:0),l=n.length*p.smoothing;return[t[0]+Math.cos(r)*l,t[1]+Math.sin(r)*l]},v=h.map(t=>[k.map(t[0],p.xMin,p.xMax,0,d.w),k.map(t[1],p.yMin,p.yMax,d.h,0)]).reduce((t,e,i,a)=>0===i?`M ${a[a.length-1][0]},${d.h} L ${e[0]},${d.h} L ${e[0]},${e[1]}`:`${t} ${((t,e,i)=>{const a=y(i[e-1],i[e-2],t),n=y(t,i[e-1],i[e+1],!0),s=e===i.length-1?" z":"";return`C ${a[0]},${a[1]} ${n[0]},${n[1]} ${t[0]},${t[1]}${s}`})(e,i,a)}`,"");n.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,s=a("#heatmap-start",n),r=a("#heatmap-stop",n),s.setAttribute("offset",100*t.played+"%"),r.setAttribute("offset",100*t.played+"%")}t.on("video:timeupdate",()=>{s&&r&&(s.setAttribute("offset",100*t.played+"%"),r.setAttribute("offset",100*t.played+"%"))}),t.on("setBar",(t,e)=>{s&&r&&"played"===t&&(s.setAttribute("offset",100*e+"%"),r.setAttribute("offset",100*e+"%"))}),t.on("ready",()=>l()),t.on("resize",()=>l()),t.on("artplayerPluginDanmuku:loaded",()=>l()),t.on("artplayerPluginDanmuku:points",t=>l(t))}})}const $='',w='',b='',S='',M='',z='',E='',T='',C='',F='',A='',P='';class D{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:a}=t.template;i(a,"display","flex"),this.template={$controlsCenter:a,$mount:a,$danmuku:null,$toggle:null,$config:null,$configPanel:null,$configModes:null,$style:null,$stylePanel:null,$styleModes:null,$colors:null,$opacitySlider:null,$opacityValue:null,$marginSlider:null,$marginValue:null,$fontSizeSlider:null,$fontSizeValue:null,$speedSlider:null,$speedValue:null,$input:null,$send:null},this.slider={opacity:null,margin:null,fontSize:null,speed:null},this.emitting=!1,this.isLock=!1,this.timer=null,this.createTemplate(),this.createSliders(),this.createEvents(),this.mount(this.option.mount),t.on("resize",()=>this.resize()),t.on("fullscreen",t=>this.onFullscreen(t)),t.on("fullscreenWeb",t=>this.onFullscreen(t)),t.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),t.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}static get icons(){return{$on:A,$off:F,$config:b,$style:P,$mode_0_off:S,$mode_0_on:M,$mode_1_off:z,$mode_1_on:E,$mode_2_off:T,$mode_2_on:C,$check_on:w,$check_off:$}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){const{option:t}=this;return`\n

\n ${A}${F}\n
\n
\n ${b}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${S}${M}\n
\u6eda\u52a8
\n
\n
\n ${z}${E}\n
\u9876\u90e8
\n
\n
\n ${T}${C}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${w}${$}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${w}${$}\n \u540c\u6b65\u89c6\u9891\u901f\u5ea6\n
\n
\n
\n \u4e0d\u900f\u660e\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n \u663e\u793a\u533a\u57df\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u5b57\u53f7\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u901f\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n
\n
\n
\n
\n ${P}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${M}\n
\u6eda\u52a8
\n
\n
\n ${E}\n
\u9876\u90e8
\n
\n
\n ${C}\n
\u5e95\u90e8
\n
\n
\n
\n
\n \u989c\u8272\n
\n ${this.COLOR.map(t=>`
`).join("")}\n
\n
\n
\n
\n
\n \n
\u53d1\u9001
\n
\n `}get OPACITY(){return l({min:0,max:100,steps:[]},this.option.OPACITY)}get FONT_SIZE(){return l({min:12,max:120,steps:[]},this.option.FONT_SIZE)}get MARGIN(){return l({min:0,max:3,steps:[{name:"1/4",value:[10,"75%"]},{name:"\u534a\u5c4f",value:[10,"50%"]},{name:"3/4",value:[10,"25%"]},{name:"\u6ee1\u5c4f",value:[10,10]}]},this.option.MARGIN)}get SPEED(){return l({min:0,max:4,steps:[{name:"\u6781\u6162",value:10},{name:"\u8f83\u6162",value:7.5,hide:!0},{name:"\u9002\u4e2d",value:5},{name:"\u8f83\u5feb",value:2.5,hide:!0},{name:"\u6781\u5feb",value:1}]},this.option.SPEED)}get COLOR(){return this.option.COLOR.length?this.option.COLOR:["#FE0302","#FF7204","#FFAA02","#FFD302","#FFFF00","#A0EE00","#00CD00","#019899","#4266BE","#89D5FF","#CC0273","#222222","#9B9B9B","#FFFFFF"]}query(t){const{query:e}=this.utils,{$danmuku:i}=this.template;return e(t,i)}append(t,e){const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){const{$player:i}=this.art.template,{$mount:a}=this.template;i.dataset[t]=e,this.outside&&(a.dataset[t]=e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,this.template.$danmuku=i,this.template.$toggle=this.query(".apd-toggle"),this.template.$config=this.query(".apd-config"),this.template.$configPanel=this.query(".apd-config-panel"),this.template.$configModes=this.query(".apd-config-mode .apd-modes"),this.template.$style=this.query(".apd-style"),this.template.$stylePanel=this.query(".apd-style-panel"),this.template.$styleModes=this.query(".apd-style-mode .apd-modes"),this.template.$colors=this.query(".apd-colors"),this.template.$antiOverlap=this.query(".apd-anti-overlap"),this.template.$syncVideo=this.query(".apd-sync-video"),this.template.$opacitySlider=this.query(".apd-config-opacity .apd-slider"),this.template.$opacityValue=this.query(".apd-config-opacity .apd-value"),this.template.$marginSlider=this.query(".apd-config-margin .apd-slider"),this.template.$marginValue=this.query(".apd-config-margin .apd-value"),this.template.$fontSizeSlider=this.query(".apd-config-fontSize .apd-slider"),this.template.$fontSizeValue=this.query(".apd-config-fontSize .apd-value"),this.template.$speedSlider=this.query(".apd-config-speed .apd-slider"),this.template.$speedValue=this.query(".apd-config-speed .apd-value"),this.template.$input=this.query(".apd-input"),this.template.$send=this.query(".apd-send");const{$toggle:a}=this.template;this.art.on("artplayerPluginDanmuku:show",()=>{e(a,"\u5173\u95ed\u5f39\u5e55")}),this.art.on("artplayerPluginDanmuku:hide",()=>{e(a,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:a,$antiOverlap:n,$syncVideo:s,$send:o,$input:r}=this.template;this.art.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.art.proxy(e,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.option.modes.includes(i)?this.danmuku.config({modes:this.option.modes.filter(t=>t!==i)}):this.danmuku.config({modes:[...this.option.modes,i]}),this.reset()}),this.art.proxy(n,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.art.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.art.proxy(i,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.danmuku.config({mode:i}),this.reset()}),this.art.proxy(a,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.art.proxy(o,"click",()=>this.emit()),this.art.proxy(r,"keypress",t=>{"Enter"===t.key&&(t.preventDefault(),this.emit())})}createSliders(){this.slider.opacity=this.createSlider(d(l({},this.OPACITY),{container:this.template.$opacitySlider,findIndex:()=>Math.round(100*this.option.opacity),onChange:t=>{const{$opacityValue:e}=this.template;e.textContent=`${t}%`,this.danmuku.config({opacity:t/100})}})),this.slider.margin=this.createSlider(d(l({},this.MARGIN),{container:this.template.$marginSlider,findIndex:()=>this.MARGIN.steps.findIndex(t=>t.value[0]===this.option.margin[0]&&t.value[1]===this.option.margin[1]),onChange:t=>{const e=this.MARGIN.steps[t];if(!e)return;const{$marginValue:i}=this.template;i.textContent=e.name,this.danmuku.config({margin:e.value})}})),this.slider.fontSize=this.createSlider(d(l({},this.FONT_SIZE),{container:this.template.$fontSizeSlider,findIndex:()=>this.danmuku.fontSize,onChange:t=>{const{$fontSizeValue:e}=this.template;e.textContent=`${t}px`,t!==this.danmuku.fontSize&&this.danmuku.config({fontSize:t})}})),this.slider.speed=this.createSlider(d(l({},this.SPEED),{container:this.template.$speedSlider,findIndex:()=>this.SPEED.steps.findIndex(t=>t.value===this.option.speed),onChange:t=>{const e=this.SPEED.steps[t];if(!e)return;const{$speedValue:i}=this.template;i.textContent=e.name,this.danmuku.config({speed:e.value})}}))}createSlider({min:t,max:e,container:i,findIndex:a,onChange:n,steps:s=[]}){const{query:o,clamp:r,setStyle:l}=this.utils;l(i,"touch-action","none"),i.innerHTML=`\n
\n
\n ${s.map(()=>'
').join("")}\n
\n
\n
\n
\n
\n ${s.map(t=>t.hide?"":`
${t.name}
`).join("")}\n
\n `;const d=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i=a()){if(ie)return;const o=(i-t)/(e-t);d.style.left=100*o+"%",0===s.length&&(p.style.width=d.style.left),n(i)}function c(a){const{top:n,height:s,left:o,width:l}=i.getBoundingClientRect();if(this.art.isRotate){const i=r(a.clientY-n,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=r(a.clientX-o,0,l);u(Math.round(i/l*(e-t)+t))}}return this.art.proxy(i,"click",t=>{c.call(this,t)}),this.art.proxy(i,"pointerdown",t=>{h=0===t.button}),this.art.on("document:pointermove",t=>{h&&c.call(this,t)}),this.art.on("document:pointerup",t=>{h&&(h=!1,c.call(this,t))}),{reset:u}}onFullscreen(t){const{$danmuku:e,$controlsCenter:i,$mount:a}=this.template;this.outside?t?this.append(i,e):this.append(a,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){const{$player:i}=this.art.template,a=t.getBoundingClientRect(),n=e.getBoundingClientRect(),s=i.getBoundingClientRect(),o=n.width/2-a.width/2,r=s.left-(a.left-o),l=a.right+o-s.right;e.style.left=r>0?`${-o+r}px`:l>0?-o-l+"px":-o+"px"}emit(){return p(this,null,function*(){const{$input:t}=this.template,e=t.value.trim();if(!e.length)return;if(this.isLock)return;if(this.emitting)return;const i={text:e,mode:this.option.mode,color:this.option.color,time:this.art.currentTime};try{this.emitting=!0;const e=yield this.option.beforeEmit(i);if(this.emitting=!1,!0!==e)return;i.border=!0,delete i.time,this.danmuku.emit(i),t.value="",this.lock()}catch(a){console.error("Error emitting danmuku:",a),this.emitting=!1}})}lock(){const{addClass:t}=this.utils,{$send:e}=this.template;this.isLock=!0;let i=this.option.lockTime;e.textContent=i,t(e,"apd-lock");const a=()=>{this.timer=setTimeout(()=>{0===i?this.unlock():(i-=1,e.textContent=i,a())},1e3)};a()}unlock(){const{removeClass:t}=this.utils,{$send:e}=this.template;clearTimeout(this.timer),this.isLock=!1,e.textContent="\u53d1\u9001",t(e,"apd-lock")}resize(){if(this.outside)return;if(this.art.fullscreen)return;if(this.art.fullscreenWeb)return;const{$player:t,$controlsCenter:e}=this.art.template,{$danmuku:i}=this.template;this.art.widtht.dataset.color===this.option.color.toUpperCase());s&&t(s,"apd-active"),e(i,this.option.visible?"\u5173\u95ed\u5f39\u5e55":"\u6253\u5f00\u5f39\u5e55"),this.resize()}mount(t){const{errorHandle:e}=this.utils,i="string"===typeof t?document.querySelector(t):t;e(i,`Can not find the mount point: ${t}`),this.append(i,this.template.$danmuku),this.template.$mount=i,this.reset()}}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku";let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(e)}):(document.head||document.documentElement).appendChild(e)),e.textContent='.artplayer-plugin-danmuku{display:flex;position:relative;z-index:99;align-items:center;justify-content:center;font-size:12px;height:32px;width:100%;color:#fff;font-weight:300;flex-shrink:0;gap:10px}.artplayer-plugin-danmuku .apd-icon{cursor:pointer;opacity:.75;transition:all .2s ease;fill:#fff}.artplayer-plugin-danmuku .apd-icon:hover{opacity:1}.artplayer-plugin-danmuku .apd-config{display:flex;position:relative}.artplayer-plugin-danmuku .apd-config .apd-config-panel{position:absolute;bottom:24px;left:0;width:320px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-config-mode,.artplayer-plugin-danmuku .apd-config-slider,.artplayer-plugin-danmuku .apd-config-other,.artplayer-plugin-danmuku .apd-style-mode{margin-bottom:15px}.artplayer-plugin-danmuku .apd-modes{display:flex;align-items:center;margin-top:5px;gap:20px}.artplayer-plugin-danmuku .apd-modes .apd-mode{cursor:pointer;text-align:center}.artplayer-plugin-danmuku .apd-modes .apd-mode:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-config-slider{display:flex;align-items:center;gap:12px}.artplayer-plugin-danmuku .apd-config-slider .apd-value{width:32px;text-align:right}.artplayer-plugin-danmuku .apd-slider{position:relative;flex:1;display:flex;height:20px;align-items:center;justify-content:center;cursor:pointer}.artplayer-plugin-danmuku .apd-slider .apd-slider-line{position:relative;height:2px;width:100%;overflow:hidden;border-radius:3px;background-color:#ffffff40}.artplayer-plugin-danmuku .apd-slider .apd-slider-points{position:absolute;inset:0;display:flex;align-items:center;justify-content:space-between}.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point{width:2px;height:2px;border-radius:50%;background-color:#ffffff80}.artplayer-plugin-danmuku .apd-slider .apd-slider-progress{width:0%;height:100%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-dot{position:absolute;transform:translate(-6px);left:0%;width:12px;height:12px;border-radius:50%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps{display:flex;align-items:center;justify-content:space-between;position:absolute;bottom:-12px;width:calc(100% + 32px);color:#777}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step{flex-shrink:0;width:36px;text-align:center;scale:.95}.artplayer-plugin-danmuku .apd-config-other{display:flex;align-items:center;gap:20px}.artplayer-plugin-danmuku .apd-config-other .apd-check-off,.artplayer-plugin-danmuku .apd-config-other .apd-check-on{width:16px;height:16px}.artplayer-plugin-danmuku .apd-config-other .apd-other{display:flex;align-items:center;cursor:pointer;gap:2px}.artplayer-plugin-danmuku .apd-config-other .apd-other:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-emitter{display:flex;flex:1;align-items:center;height:100%;background-color:#ffffff40;border-radius:5px}.artplayer-plugin-danmuku .apd-style{position:relative;display:flex;align-items:center;justify-content:center}.artplayer-plugin-danmuku .apd-style .apd-style-panel{position:absolute;bottom:24px;left:0;width:200px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-colors{display:flex;flex-wrap:wrap;margin-top:5px;gap:8px}.artplayer-plugin-danmuku .apd-colors .apd-color{width:16px;height:16px;border-radius:2px;cursor:pointer}.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active{border:1px solid black;box-shadow:0 0 0 1px #fff}.artplayer-plugin-danmuku .apd-input{outline:none;height:100%;flex:1;min-width:0;width:auto;border:none;line-height:1;color:#fff;background-color:transparent}.artplayer-plugin-danmuku .apd-input::placeholder{color:#ffffff80}.artplayer-plugin-danmuku .apd-send{display:flex;align-items:center;justify-content:center;height:100%;width:60px;flex-shrink:0;cursor:pointer;text-shadow:none;border-top-right-radius:5px;border-bottom-right-radius:5px;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-send.apd-lock{cursor:not-allowed;color:#666;background-color:#e7e7e7}.art-controls-center .apd-emitter{width:260px;flex:none}.art-fullscreen .artplayer-plugin-danmuku,.art-fullscreen-web .artplayer-plugin-danmuku{height:38px;gap:16px}.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on{width:28px;height:28px}.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter{width:400px;flex:none}.art-video-player>.artplayer-plugin-danmuku{position:absolute;left:0;right:0;bottom:-40px;padding:0 10px}.art-video-player:has(>.artplayer-plugin-danmuku){margin-bottom:40px}[data-danmuku-emitter=false] .apd-emitter{display:none!important}[data-danmuku-emitter=false] .art-controls-center .artplayer-plugin-danmuku{justify-content:flex-end;gap:18px}[data-danmuku-emitter=false].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,[data-danmuku-emitter=false].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku{gap:24px}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-icon{fill:#333}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-emitter{background-color:#f1f2f3}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input{color:#000}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input::placeholder{color:#0000004d}[data-danmuku-visible=false] .apd-toggle-off{display:block}[data-danmuku-visible=false] .apd-toggle-on,[data-danmuku-visible=true] .apd-toggle-off{display:none}[data-danmuku-visible=true] .apd-toggle-on{display:block}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-on{display:none}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-off,[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-on{display:block}[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-off,[data-danmuku-sync-video=false] .apd-sync-video .apd-check-on{display:none}[data-danmuku-sync-video=false] .apd-sync-video .apd-check-off,[data-danmuku-sync-video=true] .apd-sync-video .apd-check-on{display:block}[data-danmuku-sync-video=true] .apd-sync-video .apd-check-off{display:none}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-off{display:block}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-on{display:none}[data-danmuku-mode0=false] .art-danmuku [data-mode="0"]{opacity:0!important}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-off{display:none}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-on{display:block}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"]{color:#00a1d6}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"] path{fill:#00a1d6}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-off{display:block}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-on{display:none}[data-danmuku-mode1=false] .art-danmuku [data-mode="1"]{opacity:0!important}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-off{display:none}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-on{display:block}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"]{color:#00a1d6}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"] path{fill:#00a1d6}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-off{display:block}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-on{display:none}[data-danmuku-mode2=false] .art-danmuku [data-mode="2"]{opacity:0!important}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-off{display:none}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-on{display:block}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"]{color:#00a1d6}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"] path{fill:#00a1d6}'}function O(t){return e=>{const i=new v(e,t),a=new D(e,i);return i.option.heatmap&&x(e,i,i.option.heatmap),{name:"artplayerPluginDanmuku",emit:i.emit.bind(i),load:i.load.bind(i),config:i.config.bind(i),hide:i.hide.bind(i),show:i.show.bind(i),reset:i.reset.bind(i),mount:a.mount.bind(a),get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return O.icons=D.icons,O}); +!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDanmuku=e(),define(function(){return t.artplayerPluginDanmuku})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDanmuku=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.defineProperties,i=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,o=Math.pow,r=(e,i,a)=>i in e?t(e,i,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[i]=a,l=(t,e)=>{for(var i in e||(e={}))n.call(e,i)&&r(t,i,e[i]);if(a)for(var i of a(e))s.call(e,i)&&r(t,i,e[i]);return t},d=(t,a)=>e(t,i(a)),p=(t,e,i)=>new Promise((a,n)=>{var s=t=>{try{r(i.next(t))}catch(e){n(e)}},o=t=>{try{r(i.throw(t))}catch(e){n(e)}},r=t=>t.done?a(t.value):Promise.resolve(t.value).then(s,o);r((i=i.apply(t,e)).next())});function h(t){return t instanceof Promise||"[object Promise]"===Object.prototype.toString.call(t)}function u(t){switch(t){case 1:case 2:case 3:default:return 0;case 4:return 2;case 5:return 1}}function c(t){if("string"!==typeof t)return[];const e=new RegExp(']*?p="(?

[^"]+)"[^>]*>(?.*?)<\\/d>',"gs"),i=t.matchAll(e);return Array.from(i).map(t=>{const e=t.groups.p.split(",");if(e.length>=8){return{text:t.groups.text.trim().replaceAll(""",'"').replaceAll("'","'").replaceAll("<","<").replaceAll(">",">").replaceAll("&","&"),time:Number(e[0]),mode:u(Number(e[1])),fontSize:Number(e[2]),color:`#${Number(e[3]).toString(16)}`,timestamp:Number(e[4]),pool:Number(e[5]),userID:e[6],rowID:Number(e[7])}}return null}).filter(Boolean)}function m({data:t}){const{xml:e,id:i}=t,a=c(e);globalThis.postMessage({danmus:a,id:i})}let f=0;function g(t,{signal:e,onCancel:i}={}){return new Promise((a,n)=>{let s,o,r,l=!1;const d=++f,h=()=>y(void 0,function(t){if(void 0!==(null==t?void 0:t.reason))return t.reason;const e=new Error("Bilibili Danmu loading cancelled");return e.name="AbortError",e}(e));function g(){let t,e=!1;try{if(s){const t=s;s=void 0,t.onmessage=null,t.onerror=null,t.onmessageerror=null,t.terminate()}}catch(i){t=i,e=!0}if(void 0!==o){const a=o;o=void 0;try{URL.revokeObjectURL(a)}catch(i){e||(t=i),e=!0}}if(e)throw t}function y(t,i){if(l)return;l=!0;let s=arguments.length>1;null==e||e.removeEventListener("abort",h),null==r||r();try{g()}catch(o){s||(i=o),s=!0}s?n(i):a(t)}function v(t,e){if(!l){console.error("Error parsing Bilibili Danmu:",e);try{y(c(t))}catch(i){y(void 0,i)}}}(null==e?void 0:e.aborted)?h():(null==e||e.addEventListener("abort",h),r=null==i?void 0:i(h),l?null==r||r():function(){return p(this,null,function*(){const i=e?yield fetch(t,{signal:e}):yield fetch(t);if(l)return;const a=yield i.text();if(!l)try{const t=`\n ${u.toString()}\n ${c.toString()}\n onmessage = ${m.toString()}\n `;if(o=URL.createObjectURL(new Blob([t],{type:"application/javascript"})),l)return void g();if(s=new Worker(o),l)return void g();s.onmessage=t=>{var e;if(l||(null==(e=t.data)?void 0:e.id)!==d)return;const{danmus:i}=t.data;Array.isArray(i)?y(i):y(void 0,new Error("Invalid Bilibili Danmu worker response"))},s.onerror=t=>{var e;null==(e=t.preventDefault)||e.call(t),v(a,t.error||t)},s.onmessageerror=t=>v(a,t.error||t),s.postMessage({xml:a,id:d})}catch(n){v(a,n)}})}().catch(t=>y(void 0,t)))})}const y=new WeakMap,v=Symbol("cancelled danmuku input");function k(t){return y.has(t)||y.set(t,{closed:!1,tasks:new Set,emitting:[]}),y.get(t)}function x(t){const e=k(t),i=e.emitting[e.emitting.length-1];return!e.closed&&!t.art.isDestroy&&(!i||i.active())}function $(t){const e=k(t);e.closed=!0;for(const i of[...e.tasks])i.cancel()}const w='/*!\n * artplayer-plugin-danmuku.js v5.3.0\n * Github: https://github.com/zhw2590582/ArtPlayer\n * (c) 2017-2026 Harvey Zhao\n * Released under the MIT License.\n */\nfunction t({target:t,visibles:e,clientWidth:i,clientHeight:h,marginBottom:n,marginTop:o,antiOverlap:s}){const r=h-n,p=e.filter(e=>e.mode===t.mode&&e.top<=r).sort((t,e)=>t.top-e.top);if(0===p.length)return 2===t.mode?r-t.height:o;if(p.unshift({type:"top",top:0,left:0,right:0,height:o,width:i,speed:0,distance:i}),p.push({type:"bottom",top:r,left:0,right:0,height:n,width:i,speed:0,distance:i}),2===t.mode)for(let g=p.length-2;g>=0;g-=1){const e=p[g],i=p[g+1],h=e.top+e.height;if(i.top-h>=t.height)return i.top-t.height}else for(let g=1;g=t.height)return h}const a=[];for(let g=1;g{const i=Math.min(...e.map(t=>t.right)),h=Math.min(...t.map(t=>t.right));return i*e.length-h*t.length});break;case 1:case 2:a.sort((t,e)=>{const i=Math.max(...e.map(t=>t.width));return Math.max(...t.map(t=>t.width))*t.length-i*e.length})}return a[0][0].top}switch(t.mode){case 0:{const e=a.find(e=>e.every(e=>{if(ie.time}));return e&&e[0]?e[0].top:void 0}case 1:case 2:return}}onmessage=e=>{const{data:i}=e;if(!i.id||!i.type)return;const h=(0,{getDanmuTop:t}[i.type])(i);globalThis.postMessage({result:h,id:i.id})};\n',b="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",w],{type:"text/javascript;charset=utf-8"});function S(t){let e;try{if(e=b&&(self.URL||self.webkitURL).createObjectURL(b),!e)throw"";const i=new Worker(e,{type:"module",name:null==t?void 0:t.name});return i.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(e)}),i}catch(i){return new Worker("data:text/javascript;charset=utf-8,"+encodeURIComponent(w),{type:"module",name:null==t?void 0:t.name})}}class M{constructor(t,e){const{constructor:i,template:a}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=a.$danmuku,this.$player=a.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.worker=null,this.option=M.option,this.states={wait:[],ready:[],emit:[],stop:[]},this.start=this.start.bind(this),this.stop=this.stop.bind(this),this.reset=this.reset.bind(this),this.resize=this.resize.bind(this),this.destroy=this.destroy.bind(this),x(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!x(this))return;this.worker=new S}catch(n){throw t.off("destroy",this.destroy),$(this),n}t.on("video:play",this.start),t.on("video:playing",this.start),t.on("video:pause",this.stop),t.on("video:waiting",this.stop),t.on("resize",this.resize),this.load().catch(t=>console.warn("Failed to load initial danmuku:",t))}static get option(){return{danmuku:[],speed:5,margin:[10,"25%"],opacity:1,color:"#FFFFFF",mode:0,modes:[0,1,2],fontSize:25,antiOverlap:!0,synchronousPlayback:!1,mount:void 0,heatmap:!1,width:512,points:[],filter:()=>!0,beforeEmit:()=>!0,beforeVisible:()=>!0,visible:!0,emitter:!0,maxLength:200,lockTime:5,theme:"dark",OPACITY:{},FONT_SIZE:{},MARGIN:{},SPEED:{},COLOR:[]}}static get scheme(){return{danmuku:"array|function|string|promise",speed:"number",margin:"array",opacity:"number",color:"string",mode:"number",modes:"array",fontSize:"number|string",antiOverlap:"boolean",synchronousPlayback:"boolean",mount:"?htmldivelement|string",heatmap:"object|boolean",width:"number",points:"array",filter:"function",beforeEmit:"function",beforeVisible:"function",visible:"boolean",emitter:"boolean",maxLength:"number",lockTime:"number",theme:"string",OPACITY:"object",FONT_SIZE:"object",MARGIN:"object",SPEED:"object",COLOR:"array"}}static get cssText(){return'\n user-select: none;\n position: absolute;\n white-space: pre;\n pointer-events: none;\n perspective: 500px;\n display: inline-block;\n will-change: transform;\n font-weight: normal;\n line-height: 1.125;\n visibility: hidden;\n font-family: SimHei, "Microsoft JhengHei", Arial, Helvetica, sans-serif;\n text-shadow: rgb(0, 0, 0) 1px 0px 1px, rgb(0, 0, 0) 0px 1px 1px, rgb(0, 0, 0) 0px -1px 1px, rgb(0, 0, 0) -1px 0px 1px;\n '}get isRotate(){var t,e;return null==(e=null==(t=this.art.plugins)?void 0:t.autoOrientation)?void 0:e.state}get marginTop(){const{clamp:t}=this.utils,e=this.option.margin[0],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return M.option.margin[0]}get marginBottom(){const{clamp:t}=this.utils,e=this.option.margin[1],{clientHeight:i}=this.$player;if("number"===typeof e)return t(e,0,i);if("string"===typeof e&&e.endsWith("%")){return t(i*(Number.parseFloat(e)/100),0,i)}return M.option.margin[1]}get fontSize(){const{clamp:t}=this.utils,{clientHeight:e}=this.$player,i=this.option.fontSize;if("number"===typeof i)return Math.round(t(i,12,e));if("string"===typeof i&&i.endsWith("%")){const a=Number.parseFloat(i)/100;return Math.round(t(e*a,12,e))}return M.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=M.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){const{currentTime:t}=this.art,e=[];return this.filter("ready",t=>e.push(t)),this.filter("wait",i=>{t+.1>=i.time&&i.time>=t-.1&&e.push(i)}),e}get visibles(){const t=[],{clientWidth:e}=this.$player,i=this.getLeft(this.$player);return this.filter("emit",a=>{const n=a.$ref.offsetTop,s=this.getLeft(a.$ref)-i,o=a.$ref.clientHeight,r=a.$ref.clientWidth,l=s+r,d=e-l,p=l/a.$restTime,h={};h.top=n,h.left=s,h.height=o,h.width=r,h.right=d,h.speed=p,h.distance=l,h.time=a.$restTime,h.mode=a.mode,t.push(h)}),t}get speed(){return this.option.synchronousPlayback&&this.art.playbackRate?this.option.speed/Number(this.art.playbackRate):this.option.speed}load(t){return p(this,null,function*(){const{errorHandle:e}=this.utils,i=function(t,e){const i=k(t);if(e)for(const d of[...i.tasks])d.replace&&d.cancel();let a,n=i.closed||t.art.isDestroy;const s=new Promise(t=>a=t),o=new Set,r="function"===typeof AbortController?new AbortController:void 0,l={replace:e,signal:null==r?void 0:r.signal,active:()=>!n&&!i.closed&&!t.art.isDestroy,onCancel:t=>l.active()?(o.add(t),()=>o.delete(t)):(t(),()=>{}),cancel(){if(!n){n=!0,a(v),null==r||r.abort();for(const t of[...o])t();o.clear()}},wait:t=>Promise.race([t,s]),emit(e){i.emitting.push(l);try{return t.emit(e)}finally{i.emitting.pop()}},finish(){i.tasks.delete(l),o.clear()}};return n?a(v):i.tasks.add(l),l}(this,void 0===t);try{if(!i.active())return this;const a=function(t,e){return"function"===typeof t?{asynchronous:!0,value:t()}:h(t)?{asynchronous:!0,value:t}:"string"===typeof t?{asynchronous:!0,value:g(t,e)}:{asynchronous:!1,value:Array.isArray(t)?t:[]}}(t||this.option.danmuku,i),n=a.asynchronous?yield i.wait(a.value):a.value;if(!i.active())return this;if(e(Array.isArray(n),"Danmuku need return an array as result"),void 0===t){if(this.reset(),!i.active())return this;this.queue=[],this.states={wait:[],ready:[],emit:[],stop:[]},this.$refs=[],this.$danmuku.textContent=""}for(let t=0;t{const e=n[t],i=s[t];return"function"===typeof e||"function"===typeof i||h(e)||h(i)?e!==i:JSON.stringify(e)!==JSON.stringify(i)}))&&!e)return this;const o=function(t,e,{defaults:i,validate:a,clamp:n,mount:s}){const o=Object.assign({},i,t,e);return a(o),o.mode=n(o.mode,0,2),o.speed=n(o.speed,1,10),o.opacity=n(o.opacity,0,1),o.lockTime=n(o.lockTime,1,60),o.maxLength=n(o.maxLength,1,1e3),o.mount=o.mount||s,o}(this.option,t,{defaults:M.option,validate:t=>this.validator(t,M.scheme),clamp:i,mount:a});return x(this)?(this.option=o,t.fontSize&&(this.reset(),!x(this))||(this.option.visible?this.show():this.hide(),x(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this):this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return new Promise(e=>{t.id=Date.now(),this.worker.postMessage(t),this.worker.onmessage=i=>{const{data:a}=i;a.id===t.id&&e(a)}})}filter(t,e){const i=this.states[t]||[];for(let a=0;ae!==t),t.$state=e,t.$ref&&(t.$ref.dataset.state=e),this.states[e].push(t)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=M.cssText,t.$ref.style.visibility="hidden",t.$ref.style.marginLeft="0px",t.$ref.style.transform="translateX(0px)",t.$ref.style.transition="transform 0s linear 0s",this.$refs.push(t.$ref),t.$ref=null)}update(){const{setStyles:t}=this.utils;return this.timer=window.requestAnimationFrame(()=>p(this,null,function*(){if(this.art.playing&&!this.isHide){this.filter("emit",t=>{const e=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=e,t.$lastStartTime=Date.now(),t.$restTime<=0&&this.makeWait(t)});const e=this.readys;for(let i=0;i{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.filter("emit",e=>{switch(e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.left=`${t}px`,e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}})}continue(){const{clientWidth:t}=this.$player;return this.filter("stop",e=>{switch(this.setState(e,"emit"),e.$lastStartTime=Date.now(),e.mode){case 0:{const i=t+e.$ref.clientWidth;e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition=`transform ${e.$restTime}s linear 0s`;break}}}),this}suspend(){const{clientWidth:t}=this.$player;return this.filter("emit",e=>{switch(this.setState(e,"stop"),e.mode){case 0:{const i=t-(this.getLeft(e.$ref)-this.getLeft(this.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}stop(){return this.isStop=!0,this.suspend(),window.cancelAnimationFrame(this.timer),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){return this.isStop=!1,this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start"),this}reset(){return this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this}show(){return this.isHide=!1,this.$danmuku.style.opacity=1,this.option.visible=!0,this.art.emit("artplayerPluginDanmuku:show"),this}hide(){return this.isHide=!0,this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this}destroy(){var t;$(this),this.stop(),null==(t=this.worker)||t.terminate(),this.art.off("video:play",this.start),this.art.off("video:playing",this.start),this.art.off("video:pause",this.stop),this.art.off("video:waiting",this.stop),this.art.off("resize",this.resize),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}const z={map:(t,e,i,a,n)=>(t-e)*(n-a)/(i-e)+a,range(t,e,i){const a=Math.round(t/i)*i;return Array.from({length:Math.floor((e-t)/i)},(t,e)=>e*i+a)}};function E(t,e,i){const{query:a}=t.constructor.utils;t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},mounted(n){let s=null,r=null;function l(l=[]){if(s=null,r=null,n.innerHTML="",!t.duration||t.option.isLive)return;const d={w:n.offsetWidth,h:n.offsetHeight},p={xMin:0,xMax:d.w,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*d.h),sampling:Math.floor(d.w/100),smoothing:.2,flattening:.2};"object"===typeof i&&Object.assign(p,i);let h=[];if(Array.isArray(l)&&l.length)h=[...l];else{const i=t.duration/d.w;for(let t=0;t<=d.w;t+=p.sampling){const a=e.queue.filter(({time:e})=>e>t*i&&e<=(t+p.sampling)*i).length;h.push([t,a])}}if(0===h.length)return;const u=h[h.length-1],c=u[0],m=u[1];c!==d.w&&h.push([d.w,m]);const f=h.map(t=>t[1]),g=(Math.min(...f)+Math.max(...f))/2;for(let t=0;tg?1+p.scale:1-p.scale)+p.minHeight}const y=(t,e,i,a)=>{const n=function(t,e){const i=e[0]-t[0],a=e[1]-t[1];return{length:Math.sqrt(o(i,2)+o(a,2)),angle:Math.atan2(a,i)}}(e||t,i||t),s=z.map(Math.cos(n.angle)*p.flattening,0,1,1,0),r=n.angle*s+(a?Math.PI:0),l=n.length*p.smoothing;return[t[0]+Math.cos(r)*l,t[1]+Math.sin(r)*l]},v=h.map(t=>[z.map(t[0],p.xMin,p.xMax,0,d.w),z.map(t[1],p.yMin,p.yMax,d.h,0)]).reduce((t,e,i,a)=>0===i?`M ${a[a.length-1][0]},${d.h} L ${e[0]},${d.h} L ${e[0]},${e[1]}`:`${t} ${((t,e,i)=>{const a=y(i[e-1],i[e-2],t),n=y(t,i[e-1],i[e+1],!0),s=e===i.length-1?" z":"";return`C ${a[0]},${a[1]} ${n[0]},${n[1]} ${t[0]},${t[1]}${s}`})(e,i,a)}`,"");n.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,s=a("#heatmap-start",n),r=a("#heatmap-stop",n),s.setAttribute("offset",100*t.played+"%"),r.setAttribute("offset",100*t.played+"%")}t.on("video:timeupdate",()=>{s&&r&&(s.setAttribute("offset",100*t.played+"%"),r.setAttribute("offset",100*t.played+"%"))}),t.on("setBar",(t,e)=>{s&&r&&"played"===t&&(s.setAttribute("offset",100*e+"%"),r.setAttribute("offset",100*e+"%"))}),t.on("ready",()=>l()),t.on("resize",()=>l()),t.on("artplayerPluginDanmuku:loaded",()=>l()),t.on("artplayerPluginDanmuku:points",t=>l(t))}})}const C='',T='',F='',A='',D='',P='',L='',O='',H='',V='',j='',R='';class B{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:a}=t.template;i(a,"display","flex"),this.template={$controlsCenter:a,$mount:a,$danmuku:null,$toggle:null,$config:null,$configPanel:null,$configModes:null,$style:null,$stylePanel:null,$styleModes:null,$colors:null,$opacitySlider:null,$opacityValue:null,$marginSlider:null,$marginValue:null,$fontSizeSlider:null,$fontSizeValue:null,$speedSlider:null,$speedValue:null,$input:null,$send:null},this.slider={opacity:null,margin:null,fontSize:null,speed:null},this.emitting=!1,this.isLock=!1,this.timer=null,this.createTemplate(),this.createSliders(),this.createEvents(),this.mount(this.option.mount),t.on("resize",()=>this.resize()),t.on("fullscreen",t=>this.onFullscreen(t)),t.on("fullscreenWeb",t=>this.onFullscreen(t)),t.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),t.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}static get icons(){return{$on:j,$off:V,$config:F,$style:R,$mode_0_off:A,$mode_0_on:D,$mode_1_off:P,$mode_1_on:L,$mode_2_off:O,$mode_2_on:H,$check_on:T,$check_off:C}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){const{option:t}=this;return`\n

\n ${j}${V}\n
\n
\n ${F}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${A}${D}\n
\u6eda\u52a8
\n
\n
\n ${P}${L}\n
\u9876\u90e8
\n
\n
\n ${O}${H}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${T}${C}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${T}${C}\n \u540c\u6b65\u89c6\u9891\u901f\u5ea6\n
\n
\n
\n \u4e0d\u900f\u660e\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n \u663e\u793a\u533a\u57df\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u5b57\u53f7\n
\n
\u672a\u77e5
\n
\n
\n \u5f39\u5e55\u901f\u5ea6\n
\n
\u672a\u77e5
\n
\n
\n
\n
\n
\n
\n ${R}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${D}\n
\u6eda\u52a8
\n
\n
\n ${L}\n
\u9876\u90e8
\n
\n
\n ${H}\n
\u5e95\u90e8
\n
\n
\n
\n
\n \u989c\u8272\n
\n ${this.COLOR.map(t=>`
`).join("")}\n
\n
\n
\n
\n
\n \n
\u53d1\u9001
\n
\n `}get OPACITY(){return l({min:0,max:100,steps:[]},this.option.OPACITY)}get FONT_SIZE(){return l({min:12,max:120,steps:[]},this.option.FONT_SIZE)}get MARGIN(){return l({min:0,max:3,steps:[{name:"1/4",value:[10,"75%"]},{name:"\u534a\u5c4f",value:[10,"50%"]},{name:"3/4",value:[10,"25%"]},{name:"\u6ee1\u5c4f",value:[10,10]}]},this.option.MARGIN)}get SPEED(){return l({min:0,max:4,steps:[{name:"\u6781\u6162",value:10},{name:"\u8f83\u6162",value:7.5,hide:!0},{name:"\u9002\u4e2d",value:5},{name:"\u8f83\u5feb",value:2.5,hide:!0},{name:"\u6781\u5feb",value:1}]},this.option.SPEED)}get COLOR(){return this.option.COLOR.length?this.option.COLOR:["#FE0302","#FF7204","#FFAA02","#FFD302","#FFFF00","#A0EE00","#00CD00","#019899","#4266BE","#89D5FF","#CC0273","#222222","#9B9B9B","#FFFFFF"]}query(t){const{query:e}=this.utils,{$danmuku:i}=this.template;return e(t,i)}append(t,e){const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){const{$player:i}=this.art.template,{$mount:a}=this.template;i.dataset[t]=e,this.outside&&(a.dataset[t]=e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,this.template.$danmuku=i,this.template.$toggle=this.query(".apd-toggle"),this.template.$config=this.query(".apd-config"),this.template.$configPanel=this.query(".apd-config-panel"),this.template.$configModes=this.query(".apd-config-mode .apd-modes"),this.template.$style=this.query(".apd-style"),this.template.$stylePanel=this.query(".apd-style-panel"),this.template.$styleModes=this.query(".apd-style-mode .apd-modes"),this.template.$colors=this.query(".apd-colors"),this.template.$antiOverlap=this.query(".apd-anti-overlap"),this.template.$syncVideo=this.query(".apd-sync-video"),this.template.$opacitySlider=this.query(".apd-config-opacity .apd-slider"),this.template.$opacityValue=this.query(".apd-config-opacity .apd-value"),this.template.$marginSlider=this.query(".apd-config-margin .apd-slider"),this.template.$marginValue=this.query(".apd-config-margin .apd-value"),this.template.$fontSizeSlider=this.query(".apd-config-fontSize .apd-slider"),this.template.$fontSizeValue=this.query(".apd-config-fontSize .apd-value"),this.template.$speedSlider=this.query(".apd-config-speed .apd-slider"),this.template.$speedValue=this.query(".apd-config-speed .apd-value"),this.template.$input=this.query(".apd-input"),this.template.$send=this.query(".apd-send");const{$toggle:a}=this.template;this.art.on("artplayerPluginDanmuku:show",()=>{e(a,"\u5173\u95ed\u5f39\u5e55")}),this.art.on("artplayerPluginDanmuku:hide",()=>{e(a,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:a,$antiOverlap:n,$syncVideo:s,$send:o,$input:r}=this.template;this.art.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.art.proxy(e,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.option.modes.includes(i)?this.danmuku.config({modes:this.option.modes.filter(t=>t!==i)}):this.danmuku.config({modes:[...this.option.modes,i]}),this.reset()}),this.art.proxy(n,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.art.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.art.proxy(i,"click",t=>{const e=t.target.closest(".apd-mode");if(!e)return;const i=Number(e.dataset.mode);this.danmuku.config({mode:i}),this.reset()}),this.art.proxy(a,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.art.proxy(o,"click",()=>this.emit()),this.art.proxy(r,"keypress",t=>{"Enter"===t.key&&(t.preventDefault(),this.emit())})}createSliders(){this.slider.opacity=this.createSlider(d(l({},this.OPACITY),{container:this.template.$opacitySlider,findIndex:()=>Math.round(100*this.option.opacity),onChange:t=>{const{$opacityValue:e}=this.template;e.textContent=`${t}%`,this.danmuku.config({opacity:t/100})}})),this.slider.margin=this.createSlider(d(l({},this.MARGIN),{container:this.template.$marginSlider,findIndex:()=>this.MARGIN.steps.findIndex(t=>t.value[0]===this.option.margin[0]&&t.value[1]===this.option.margin[1]),onChange:t=>{const e=this.MARGIN.steps[t];if(!e)return;const{$marginValue:i}=this.template;i.textContent=e.name,this.danmuku.config({margin:e.value})}})),this.slider.fontSize=this.createSlider(d(l({},this.FONT_SIZE),{container:this.template.$fontSizeSlider,findIndex:()=>this.danmuku.fontSize,onChange:t=>{const{$fontSizeValue:e}=this.template;e.textContent=`${t}px`,t!==this.danmuku.fontSize&&this.danmuku.config({fontSize:t})}})),this.slider.speed=this.createSlider(d(l({},this.SPEED),{container:this.template.$speedSlider,findIndex:()=>this.SPEED.steps.findIndex(t=>t.value===this.option.speed),onChange:t=>{const e=this.SPEED.steps[t];if(!e)return;const{$speedValue:i}=this.template;i.textContent=e.name,this.danmuku.config({speed:e.value})}}))}createSlider({min:t,max:e,container:i,findIndex:a,onChange:n,steps:s=[]}){const{query:o,clamp:r,setStyle:l}=this.utils;l(i,"touch-action","none"),i.innerHTML=`\n
\n
\n ${s.map(()=>'
').join("")}\n
\n
\n
\n
\n
\n ${s.map(t=>t.hide?"":`
${t.name}
`).join("")}\n
\n `;const d=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i=a()){if(ie)return;const o=(i-t)/(e-t);d.style.left=100*o+"%",0===s.length&&(p.style.width=d.style.left),n(i)}function c(a){const{top:n,height:s,left:o,width:l}=i.getBoundingClientRect();if(this.art.isRotate){const i=r(a.clientY-n,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=r(a.clientX-o,0,l);u(Math.round(i/l*(e-t)+t))}}return this.art.proxy(i,"click",t=>{c.call(this,t)}),this.art.proxy(i,"pointerdown",t=>{h=0===t.button}),this.art.on("document:pointermove",t=>{h&&c.call(this,t)}),this.art.on("document:pointerup",t=>{h&&(h=!1,c.call(this,t))}),{reset:u}}onFullscreen(t){const{$danmuku:e,$controlsCenter:i,$mount:a}=this.template;this.outside?t?this.append(i,e):this.append(a,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){const{$player:i}=this.art.template,a=t.getBoundingClientRect(),n=e.getBoundingClientRect(),s=i.getBoundingClientRect(),o=n.width/2-a.width/2,r=s.left-(a.left-o),l=a.right+o-s.right;e.style.left=r>0?`${-o+r}px`:l>0?-o-l+"px":-o+"px"}emit(){return p(this,null,function*(){const{$input:t}=this.template,e=t.value.trim();if(!e.length)return;if(this.isLock)return;if(this.emitting)return;const i={text:e,mode:this.option.mode,color:this.option.color,time:this.art.currentTime};try{this.emitting=!0;const e=yield this.option.beforeEmit(i);if(this.emitting=!1,!0!==e)return;i.border=!0,delete i.time,this.danmuku.emit(i),t.value="",this.lock()}catch(a){console.error("Error emitting danmuku:",a),this.emitting=!1}})}lock(){const{addClass:t}=this.utils,{$send:e}=this.template;this.isLock=!0;let i=this.option.lockTime;e.textContent=i,t(e,"apd-lock");const a=()=>{this.timer=setTimeout(()=>{0===i?this.unlock():(i-=1,e.textContent=i,a())},1e3)};a()}unlock(){const{removeClass:t}=this.utils,{$send:e}=this.template;clearTimeout(this.timer),this.isLock=!1,e.textContent="\u53d1\u9001",t(e,"apd-lock")}resize(){if(this.outside)return;if(this.art.fullscreen)return;if(this.art.fullscreenWeb)return;const{$player:t,$controlsCenter:e}=this.art.template,{$danmuku:i}=this.template;this.art.widtht.dataset.color===this.option.color.toUpperCase());s&&t(s,"apd-active"),e(i,this.option.visible?"\u5173\u95ed\u5f39\u5e55":"\u6253\u5f00\u5f39\u5e55"),this.resize()}mount(t){const{errorHandle:e}=this.utils,i="string"===typeof t?document.querySelector(t):t;e(i,`Can not find the mount point: ${t}`),this.append(i,this.template.$danmuku),this.template.$mount=i,this.reset()}}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku";let e=document.getElementById(t);e||(e=document.createElement("style"),e.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(e)}):(document.head||document.documentElement).appendChild(e)),e.textContent='.artplayer-plugin-danmuku{display:flex;position:relative;z-index:99;align-items:center;justify-content:center;font-size:12px;height:32px;width:100%;color:#fff;font-weight:300;flex-shrink:0;gap:10px}.artplayer-plugin-danmuku .apd-icon{cursor:pointer;opacity:.75;transition:all .2s ease;fill:#fff}.artplayer-plugin-danmuku .apd-icon:hover{opacity:1}.artplayer-plugin-danmuku .apd-config{display:flex;position:relative}.artplayer-plugin-danmuku .apd-config .apd-config-panel{position:absolute;bottom:24px;left:0;width:320px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-config-mode,.artplayer-plugin-danmuku .apd-config-slider,.artplayer-plugin-danmuku .apd-config-other,.artplayer-plugin-danmuku .apd-style-mode{margin-bottom:15px}.artplayer-plugin-danmuku .apd-modes{display:flex;align-items:center;margin-top:5px;gap:20px}.artplayer-plugin-danmuku .apd-modes .apd-mode{cursor:pointer;text-align:center}.artplayer-plugin-danmuku .apd-modes .apd-mode:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-config-slider{display:flex;align-items:center;gap:12px}.artplayer-plugin-danmuku .apd-config-slider .apd-value{width:32px;text-align:right}.artplayer-plugin-danmuku .apd-slider{position:relative;flex:1;display:flex;height:20px;align-items:center;justify-content:center;cursor:pointer}.artplayer-plugin-danmuku .apd-slider .apd-slider-line{position:relative;height:2px;width:100%;overflow:hidden;border-radius:3px;background-color:#ffffff40}.artplayer-plugin-danmuku .apd-slider .apd-slider-points{position:absolute;inset:0;display:flex;align-items:center;justify-content:space-between}.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point{width:2px;height:2px;border-radius:50%;background-color:#ffffff80}.artplayer-plugin-danmuku .apd-slider .apd-slider-progress{width:0%;height:100%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-dot{position:absolute;transform:translate(-6px);left:0%;width:12px;height:12px;border-radius:50%;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps{display:flex;align-items:center;justify-content:space-between;position:absolute;bottom:-12px;width:calc(100% + 32px);color:#777}.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step{flex-shrink:0;width:36px;text-align:center;scale:.95}.artplayer-plugin-danmuku .apd-config-other{display:flex;align-items:center;gap:20px}.artplayer-plugin-danmuku .apd-config-other .apd-check-off,.artplayer-plugin-danmuku .apd-config-other .apd-check-on{width:16px;height:16px}.artplayer-plugin-danmuku .apd-config-other .apd-other{display:flex;align-items:center;cursor:pointer;gap:2px}.artplayer-plugin-danmuku .apd-config-other .apd-other:hover{color:#00a1d6}.artplayer-plugin-danmuku .apd-emitter{display:flex;flex:1;align-items:center;height:100%;background-color:#ffffff40;border-radius:5px}.artplayer-plugin-danmuku .apd-style{position:relative;display:flex;align-items:center;justify-content:center}.artplayer-plugin-danmuku .apd-style .apd-style-panel{position:absolute;bottom:24px;left:0;width:200px;padding:10px;opacity:0;pointer-events:none}.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner{width:100%;border-radius:3px;background-color:#000000d9;padding:10px}.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel{opacity:100;pointer-events:all}.artplayer-plugin-danmuku .apd-colors{display:flex;flex-wrap:wrap;margin-top:5px;gap:8px}.artplayer-plugin-danmuku .apd-colors .apd-color{width:16px;height:16px;border-radius:2px;cursor:pointer}.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active{border:1px solid black;box-shadow:0 0 0 1px #fff}.artplayer-plugin-danmuku .apd-input{outline:none;height:100%;flex:1;min-width:0;width:auto;border:none;line-height:1;color:#fff;background-color:transparent}.artplayer-plugin-danmuku .apd-input::placeholder{color:#ffffff80}.artplayer-plugin-danmuku .apd-send{display:flex;align-items:center;justify-content:center;height:100%;width:60px;flex-shrink:0;cursor:pointer;text-shadow:none;border-top-right-radius:5px;border-bottom-right-radius:5px;background-color:#00a1d6}.artplayer-plugin-danmuku .apd-send.apd-lock{cursor:not-allowed;color:#666;background-color:#e7e7e7}.art-controls-center .apd-emitter{width:260px;flex:none}.art-fullscreen .artplayer-plugin-danmuku,.art-fullscreen-web .artplayer-plugin-danmuku{height:38px;gap:16px}.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on{width:28px;height:28px}.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter{width:400px;flex:none}.art-video-player>.artplayer-plugin-danmuku{position:absolute;left:0;right:0;bottom:-40px;padding:0 10px}.art-video-player:has(>.artplayer-plugin-danmuku){margin-bottom:40px}[data-danmuku-emitter=false] .apd-emitter{display:none!important}[data-danmuku-emitter=false] .art-controls-center .artplayer-plugin-danmuku{justify-content:flex-end;gap:18px}[data-danmuku-emitter=false].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,[data-danmuku-emitter=false].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku{gap:24px}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-icon{fill:#333}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-emitter{background-color:#f1f2f3}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input{color:#000}[data-danmuku-theme=light]>.artplayer-plugin-danmuku .apd-input::placeholder{color:#0000004d}[data-danmuku-visible=false] .apd-toggle-off{display:block}[data-danmuku-visible=false] .apd-toggle-on,[data-danmuku-visible=true] .apd-toggle-off{display:none}[data-danmuku-visible=true] .apd-toggle-on{display:block}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-on{display:none}[data-danmuku-anti-overlap=false] .apd-anti-overlap .apd-check-off,[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-on{display:block}[data-danmuku-anti-overlap=true] .apd-anti-overlap .apd-check-off,[data-danmuku-sync-video=false] .apd-sync-video .apd-check-on{display:none}[data-danmuku-sync-video=false] .apd-sync-video .apd-check-off,[data-danmuku-sync-video=true] .apd-sync-video .apd-check-on{display:block}[data-danmuku-sync-video=true] .apd-sync-video .apd-check-off{display:none}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-off{display:block}[data-danmuku-mode0=false] .apd-config-mode .apd-mode-0-on{display:none}[data-danmuku-mode0=false] .art-danmuku [data-mode="0"]{opacity:0!important}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-off{display:none}[data-danmuku-mode0=true] .apd-config-mode .apd-mode-0-on{display:block}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"]{color:#00a1d6}[data-danmuku-mode="0"] .apd-style-mode [data-mode="0"] path{fill:#00a1d6}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-off{display:block}[data-danmuku-mode1=false] .apd-config-mode .apd-mode-1-on{display:none}[data-danmuku-mode1=false] .art-danmuku [data-mode="1"]{opacity:0!important}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-off{display:none}[data-danmuku-mode1=true] .apd-config-mode .apd-mode-1-on{display:block}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"]{color:#00a1d6}[data-danmuku-mode="1"] .apd-style-mode [data-mode="1"] path{fill:#00a1d6}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-off{display:block}[data-danmuku-mode2=false] .apd-config-mode .apd-mode-2-on{display:none}[data-danmuku-mode2=false] .art-danmuku [data-mode="2"]{opacity:0!important}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-off{display:none}[data-danmuku-mode2=true] .apd-config-mode .apd-mode-2-on{display:block}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"]{color:#00a1d6}[data-danmuku-mode="2"] .apd-style-mode [data-mode="2"] path{fill:#00a1d6}'}function q(t){return e=>{const i=new M(e,t),a=e.isDestroy?void 0:new B(e,i);return!e.isDestroy&&i.option.heatmap&&E(e,i,i.option.heatmap),{name:"artplayerPluginDanmuku",emit:i.emit.bind(i),load:i.load.bind(i),config:i.config.bind(i),hide:i.hide.bind(i),show:i.show.bind(i),reset:i.reset.bind(i),mount:a?a.mount.bind(a):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return q.icons=B.icons,q}); diff --git a/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs b/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs index fac426cd8..97dbcd0db 100644 --- a/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs +++ b/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs @@ -4,6 +4,91 @@ * (c) 2017-2026 Harvey Zhao * Released under the MIT License. */ +function defaultOption() { + return { + danmuku: [], + speed: 5, + margin: [10, "25%"], + opacity: 1, + color: "#FFFFFF", + mode: 0, + modes: [0, 1, 2], + fontSize: 25, + antiOverlap: true, + synchronousPlayback: false, + mount: void 0, + heatmap: false, + width: 512, + points: [], + filter: () => true, + beforeEmit: () => true, + beforeVisible: () => true, + visible: true, + emitter: true, + maxLength: 200, + lockTime: 5, + theme: "dark", + OPACITY: {}, + FONT_SIZE: {}, + MARGIN: {}, + SPEED: {}, + COLOR: [] + }; +} +function optionScheme() { + return { + danmuku: "array|function|string|promise", + speed: "number", + margin: "array", + opacity: "number", + color: "string", + mode: "number", + modes: "array", + fontSize: "number|string", + antiOverlap: "boolean", + synchronousPlayback: "boolean", + mount: "?htmldivelement|string", + heatmap: "object|boolean", + width: "number", + points: "array", + filter: "function", + beforeEmit: "function", + beforeVisible: "function", + visible: "boolean", + emitter: "boolean", + maxLength: "number", + lockTime: "number", + theme: "string", + OPACITY: "object", + FONT_SIZE: "object", + MARGIN: "object", + SPEED: "object", + COLOR: "array" + }; +} +function isPromiseInput(value) { + return value instanceof Promise || Object.prototype.toString.call(value) === "[object Promise]"; +} +function optionChanged(previous, update) { + return Object.keys(update).some((key) => { + const before = previous[key]; + const after = update[key]; + if (typeof before === "function" || typeof after === "function" || isPromiseInput(before) || isPromiseInput(after)) + return before !== after; + return JSON.stringify(before) !== JSON.stringify(after); + }); +} +function normalizeOption(previous, update, { defaults, validate, clamp, mount }) { + const next = Object.assign({}, defaults, previous, update); + validate(next); + next.mode = clamp(next.mode, 0, 2); + next.speed = clamp(next.speed, 1, 10); + next.opacity = clamp(next.opacity, 0, 1); + next.lockTime = clamp(next.lockTime, 1, 60); + next.maxLength = clamp(next.maxLength, 1, 1e3); + next.mount = next.mount || mount; + return next; +} function getMode(key) { switch (key) { case 1: @@ -45,41 +130,219 @@ function bilibiliDanmuParseFromXml(xmlString) { } function onmessage({ data }) { const { xml, id } = data; - if (!id || !xml) - return; const danmus = bilibiliDanmuParseFromXml(xml); globalThis.postMessage({ danmus, id }); } -function createWorker() { - const workerText = ` - ${getMode.toString()} - ${bilibiliDanmuParseFromXml.toString()} - onmessage = ${onmessage.toString()} - `; - const blob2 = new Blob([workerText], { type: "application/javascript" }); - return new Worker(URL.createObjectURL(blob2)); +let nextRequest = 0; +function abortError(signal) { + if (signal?.reason !== void 0) + return signal.reason; + const error = new Error("Bilibili Danmu loading cancelled"); + error.name = "AbortError"; + return error; } -function bilibiliDanmuParseFromUrl(url) { - return new Promise(async (resolve) => { - const res = await fetch(url); - const xml = await res.text(); - try { - const worker = createWorker(); - worker.onmessage = (event) => { - const { danmus, id } = event.data; - if (!id || !danmus) - return; - resolve(danmus); - worker.terminate(); - }; - worker.postMessage({ xml, id: Date.now() }); - } catch (error) { - console.error("Error parsing Bilibili Danmu:", error); - const danmus = bilibiliDanmuParseFromXml(xml); - resolve(danmus); +function bilibiliDanmuParseFromUrl(url, { signal, onCancel } = {}) { + return new Promise((resolve, reject) => { + let settled = false; + let worker; + let workerUrl; + let releaseCancel; + const id = ++nextRequest; + const cancel = () => finish(void 0, abortError(signal)); + function releaseWorker() { + let error; + let failed = false; + try { + if (worker) { + const current = worker; + worker = void 0; + current.onmessage = null; + current.onerror = null; + current.onmessageerror = null; + current.terminate(); + } + } catch (failure) { + error = failure; + failed = true; + } + if (workerUrl !== void 0) { + const current = workerUrl; + workerUrl = void 0; + try { + URL.revokeObjectURL(current); + } catch (failure) { + if (!failed) + error = failure; + failed = true; + } + } + if (failed) + throw error; } + function finish(value, error) { + if (settled) + return; + settled = true; + let failed = arguments.length > 1; + signal?.removeEventListener("abort", cancel); + releaseCancel?.(); + try { + releaseWorker(); + } catch (cleanupError) { + if (!failed) + error = cleanupError; + failed = true; + } + if (failed) + reject(error); + else resolve(value); + } + function fallback(xml, error) { + if (settled) + return; + console.error("Error parsing Bilibili Danmu:", error); + try { + finish(bilibiliDanmuParseFromXml(xml)); + } catch (failure) { + finish(void 0, failure); + } + } + async function read() { + const response = signal ? await fetch(url, { signal }) : await fetch(url); + if (settled) + return; + const xml = await response.text(); + if (settled) + return; + try { + const workerText = ` + ${getMode.toString()} + ${bilibiliDanmuParseFromXml.toString()} + onmessage = ${onmessage.toString()} + `; + workerUrl = URL.createObjectURL(new Blob([workerText], { type: "application/javascript" })); + if (settled) { + releaseWorker(); + return; + } + worker = new Worker(workerUrl); + if (settled) { + releaseWorker(); + return; + } + worker.onmessage = (event) => { + if (settled || event.data?.id !== id) + return; + const { danmus } = event.data; + if (!Array.isArray(danmus)) { + finish(void 0, new Error("Invalid Bilibili Danmu worker response")); + return; + } + finish(danmus); + }; + worker.onerror = (event) => { + event.preventDefault?.(); + fallback(xml, event.error || event); + }; + worker.onmessageerror = (event) => fallback(xml, event.error || event); + worker.postMessage({ xml, id }); + } catch (error) { + fallback(xml, error); + } + } + if (signal?.aborted) { + cancel(); + return; + } + signal?.addEventListener("abort", cancel); + releaseCancel = onCancel?.(cancel); + if (settled) { + releaseCancel?.(); + return; + } + read().catch((error) => finish(void 0, error)); }); } +const inputs = /* @__PURE__ */ new WeakMap(); +const cancelled = /* @__PURE__ */ Symbol("cancelled danmuku input"); +function stateFor(owner) { + if (!inputs.has(owner)) + inputs.set(owner, { closed: false, tasks: /* @__PURE__ */ new Set(), emitting: [] }); + return inputs.get(owner); +} +function inputActive(owner) { + const state = stateFor(owner); + const task = state.emitting[state.emitting.length - 1]; + return !state.closed && !owner.art.isDestroy && (!task || task.active()); +} +function beginInput(owner, replace) { + const state = stateFor(owner); + if (replace) { + for (const task2 of [...state.tasks]) { + if (task2.replace) + task2.cancel(); + } + } + let stopped = state.closed || owner.art.isDestroy; + let resolveCancel; + const cancellation = new Promise((resolve) => resolveCancel = resolve); + const handlers = /* @__PURE__ */ new Set(); + const controller = typeof AbortController === "function" ? new AbortController() : void 0; + const task = { + replace, + signal: controller?.signal, + active: () => !stopped && !state.closed && !owner.art.isDestroy, + onCancel(callback) { + if (!task.active()) { + callback(); + return () => { + }; + } + handlers.add(callback); + return () => handlers.delete(callback); + }, + cancel() { + if (stopped) + return; + stopped = true; + resolveCancel(cancelled); + controller?.abort(); + for (const callback of [...handlers]) callback(); + handlers.clear(); + }, + wait: (value) => Promise.race([value, cancellation]), + emit(danmu) { + state.emitting.push(task); + try { + return owner.emit(danmu); + } finally { + state.emitting.pop(); + } + }, + finish() { + state.tasks.delete(task); + handlers.clear(); + } + }; + if (stopped) + resolveCancel(cancelled); + else state.tasks.add(task); + return task; +} +function cancelInputs(owner) { + const state = stateFor(owner); + state.closed = true; + for (const task of [...state.tasks]) task.cancel(); +} +function readInput(target, task) { + if (typeof target === "function") + return { asynchronous: true, value: target() }; + if (isPromiseInput(target)) + return { asynchronous: true, value: target }; + if (typeof target === "string") + return { asynchronous: true, value: bilibiliDanmuParseFromUrl(target, task) }; + return { asynchronous: false, value: Array.isArray(target) ? target : [] }; +} const jsContent = '/*!\n * artplayer-plugin-danmuku.js v5.3.0\n * Github: https://github.com/zhw2590582/ArtPlayer\n * (c) 2017-2026 Harvey Zhao\n * Released under the MIT License.\n */\nfunction getDanmuTop({ target, visibles, clientWidth, clientHeight, marginBottom, marginTop, antiOverlap }) {\n const maxTop = clientHeight - marginBottom;\n const danmus = visibles.filter((item) => item.mode === target.mode && item.top <= maxTop).sort((prev, next) => prev.top - next.top);\n if (danmus.length === 0) {\n if (target.mode === 2) {\n return maxTop - target.height;\n } else {\n return marginTop;\n }\n }\n danmus.unshift({\n type: "top",\n top: 0,\n left: 0,\n right: 0,\n height: marginTop,\n width: clientWidth,\n speed: 0,\n distance: clientWidth\n });\n danmus.push({\n type: "bottom",\n top: maxTop,\n left: 0,\n right: 0,\n height: marginBottom,\n width: clientWidth,\n speed: 0,\n distance: clientWidth\n });\n if (target.mode === 2) {\n for (let index = danmus.length - 2; index >= 0; index -= 1) {\n const item = danmus[index];\n const prev = danmus[index + 1];\n const itemBottom = item.top + item.height;\n const diff = prev.top - itemBottom;\n if (diff >= target.height) {\n return prev.top - target.height;\n }\n }\n } else {\n for (let index = 1; index < danmus.length; index += 1) {\n const item = danmus[index];\n const prev = danmus[index - 1];\n const prevBottom = prev.top + prev.height;\n const diff = item.top - prevBottom;\n if (diff >= target.height) {\n return prevBottom;\n }\n }\n }\n const topMap = [];\n for (let index = 1; index < danmus.length - 1; index += 1) {\n const item = danmus[index];\n if (topMap.length) {\n const last = topMap[topMap.length - 1];\n if (last[0].top === item.top) {\n last.push(item);\n } else {\n topMap.push([item]);\n }\n } else {\n topMap.push([item]);\n }\n }\n if (antiOverlap) {\n switch (target.mode) {\n case 0: {\n const result = topMap.find((list) => {\n return list.every((danmu) => {\n if (clientWidth < danmu.distance)\n return false;\n if (target.speed < danmu.speed)\n return true;\n const overlapTime = danmu.right / (target.speed - danmu.speed);\n if (overlapTime > danmu.time)\n return true;\n return false;\n });\n });\n return result && result[0] ? result[0].top : void 0;\n }\n // 静止弹幕没有重叠问题\n case 1:\n case 2:\n return void 0;\n }\n } else {\n switch (target.mode) {\n case 0:\n topMap.sort((prev, next) => {\n const nextMinRight = Math.min(...next.map((item) => item.right));\n const prevMinRight = Math.min(...prev.map((item) => item.right));\n return nextMinRight * next.length - prevMinRight * prev.length;\n });\n break;\n case 1:\n case 2:\n topMap.sort((prev, next) => {\n const nextMaxWidth = Math.max(...next.map((item) => item.width));\n const prevMaxWidth = Math.max(...prev.map((item) => item.width));\n return prevMaxWidth * prev.length - nextMaxWidth * next.length;\n });\n break;\n }\n return topMap[0][0].top;\n }\n}\nonmessage = (event) => {\n const { data } = event;\n if (!data.id || !data.type)\n return;\n const fns = { getDanmuTop };\n const fn = fns[data.type];\n const result = fn(data);\n globalThis.postMessage({\n result,\n id: data.id\n });\n};\n'; const blob = typeof self !== "undefined" && self.Blob && new Blob(["URL.revokeObjectURL(import.meta.url);", jsContent], { type: "text/javascript;charset=utf-8" }); function WorkerWrapper(options) { @@ -119,113 +382,40 @@ class Danmuku { this.isHide = false; this.timer = null; this.index = 0; + this.worker = null; this.option = Danmuku.option; this.states = { wait: [], ready: [], emit: [], stop: [] }; - this.config(option, true); - this.worker = new WorkerWrapper(); this.start = this.start.bind(this); this.stop = this.stop.bind(this); this.reset = this.reset.bind(this); this.resize = this.resize.bind(this); this.destroy = this.destroy.bind(this); + inputActive(this); + art.on("destroy", this.destroy); + try { + this.config(option, true); + if (!inputActive(this)) + return; + this.worker = new WorkerWrapper(); + } catch (error) { + art.off("destroy", this.destroy); + cancelInputs(this); + throw error; + } art.on("video:play", this.start); art.on("video:playing", this.start); art.on("video:pause", this.stop); art.on("video:waiting", this.stop); - art.on("destroy", this.destroy); art.on("resize", this.resize); - this.load(); + this.load().catch((error) => console.warn("Failed to load initial danmuku:", error)); } // 默认配置 static get option() { - return { - danmuku: [], - // 弹幕数据 - speed: 5, - // 弹幕持续时间,范围在[1 ~ 10] - margin: [10, "25%"], - // 弹幕上下边距,支持像素数字和百分比 - opacity: 1, - // 弹幕透明度,范围在[0 ~ 1] - color: "#FFFFFF", - // 默认弹幕颜色,可以被单独弹幕项覆盖 - mode: 0, - // 默认弹幕模式: 0: 滚动,1: 顶部,2: 底部 - modes: [0, 1, 2], - // 弹幕可见的模式 - fontSize: 25, - // 弹幕字体大小,支持像素数字和百分比 - antiOverlap: true, - // 弹幕是否防重叠 - synchronousPlayback: false, - // 是否同步播放速度 - mount: void 0, - // 弹幕发射器挂载点, 默认为播放器控制栏中部 - heatmap: false, - // 是否开启热力图 - width: 512, - // 当播放器宽度小于此值时,弹幕发射器置于播放器底部 - points: [], - // 热力图数据 - filter: () => true, - // 弹幕载入前的过滤器,只支持返回布尔值 - beforeEmit: () => true, - // 弹幕发送前的过滤器,支持返回 Promise - beforeVisible: () => true, - // 弹幕显示前的过滤器,支持返回 Promise - visible: true, - // 弹幕层是否可见 - emitter: true, - // 是否开启弹幕发射器 - maxLength: 200, - // 弹幕输入框最大长度, 范围在[1 ~ 1000] - lockTime: 5, - // 输入框锁定时间,范围在[1 ~ 60] - theme: "dark", - // 弹幕主题,支持 dark 和 light,只在自定义挂载时生效 - OPACITY: {}, - // 不透明度配置项 - FONT_SIZE: {}, - // 弹幕字号配置项 - MARGIN: {}, - // 显示区域配置项 - SPEED: {}, - // 弹幕速度配置项 - COLOR: [] - // 颜色列表配置项 - }; + return defaultOption(); } // 配置校验 static get scheme() { - return { - danmuku: "array|function|string", - speed: "number", - margin: "array", - opacity: "number", - color: "string", - mode: "number", - modes: "array", - fontSize: "number|string", - antiOverlap: "boolean", - synchronousPlayback: "boolean", - mount: "?htmldivelement|string", - heatmap: "object|boolean", - width: "number", - points: "array", - filter: "function", - beforeEmit: "function", - beforeVisible: "function", - visible: "boolean", - emitter: "boolean", - maxLength: "number", - lockTime: "number", - theme: "string", - OPACITY: "object", - FONT_SIZE: "object", - MARGIN: "object", - SPEED: "object", - COLOR: "array" - }; + return optionScheme(); } // 初始弹幕样式 static get cssText() { @@ -345,40 +535,48 @@ class Danmuku { // 加载弹幕 async load(danmuku) { const { errorHandle } = this.utils; - let danmus = []; - const target = danmuku || this.option.danmuku; + const task = beginInput(this, danmuku === void 0); try { - if (typeof target === "function") { - danmus = await target(); - } else if (target instanceof Promise) { - danmus = await target; - } else if (typeof target === "string") { - danmus = await bilibiliDanmuParseFromUrl(target); - } else if (Array.isArray(target)) { - danmus = target; - } + if (!task.active()) + return this; + const target = danmuku || this.option.danmuku; + const input = readInput(target, task); + const danmus = input.asynchronous ? await task.wait(input.value) : input.value; + if (!task.active()) + return this; errorHandle(Array.isArray(danmus), "Danmuku need return an array as result"); if (danmuku === void 0) { this.reset(); + if (!task.active()) + return this; this.queue = []; this.states = { wait: [], ready: [], emit: [], stop: [] }; this.$refs = []; this.$danmuku.textContent = ""; } for (let index = 0; index < danmus.length; index++) { + if (!task.active()) + return this; const danmu = danmus[index]; - await this.emit(danmu); + await task.emit(danmu); } - this.art.emit("artplayerPluginDanmuku:loaded", this.queue); + if (task.active()) + this.art.emit("artplayerPluginDanmuku:loaded", this.queue); } catch (error) { + if (!task.active()) + return this; this.art.emit("artplayerPluginDanmuku:error", error); throw error; + } finally { + task.finish(); } return this; } // 把原始弹幕转换到弹幕队列 async emit(danmu) { const { clamp } = this.utils; + if (!inputActive(this)) + return this; this.validator(danmu, { id: "?string", // 弹幕唯一标识 @@ -397,7 +595,7 @@ class Danmuku { }); if (!danmu.text.trim()) return this; - if (danmu.time) { + if (danmu.time !== void 0) { danmu.time = clamp(danmu.time, 0, Infinity); } else { danmu.time = this.art.currentTime + 0.5; @@ -415,6 +613,8 @@ class Danmuku { return this; if (!this.option.filter(danmu)) return this; + if (!inputActive(this)) + return this; const item = { ...danmu, $state: "wait", @@ -436,28 +636,30 @@ class Danmuku { config(option, isInit = false) { const { clamp } = this.utils; const { $controlsCenter } = this.art.template; - const changed = Object.keys(option).some( - (key) => JSON.stringify(this.option[key]) !== JSON.stringify(option[key]) - ); + const changed = optionChanged(this.option, option); if (!changed && !isInit) return this; - this.option = Object.assign({}, Danmuku.option, this.option, option); - this.validator(this.option, Danmuku.scheme); - this.option.mode = clamp(this.option.mode, 0, 2); - this.option.speed = clamp(this.option.speed, 1, 10); - this.option.opacity = clamp(this.option.opacity, 0, 1); - this.option.lockTime = clamp(this.option.lockTime, 1, 60); - this.option.maxLength = clamp(this.option.maxLength, 1, 1e3); - this.option.mount = this.option.mount || $controlsCenter; + const next = normalizeOption(this.option, option, { + defaults: Danmuku.option, + validate: (value) => this.validator(value, Danmuku.scheme), + clamp, + mount: $controlsCenter + }); + if (!inputActive(this)) + return this; + this.option = next; if (option.fontSize) { this.reset(); + if (!inputActive(this)) + return this; } if (this.option.visible) { this.show(); } else { this.hide(); } - this.art.emit("artplayerPluginDanmuku:config", this.option); + if (inputActive(this)) + this.art.emit("artplayerPluginDanmuku:config", this.option); return this; } // 计算DOM的left值,受到旋屏影响 @@ -688,13 +890,14 @@ class Danmuku { return this; } destroy() { + cancelInputs(this); this.stop(); - this.worker.terminate(); + this.worker?.terminate(); this.art.off("video:play", this.start); this.art.off("video:playing", this.start); this.art.off("video:pause", this.stop); this.art.off("video:waiting", this.stop); - this.art.off("resize", this.reset); + this.art.off("resize", this.resize); this.art.off("destroy", this.destroy); this.art.emit("artplayerPluginDanmuku:destroy"); } @@ -1536,8 +1739,8 @@ if (typeof document !== "undefined") { function artplayerPluginDanmuku(option) { return (art) => { const danmuku = new Danmuku(art, option); - const setting = new Setting(art, danmuku); - if (danmuku.option.heatmap) { + const setting = art.isDestroy ? void 0 : new Setting(art, danmuku); + if (!art.isDestroy && danmuku.option.heatmap) { heatmap(art, danmuku, danmuku.option.heatmap); } return { @@ -1548,7 +1751,8 @@ function artplayerPluginDanmuku(option) { hide: danmuku.hide.bind(danmuku), show: danmuku.show.bind(danmuku), reset: danmuku.reset.bind(danmuku), - mount: setting.mount.bind(setting), + mount: setting ? setting.mount.bind(setting) : () => { + }, get option() { return danmuku.option; }, diff --git a/packages/artplayer-plugin-danmuku/src/bilibili-parser.js b/packages/artplayer-plugin-danmuku/src/bilibili-parser.js new file mode 100644 index 000000000..ac000e5f9 --- /dev/null +++ b/packages/artplayer-plugin-danmuku/src/bilibili-parser.js @@ -0,0 +1,56 @@ +export function getMode(key) { + switch (key) { + case 1: + case 2: + case 3: + return 0 + case 4: + return 2 + case 5: + return 1 + default: + return 0 + } +} + +export function bilibiliDanmuParseFromXml(xmlString) { + if (typeof xmlString !== 'string') + return [] + const reg = /]*?p="(?

[^"]+)"[^>]*>(?.*?)<\/d>/gs + const matches = xmlString.matchAll(reg) + return Array.from(matches) + .map((match) => { + const attr = match.groups.p.split(',') + if (attr.length >= 8) { + const text = match.groups.text + .trim() + .replaceAll('"', '"') + .replaceAll(''', '\'') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('&', '&') + + return { + text, + time: Number(attr[0]), + mode: getMode(Number(attr[1])), + fontSize: Number(attr[2]), + color: `#${Number(attr[3]).toString(16)}`, + timestamp: Number(attr[4]), + pool: Number(attr[5]), + userID: attr[6], + rowID: Number(attr[7]), + } + } + else { + return null + } + }) + .filter(Boolean) +} + +export function onmessage({ data }) { + const { xml, id } = data + const danmus = bilibiliDanmuParseFromXml(xml) + globalThis.postMessage({ danmus, id }) +} diff --git a/packages/artplayer-plugin-danmuku/src/bilibili.js b/packages/artplayer-plugin-danmuku/src/bilibili.js index 9b0d410e0..47be475fb 100644 --- a/packages/artplayer-plugin-danmuku/src/bilibili.js +++ b/packages/artplayer-plugin-danmuku/src/bilibili.js @@ -1,93 +1,144 @@ -function getMode(key) { - switch (key) { - case 1: - case 2: - case 3: - return 0 - case 4: - return 2 - case 5: - return 1 - default: - return 0 - } +import { bilibiliDanmuParseFromXml, getMode, onmessage } from './bilibili-parser' + +let nextRequest = 0 + +function abortError(signal) { + if (signal?.reason !== undefined) + return signal.reason + const error = new Error('Bilibili Danmu loading cancelled') + error.name = 'AbortError' + return error } -function bilibiliDanmuParseFromXml(xmlString) { - if (typeof xmlString !== 'string') - return [] - const reg = /]*?p="(?

[^"]+)"[^>]*>(?.*?)<\/d>/gs - const matches = xmlString.matchAll(reg) - return Array.from(matches) - .map((match) => { - const attr = match.groups.p.split(',') - if (attr.length >= 8) { - const text = match.groups.text - .trim() - .replaceAll('"', '"') - .replaceAll(''', '\'') - .replaceAll('<', '<') - .replaceAll('>', '>') - .replaceAll('&', '&') +export function bilibiliDanmuParseFromUrl(url, { signal, onCancel } = {}) { + return new Promise((resolve, reject) => { + let settled = false + let worker + let workerUrl + let releaseCancel + const id = ++nextRequest + const cancel = () => finish(undefined, abortError(signal)) - return { - text, - time: Number(attr[0]), - mode: getMode(Number(attr[1])), - fontSize: Number(attr[2]), - color: `#${Number(attr[3]).toString(16)}`, - timestamp: Number(attr[4]), - pool: Number(attr[5]), - userID: attr[6], - rowID: Number(attr[7]), + function releaseWorker() { + let error + let failed = false + try { + if (worker) { + const current = worker + worker = undefined + current.onmessage = null + current.onerror = null + current.onmessageerror = null + current.terminate() } } - else { - return null + catch (failure) { + error = failure + failed = true } - }) - .filter(Boolean) -} - -function onmessage({ data }) { - const { xml, id } = data - if (!id || !xml) - return - const danmus = bilibiliDanmuParseFromXml(xml) - globalThis.postMessage({ danmus, id }) -} - -function createWorker() { - const workerText = ` - ${getMode.toString()} - ${bilibiliDanmuParseFromXml.toString()} - onmessage = ${onmessage.toString()} - ` - const blob = new Blob([workerText], { type: 'application/javascript' }) - return new Worker(URL.createObjectURL(blob)) -} - -export function bilibiliDanmuParseFromUrl(url) { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const res = await fetch(url) - const xml = await res.text() - - try { - const worker = createWorker() - worker.onmessage = (event) => { - const { danmus, id } = event.data - if (!id || !danmus) - return - resolve(danmus) - worker.terminate() + if (workerUrl !== undefined) { + const current = workerUrl + workerUrl = undefined + try { + URL.revokeObjectURL(current) + } + catch (failure) { + if (!failed) + error = failure + failed = true + } } - worker.postMessage({ xml, id: Date.now() }) + if (failed) + throw error } - catch (error) { + + function finish(value, error) { + if (settled) + return + settled = true + let failed = arguments.length > 1 + signal?.removeEventListener('abort', cancel) + releaseCancel?.() + try { + releaseWorker() + } + catch (cleanupError) { + if (!failed) + error = cleanupError + failed = true + } + if (failed) + reject(error) + else resolve(value) + } + + function fallback(xml, error) { + if (settled) + return console.error('Error parsing Bilibili Danmu:', error) - const danmus = bilibiliDanmuParseFromXml(xml) - resolve(danmus) + try { + finish(bilibiliDanmuParseFromXml(xml)) + } + catch (failure) { + finish(undefined, failure) + } } + + async function read() { + const response = signal ? await fetch(url, { signal }) : await fetch(url) + if (settled) + return + const xml = await response.text() + if (settled) + return + try { + const workerText = ` + ${getMode.toString()} + ${bilibiliDanmuParseFromXml.toString()} + onmessage = ${onmessage.toString()} + ` + workerUrl = URL.createObjectURL(new Blob([workerText], { type: 'application/javascript' })) + if (settled) { + releaseWorker() + return + } + worker = new Worker(workerUrl) + if (settled) { + releaseWorker() + return + } + worker.onmessage = (event) => { + if (settled || event.data?.id !== id) + return + const { danmus } = event.data + if (!Array.isArray(danmus)) { + finish(undefined, new Error('Invalid Bilibili Danmu worker response')) + return + } + finish(danmus) + } + worker.onerror = (event) => { + event.preventDefault?.() + fallback(xml, event.error || event) + } + worker.onmessageerror = event => fallback(xml, event.error || event) + worker.postMessage({ xml, id }) + } + catch (error) { + fallback(xml, error) + } + } + + if (signal?.aborted) { + cancel() + return + } + signal?.addEventListener('abort', cancel) + releaseCancel = onCancel?.(cancel) + if (settled) { + releaseCancel?.() + return + } + read().catch(error => finish(undefined, error)) }) } diff --git a/packages/artplayer-plugin-danmuku/src/config.js b/packages/artplayer-plugin-danmuku/src/config.js new file mode 100644 index 000000000..25c0bda12 --- /dev/null +++ b/packages/artplayer-plugin-danmuku/src/config.js @@ -0,0 +1,89 @@ +export function defaultOption() { + return { + danmuku: [], + speed: 5, + margin: [10, '25%'], + opacity: 1, + color: '#FFFFFF', + mode: 0, + modes: [0, 1, 2], + fontSize: 25, + antiOverlap: true, + synchronousPlayback: false, + mount: undefined, + heatmap: false, + width: 512, + points: [], + filter: () => true, + beforeEmit: () => true, + beforeVisible: () => true, + visible: true, + emitter: true, + maxLength: 200, + lockTime: 5, + theme: 'dark', + OPACITY: {}, + FONT_SIZE: {}, + MARGIN: {}, + SPEED: {}, + COLOR: [], + } +} + +export function optionScheme() { + return { + danmuku: 'array|function|string|promise', + speed: 'number', + margin: 'array', + opacity: 'number', + color: 'string', + mode: 'number', + modes: 'array', + fontSize: 'number|string', + antiOverlap: 'boolean', + synchronousPlayback: 'boolean', + mount: '?htmldivelement|string', + heatmap: 'object|boolean', + width: 'number', + points: 'array', + filter: 'function', + beforeEmit: 'function', + beforeVisible: 'function', + visible: 'boolean', + emitter: 'boolean', + maxLength: 'number', + lockTime: 'number', + theme: 'string', + OPACITY: 'object', + FONT_SIZE: 'object', + MARGIN: 'object', + SPEED: 'object', + COLOR: 'array', + } +} + +export function isPromiseInput(value) { + return value instanceof Promise || Object.prototype.toString.call(value) === '[object Promise]' +} + +export function optionChanged(previous, update) { + return Object.keys(update).some((key) => { + const before = previous[key] + const after = update[key] + if (typeof before === 'function' || typeof after === 'function' || isPromiseInput(before) || isPromiseInput(after)) + return before !== after + return JSON.stringify(before) !== JSON.stringify(after) + }) +} + +export function normalizeOption(previous, update, { defaults, validate, clamp, mount }) { + const next = Object.assign({}, defaults, previous, update) + validate(next) + next.mode = clamp(next.mode, 0, 2) + next.speed = clamp(next.speed, 1, 10) + next.opacity = clamp(next.opacity, 0, 1) + next.lockTime = clamp(next.lockTime, 1, 60) + next.maxLength = clamp(next.maxLength, 1, 1000) + next.mount = next.mount || mount + return next +} diff --git a/packages/artplayer-plugin-danmuku/src/danmuku.js b/packages/artplayer-plugin-danmuku/src/danmuku.js index 307f590f3..82f6de465 100644 --- a/packages/artplayer-plugin-danmuku/src/danmuku.js +++ b/packages/artplayer-plugin-danmuku/src/danmuku.js @@ -1,4 +1,5 @@ -import { bilibiliDanmuParseFromUrl } from './bilibili' +import { defaultOption, normalizeOption, optionChanged, optionScheme } from './config' +import { beginInput, cancelInputs, inputActive, readInput } from './input' import DanmuWorker from './worker.js?worker&inline' export default class Danmuku { @@ -17,6 +18,7 @@ export default class Danmuku { this.isHide = false // 是否隐藏 this.timer = null // 定时器 this.index = 0 // 弹幕索引 + this.worker = null // 格式化后的配置项 this.option = Danmuku.option @@ -24,12 +26,6 @@ export default class Danmuku { // 弹幕状态池 this.states = { wait: [], ready: [], emit: [], stop: [] } - // 初始化配置 - this.config(option, true) - - // 创建 Web Worker, 用于计算弹幕的 top 值 - this.worker = new DanmuWorker() - // 绑定公用事件 this.start = this.start.bind(this) this.stop = this.stop.bind(this) @@ -37,82 +33,41 @@ export default class Danmuku { this.resize = this.resize.bind(this) this.destroy = this.destroy.bind(this) + // 配置事件可同步销毁播放器,先建立输入和销毁边界。 + inputActive(this) + art.on('destroy', this.destroy) + try { + this.config(option, true) + if (!inputActive(this)) + return + + // 创建 Web Worker, 用于计算弹幕的 top 值 + this.worker = new DanmuWorker() + } + catch (error) { + art.off('destroy', this.destroy) + cancelInputs(this) + throw error + } // 监听事件 art.on('video:play', this.start) art.on('video:playing', this.start) art.on('video:pause', this.stop) art.on('video:waiting', this.stop) - art.on('destroy', this.destroy) art.on('resize', this.resize) // 开始加载弹幕 - this.load() + this.load().catch(error => console.warn('Failed to load initial danmuku:', error)) } // 默认配置 static get option() { - return { - danmuku: [], // 弹幕数据 - speed: 5, // 弹幕持续时间,范围在[1 ~ 10] - margin: [10, '25%'], // 弹幕上下边距,支持像素数字和百分比 - opacity: 1, // 弹幕透明度,范围在[0 ~ 1] - color: '#FFFFFF', // 默认弹幕颜色,可以被单独弹幕项覆盖 - mode: 0, // 默认弹幕模式: 0: 滚动,1: 顶部,2: 底部 - modes: [0, 1, 2], // 弹幕可见的模式 - fontSize: 25, // 弹幕字体大小,支持像素数字和百分比 - antiOverlap: true, // 弹幕是否防重叠 - synchronousPlayback: false, // 是否同步播放速度 - mount: undefined, // 弹幕发射器挂载点, 默认为播放器控制栏中部 - heatmap: false, // 是否开启热力图 - width: 512, // 当播放器宽度小于此值时,弹幕发射器置于播放器底部 - points: [], // 热力图数据 - filter: () => true, // 弹幕载入前的过滤器,只支持返回布尔值 - beforeEmit: () => true, // 弹幕发送前的过滤器,支持返回 Promise - beforeVisible: () => true, // 弹幕显示前的过滤器,支持返回 Promise - visible: true, // 弹幕层是否可见 - emitter: true, // 是否开启弹幕发射器 - maxLength: 200, // 弹幕输入框最大长度, 范围在[1 ~ 1000] - lockTime: 5, // 输入框锁定时间,范围在[1 ~ 60] - theme: 'dark', // 弹幕主题,支持 dark 和 light,只在自定义挂载时生效 - OPACITY: {}, // 不透明度配置项 - FONT_SIZE: {}, // 弹幕字号配置项 - MARGIN: {}, // 显示区域配置项 - SPEED: {}, // 弹幕速度配置项 - COLOR: [], // 颜色列表配置项 - } + return defaultOption() } // 配置校验 static get scheme() { - return { - danmuku: 'array|function|string', - speed: 'number', - margin: 'array', - opacity: 'number', - color: 'string', - mode: 'number', - modes: 'array', - fontSize: 'number|string', - antiOverlap: 'boolean', - synchronousPlayback: 'boolean', - mount: '?htmldivelement|string', - heatmap: 'object|boolean', - width: 'number', - points: 'array', - filter: 'function', - beforeEmit: 'function', - beforeVisible: 'function', - visible: 'boolean', - emitter: 'boolean', - maxLength: 'number', - lockTime: 'number', - theme: 'string', - OPACITY: 'object', - FONT_SIZE: 'object', - MARGIN: 'object', - SPEED: 'object', - COLOR: 'array', - } + return optionScheme() } // 初始弹幕样式 @@ -264,29 +219,25 @@ export default class Danmuku { // 加载弹幕 async load(danmuku) { const { errorHandle } = this.utils - - let danmus = [] - const target = danmuku || this.option.danmuku + const task = beginInput(this, danmuku === undefined) try { - if (typeof target === 'function') { - danmus = await target() // 异步函数获取 - } - else if (target instanceof Promise) { - danmus = await target // 从 Promise 对象获取 - } - else if (typeof target === 'string') { - danmus = await bilibiliDanmuParseFromUrl(target) // 从B站xml链接解析 - } - else if (Array.isArray(target)) { - danmus = target // 直接传入数组 - } + if (!task.active()) + return this + const target = danmuku || this.option.danmuku + const input = readInput(target, task) + // 数组在第一次 await 前提交,空数组的 loaded 保持同步。 + const danmus = input.asynchronous ? await task.wait(input.value) : input.value + if (!task.active()) + return this errorHandle(Array.isArray(danmus), 'Danmuku need return an array as result') // 假如没有传入弹幕参数,则清空弹幕,否则追加弹幕 if (danmuku === undefined) { this.reset() // 重置弹幕 + if (!task.active()) + return this this.queue = [] // 清空弹幕队列 this.states = { wait: [], ready: [], emit: [], stop: [] } // 清空弹幕状态池 this.$refs = [] // 清空弹幕DOM节点池 @@ -295,16 +246,24 @@ export default class Danmuku { // 逐个验证原始弹幕并转换到弹幕队列 for (let index = 0; index < danmus.length; index++) { + if (!task.active()) + return this const danmu = danmus[index] - await this.emit(danmu) + await task.emit(danmu) } - this.art.emit('artplayerPluginDanmuku:loaded', this.queue) + if (task.active()) + this.art.emit('artplayerPluginDanmuku:loaded', this.queue) } catch (error) { + if (!task.active()) + return this this.art.emit('artplayerPluginDanmuku:error', error) throw error } + finally { + task.finish() + } return this } @@ -312,6 +271,8 @@ export default class Danmuku { // 把原始弹幕转换到弹幕队列 async emit(danmu) { const { clamp } = this.utils + if (!inputActive(this)) + return this this.validator(danmu, { id: '?string', // 弹幕唯一标识 @@ -328,7 +289,7 @@ export default class Danmuku { return this // 设置弹幕时间,如果没有则默认为当前时间加 0.5 秒 - if (danmu.time) { + if (danmu.time !== undefined) { danmu.time = clamp(danmu.time, 0, Infinity) } else { @@ -357,6 +318,8 @@ export default class Danmuku { // 自定义弹幕过滤函数 if (!this.option.filter(danmu)) return this + if (!inputActive(this)) + return this // 添加自定义属性 const item = { @@ -389,28 +352,28 @@ export default class Danmuku { const { $controlsCenter } = this.art.template // 判断配置项是否有变化 - const changed = Object.keys(option).some( - key => JSON.stringify(this.option[key]) !== JSON.stringify(option[key]), - ) + const changed = optionChanged(this.option, option) // 没有变化则直接返回 if (!changed && !isInit) return this // 更新配置项 - this.option = Object.assign({}, Danmuku.option, this.option, option) - this.validator(this.option, Danmuku.scheme) - - this.option.mode = clamp(this.option.mode, 0, 2) - this.option.speed = clamp(this.option.speed, 1, 10) - this.option.opacity = clamp(this.option.opacity, 0, 1) - this.option.lockTime = clamp(this.option.lockTime, 1, 60) - this.option.maxLength = clamp(this.option.maxLength, 1, 1000) - this.option.mount = this.option.mount || $controlsCenter + const next = normalizeOption(this.option, option, { + defaults: Danmuku.option, + validate: value => this.validator(value, Danmuku.scheme), + clamp, + mount: $controlsCenter, + }) + if (!inputActive(this)) + return this + this.option = next // 动态配置有字体大小,需要重新渲染 if (option.fontSize) { this.reset() + if (!inputActive(this)) + return this } // 通过配置项控制弹幕的显示和隐藏 @@ -421,7 +384,8 @@ export default class Danmuku { this.hide() } - this.art.emit('artplayerPluginDanmuku:config', this.option) + if (inputActive(this)) + this.art.emit('artplayerPluginDanmuku:config', this.option) return this } @@ -718,13 +682,14 @@ export default class Danmuku { } destroy() { + cancelInputs(this) this.stop() - this.worker.terminate() + this.worker?.terminate() this.art.off('video:play', this.start) this.art.off('video:playing', this.start) this.art.off('video:pause', this.stop) this.art.off('video:waiting', this.stop) - this.art.off('resize', this.reset) + this.art.off('resize', this.resize) this.art.off('destroy', this.destroy) this.art.emit('artplayerPluginDanmuku:destroy') } diff --git a/packages/artplayer-plugin-danmuku/src/index.js b/packages/artplayer-plugin-danmuku/src/index.js index 7d09b933d..1b6d8326c 100644 --- a/packages/artplayer-plugin-danmuku/src/index.js +++ b/packages/artplayer-plugin-danmuku/src/index.js @@ -5,9 +5,9 @@ import Setting from './setting' export default function artplayerPluginDanmuku(option) { return (art) => { const danmuku = new Danmuku(art, option) - const setting = new Setting(art, danmuku) + const setting = art.isDestroy ? undefined : new Setting(art, danmuku) - if (danmuku.option.heatmap) { + if (!art.isDestroy && danmuku.option.heatmap) { heatmap(art, danmuku, danmuku.option.heatmap) } @@ -19,7 +19,7 @@ export default function artplayerPluginDanmuku(option) { hide: danmuku.hide.bind(danmuku), show: danmuku.show.bind(danmuku), reset: danmuku.reset.bind(danmuku), - mount: setting.mount.bind(setting), + mount: setting ? setting.mount.bind(setting) : () => {}, get option() { return danmuku.option }, diff --git a/packages/artplayer-plugin-danmuku/src/input.js b/packages/artplayer-plugin-danmuku/src/input.js new file mode 100644 index 000000000..83442a21a --- /dev/null +++ b/packages/artplayer-plugin-danmuku/src/input.js @@ -0,0 +1,88 @@ +import { bilibiliDanmuParseFromUrl } from './bilibili' +import { isPromiseInput } from './config' + +const inputs = new WeakMap() +const cancelled = Symbol('cancelled danmuku input') + +function stateFor(owner) { + if (!inputs.has(owner)) + inputs.set(owner, { closed: false, tasks: new Set(), emitting: [] }) + return inputs.get(owner) +} + +export function inputActive(owner) { + const state = stateFor(owner) + const task = state.emitting[state.emitting.length - 1] + return !state.closed && !owner.art.isDestroy && (!task || task.active()) +} + +export function beginInput(owner, replace) { + const state = stateFor(owner) + if (replace) { + for (const task of [...state.tasks]) { + if (task.replace) + task.cancel() + } + } + let stopped = state.closed || owner.art.isDestroy + let resolveCancel + const cancellation = new Promise(resolve => resolveCancel = resolve) + const handlers = new Set() + const controller = typeof AbortController === 'function' ? new AbortController() : undefined + const task = { + replace, + signal: controller?.signal, + active: () => !stopped && !state.closed && !owner.art.isDestroy, + onCancel(callback) { + if (!task.active()) { + callback() + return () => {} + } + handlers.add(callback) + return () => handlers.delete(callback) + }, + cancel() { + if (stopped) + return + stopped = true + resolveCancel(cancelled) + controller?.abort() + for (const callback of [...handlers]) callback() + handlers.clear() + }, + wait: value => Promise.race([value, cancellation]), + emit(danmu) { + state.emitting.push(task) + try { + return owner.emit(danmu) + } + finally { + state.emitting.pop() + } + }, + finish() { + state.tasks.delete(task) + handlers.clear() + }, + } + if (stopped) + resolveCancel(cancelled) + else state.tasks.add(task) + return task +} + +export function cancelInputs(owner) { + const state = stateFor(owner) + state.closed = true + for (const task of [...state.tasks]) task.cancel() +} + +export function readInput(target, task) { + if (typeof target === 'function') + return { asynchronous: true, value: target() } + if (isPromiseInput(target)) + return { asynchronous: true, value: target } + if (typeof target === 'string') + return { asynchronous: true, value: bilibiliDanmuParseFromUrl(target, task) } + return { asynchronous: false, value: Array.isArray(target) ? target : [] } +} diff --git a/refactor/baselines/danmuku-input-validation.json b/refactor/baselines/danmuku-input-validation.json new file mode 100644 index 000000000..87d0b36fb --- /dev/null +++ b/refactor/baselines/danmuku-input-validation.json @@ -0,0 +1,6771 @@ +{ + "schemaVersion": 1, + "task": "PKG-DANMUKU-03", + "status": "done", + "capturedAt": "2026-09-13T10:34:37.905Z", + "baselineCommit": "d3c91af92c6a2f7cfdbfa0cae8b4b8c6d5c93cde", + "historical": { + "tests": 86, + "file": "refactor/.cache/danmuku03-historical.log", + "sha256": "dedfbec48537135b45b8048d0ad1b813f17e6a32a219af7e75f039ea4d6ca7e7" + }, + "controlled": [ + { + "format": "source", + "tests": 46, + "file": "refactor/.cache/danmuku03-candidate-source-final.log", + "sha256": "bcb2f5fe1b3ecd065abe69c970e53d87beda9b7c3427904661d0df29c0e151e0" + }, + { + "format": "main", + "tests": 46, + "file": "refactor/.cache/danmuku03-candidate-main-final.log", + "sha256": "c0696fb27b88aa247ccf2edbba095377e37c31b39818ec44ca50e6393caf7120" + }, + { + "format": "legacy", + "tests": 46, + "file": "refactor/.cache/danmuku03-candidate-legacy-final.log", + "sha256": "8ee98db5268974875dcbe282ef0831126f9989cddb0a90726aa6fe04dd31d646" + } + ], + "browsers": [ + { + "format": "source", + "file": "refactor/.cache/danmuku03-browser-source/report.json", + "sha256": "5967aa83e7397c660aeef747bc836f831d3eb3a307b0c3a8d8836bdc35bc022d", + "tests": 24, + "versions": [ + "chromium: 153.0.8010.12", + "firefox: 155.0", + "webkit: 26.6" + ], + "spec": { + "file": "refactor/.cache/danmuku03-browser-source/danmuku-input.spec.js", + "sha256": "37a96982ea7bf2fb99df5e4d829ce1c3f512145092a440586c2d5ad7d0a2d6f3" + }, + "observations": [ + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "chromium", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/905ee766-8319-4ce2-b88c-2e8d835ea10f", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/f951e296-e24f-45b9-8f8c-d39fd2857833", + "blob:http://127.0.0.1:8084/905ee766-8319-4ce2-b88c-2e8d835ea10f" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/905ee766-8319-4ce2-b88c-2e8d835ea10f", + "blob:http://127.0.0.1:8084/f951e296-e24f-45b9-8f8c-d39fd2857833" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "chromium", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Failed to fetch", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/53e84983-e99b-43c2-b5d8-82ad422aedf3", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/0a70b910-de9e-4787-ad41-fd44da5e4db7", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/1309f4fe-4412-4b28-abea-b93708aa2869", + "blob:http://127.0.0.1:8084/53e84983-e99b-43c2-b5d8-82ad422aedf3", + "blob:http://127.0.0.1:8084/0a70b910-de9e-4787-ad41-fd44da5e4db7" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/53e84983-e99b-43c2-b5d8-82ad422aedf3", + "blob:http://127.0.0.1:8084/0a70b910-de9e-4787-ad41-fd44da5e4db7" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/1309f4fe-4412-4b28-abea-b93708aa2869", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/53e84983-e99b-43c2-b5d8-82ad422aedf3", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/0a70b910-de9e-4787-ad41-fd44da5e4db7", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Failed to fetch" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "chromium", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "chromium", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "chromium", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/98ac9323-1581-4733-a66f-80743412680f", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/6241b63d-0858-4cef-ad3b-bd07255989a2", + "blob:http://127.0.0.1:8084/98ac9323-1581-4733-a66f-80743412680f" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/98ac9323-1581-4733-a66f-80743412680f", + "blob:http://127.0.0.1:8084/6241b63d-0858-4cef-ad3b-bd07255989a2" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "chromium", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Failed to fetch", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/6ee902e7-93f3-4ad2-802a-2909ddbcd9b8", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/06021393-a47a-4d83-a613-f072247bd237", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/3448d6ff-80e6-4400-90e6-f45efa3d8728", + "blob:http://127.0.0.1:8084/6ee902e7-93f3-4ad2-802a-2909ddbcd9b8", + "blob:http://127.0.0.1:8084/06021393-a47a-4d83-a613-f072247bd237" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/6ee902e7-93f3-4ad2-802a-2909ddbcd9b8", + "blob:http://127.0.0.1:8084/06021393-a47a-4d83-a613-f072247bd237" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/3448d6ff-80e6-4400-90e6-f45efa3d8728", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/6ee902e7-93f3-4ad2-802a-2909ddbcd9b8", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/06021393-a47a-4d83-a613-f072247bd237", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Failed to fetch" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "chromium", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "chromium", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "firefox", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/0d452f87-775e-44d8-8fd1-4c83ac615d26", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/546ece4a-939f-48c3-b05c-4994863cb7a0", + "blob:http://127.0.0.1:8084/0d452f87-775e-44d8-8fd1-4c83ac615d26" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/0d452f87-775e-44d8-8fd1-4c83ac615d26", + "blob:http://127.0.0.1:8084/546ece4a-939f-48c3-b05c-4994863cb7a0" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "firefox", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: NetworkError when attempting to fetch resource.", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/4c228d41-4a97-4db6-916b-475458efe582", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/7bd47fa5-c950-4367-88f9-02573e7943c9", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/0f4d50de-a736-45d8-83a8-e30d98d1e403", + "blob:http://127.0.0.1:8084/4c228d41-4a97-4db6-916b-475458efe582", + "blob:http://127.0.0.1:8084/7bd47fa5-c950-4367-88f9-02573e7943c9" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/4c228d41-4a97-4db6-916b-475458efe582", + "blob:http://127.0.0.1:8084/7bd47fa5-c950-4367-88f9-02573e7943c9" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/0f4d50de-a736-45d8-83a8-e30d98d1e403", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/4c228d41-4a97-4db6-916b-475458efe582", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/7bd47fa5-c950-4367-88f9-02573e7943c9", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: NetworkError when attempting to fetch resource." + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "firefox", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "firefox", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "firefox", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/8e69328b-97d7-45f8-930f-1d97e2471fce", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/786c3352-0e99-4495-bad4-b1c673950c7c", + "blob:http://127.0.0.1:8084/8e69328b-97d7-45f8-930f-1d97e2471fce" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/8e69328b-97d7-45f8-930f-1d97e2471fce", + "blob:http://127.0.0.1:8084/786c3352-0e99-4495-bad4-b1c673950c7c" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "firefox", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: NetworkError when attempting to fetch resource.", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/d3b21649-c5d3-473b-b864-8835269ed5fc", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/8cf60956-cd8b-4572-add2-7024fd4113fe", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/f93b024d-58c5-4676-a5a0-0ca14d789d96", + "blob:http://127.0.0.1:8084/d3b21649-c5d3-473b-b864-8835269ed5fc", + "blob:http://127.0.0.1:8084/8cf60956-cd8b-4572-add2-7024fd4113fe" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/d3b21649-c5d3-473b-b864-8835269ed5fc", + "blob:http://127.0.0.1:8084/8cf60956-cd8b-4572-add2-7024fd4113fe" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/f93b024d-58c5-4676-a5a0-0ca14d789d96", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/d3b21649-c5d3-473b-b864-8835269ed5fc", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/8cf60956-cd8b-4572-add2-7024fd4113fe", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: NetworkError when attempting to fetch resource." + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "firefox", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "firefox", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "webkit", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/a328138c-3aa4-4ac9-8de6-40c3590077c7", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/226aa06b-e166-4c3f-b5f9-5591b6823721", + "blob:http://127.0.0.1:8084/a328138c-3aa4-4ac9-8de6-40c3590077c7" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/a328138c-3aa4-4ac9-8de6-40c3590077c7", + "blob:http://127.0.0.1:8084/226aa06b-e166-4c3f-b5f9-5591b6823721" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "webkit", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Load failed", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/38a6856b-0378-489a-a528-de8a728125b5", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/0f842e97-c239-4efc-b551-85078f831119", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/a0b2554c-93fa-4ce8-9f26-29c87fd0b758", + "blob:http://127.0.0.1:8084/38a6856b-0378-489a-a528-de8a728125b5", + "blob:http://127.0.0.1:8084/0f842e97-c239-4efc-b551-85078f831119" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/38a6856b-0378-489a-a528-de8a728125b5", + "blob:http://127.0.0.1:8084/0f842e97-c239-4efc-b551-85078f831119" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/a0b2554c-93fa-4ce8-9f26-29c87fd0b758", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/38a6856b-0378-489a-a528-de8a728125b5", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/0f842e97-c239-4efc-b551-85078f831119", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Load failed" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "webkit", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "webkit", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "webkit", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/a2cfda3f-fbec-4fd1-9751-15999e1c10f2", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/57339828-20bc-4787-a1fd-583ceb852d63", + "blob:http://127.0.0.1:8084/a2cfda3f-fbec-4fd1-9751-15999e1c10f2" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/a2cfda3f-fbec-4fd1-9751-15999e1c10f2", + "blob:http://127.0.0.1:8084/57339828-20bc-4787-a1fd-583ceb852d63" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "webkit", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Load failed", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/b6ec8cf5-3f57-48c8-9370-89d45fe713ca", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/8d777727-88cd-4c5f-ac93-2dc79f495db4", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/538cfe4a-e5e1-4eaf-8707-8f4fcf0dd281", + "blob:http://127.0.0.1:8084/b6ec8cf5-3f57-48c8-9370-89d45fe713ca", + "blob:http://127.0.0.1:8084/8d777727-88cd-4c5f-ac93-2dc79f495db4" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/b6ec8cf5-3f57-48c8-9370-89d45fe713ca", + "blob:http://127.0.0.1:8084/8d777727-88cd-4c5f-ac93-2dc79f495db4" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/538cfe4a-e5e1-4eaf-8707-8f4fcf0dd281", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/b6ec8cf5-3f57-48c8-9370-89d45fe713ca", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/8d777727-88cd-4c5f-ac93-2dc79f495db4", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Load failed" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "webkit", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "in-memory source UMD", + "sha256": "ed20cee74240503a48e20775189725a12e613f36180ea5355f27cf49a37d0927", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "webkit", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + } + ] + }, + { + "format": "main", + "file": "refactor/.cache/danmuku03-browser-main/report.json", + "sha256": "7a4ee62c21b8d1f0736cbb2bf6b8b6a41d7e2195f335727db9cfa7799f8bfe3d", + "tests": 24, + "versions": [ + "chromium: 153.0.8010.12", + "firefox: 155.0", + "webkit: 26.6" + ], + "spec": { + "file": "refactor/.cache/danmuku03-browser-main/danmuku-input.spec.js", + "sha256": "37a96982ea7bf2fb99df5e4d829ce1c3f512145092a440586c2d5ad7d0a2d6f3" + }, + "observations": [ + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "chromium", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/7931ae40-e773-474d-bba3-7936d2557d20", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/21188295-abbf-4178-b8c0-14d0d50a41f1", + "blob:http://127.0.0.1:8084/7931ae40-e773-474d-bba3-7936d2557d20" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/7931ae40-e773-474d-bba3-7936d2557d20", + "blob:http://127.0.0.1:8084/21188295-abbf-4178-b8c0-14d0d50a41f1" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "chromium", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Failed to fetch", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/758ff1f0-ce6e-400a-b8ea-3f5bc11cc500", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/d386f3f2-422b-4cb5-aee7-b28141fd5174", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/f9462648-c7bc-434a-821c-595931fad776", + "blob:http://127.0.0.1:8084/758ff1f0-ce6e-400a-b8ea-3f5bc11cc500", + "blob:http://127.0.0.1:8084/d386f3f2-422b-4cb5-aee7-b28141fd5174" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/758ff1f0-ce6e-400a-b8ea-3f5bc11cc500", + "blob:http://127.0.0.1:8084/d386f3f2-422b-4cb5-aee7-b28141fd5174" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/f9462648-c7bc-434a-821c-595931fad776", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/758ff1f0-ce6e-400a-b8ea-3f5bc11cc500", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/d386f3f2-422b-4cb5-aee7-b28141fd5174", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Failed to fetch" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "chromium", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "chromium", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "chromium", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/c574a64d-15f5-46e5-a0bc-23dae7b461c4", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/37a4ea34-2005-4b4d-afdb-48640460e8cf", + "blob:http://127.0.0.1:8084/c574a64d-15f5-46e5-a0bc-23dae7b461c4" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/c574a64d-15f5-46e5-a0bc-23dae7b461c4", + "blob:http://127.0.0.1:8084/37a4ea34-2005-4b4d-afdb-48640460e8cf" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "chromium", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Failed to fetch", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/c0769544-b3d3-4356-a28f-4625ddd297c2", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/34d648de-33d0-404c-a964-87cdcb48cf88", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/c93e4ca3-df59-467e-88ff-42a9517c8e41", + "blob:http://127.0.0.1:8084/c0769544-b3d3-4356-a28f-4625ddd297c2", + "blob:http://127.0.0.1:8084/34d648de-33d0-404c-a964-87cdcb48cf88" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/c0769544-b3d3-4356-a28f-4625ddd297c2", + "blob:http://127.0.0.1:8084/34d648de-33d0-404c-a964-87cdcb48cf88" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/c93e4ca3-df59-467e-88ff-42a9517c8e41", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/c0769544-b3d3-4356-a28f-4625ddd297c2", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/34d648de-33d0-404c-a964-87cdcb48cf88", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Failed to fetch" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "chromium", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "chromium", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "firefox", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/239cadd4-ff54-48b1-a4de-b5403deeb3aa", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/aa69ae8b-bbb7-4ba9-8b6b-a9fb65dfaf07", + "blob:http://127.0.0.1:8084/239cadd4-ff54-48b1-a4de-b5403deeb3aa" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/239cadd4-ff54-48b1-a4de-b5403deeb3aa", + "blob:http://127.0.0.1:8084/aa69ae8b-bbb7-4ba9-8b6b-a9fb65dfaf07" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "firefox", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: NetworkError when attempting to fetch resource.", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/c6cc6036-f034-4764-9d95-a65b071b7890", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/3bb99fcf-185b-4394-b38c-30c2a1c37797", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/d1d3fc57-cd09-471c-9ac8-f9ccf6bf0279", + "blob:http://127.0.0.1:8084/c6cc6036-f034-4764-9d95-a65b071b7890", + "blob:http://127.0.0.1:8084/3bb99fcf-185b-4394-b38c-30c2a1c37797" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/c6cc6036-f034-4764-9d95-a65b071b7890", + "blob:http://127.0.0.1:8084/3bb99fcf-185b-4394-b38c-30c2a1c37797" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/d1d3fc57-cd09-471c-9ac8-f9ccf6bf0279", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/c6cc6036-f034-4764-9d95-a65b071b7890", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/3bb99fcf-185b-4394-b38c-30c2a1c37797", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: NetworkError when attempting to fetch resource." + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "firefox", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "firefox", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "firefox", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/b9905e7d-9f92-445d-b85a-1ff762b92098", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/4dfb00d5-c560-4fa7-b39e-474a69904713", + "blob:http://127.0.0.1:8084/b9905e7d-9f92-445d-b85a-1ff762b92098" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/b9905e7d-9f92-445d-b85a-1ff762b92098", + "blob:http://127.0.0.1:8084/4dfb00d5-c560-4fa7-b39e-474a69904713" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "firefox", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: NetworkError when attempting to fetch resource.", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/23be7a32-759b-4b8e-96ff-a06317d72ea2", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/545cca93-0133-47af-95dc-4032b5b9fb71", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/57e72acd-9989-427b-a6ed-7913aaff0582", + "blob:http://127.0.0.1:8084/23be7a32-759b-4b8e-96ff-a06317d72ea2", + "blob:http://127.0.0.1:8084/545cca93-0133-47af-95dc-4032b5b9fb71" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/23be7a32-759b-4b8e-96ff-a06317d72ea2", + "blob:http://127.0.0.1:8084/545cca93-0133-47af-95dc-4032b5b9fb71" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/57e72acd-9989-427b-a6ed-7913aaff0582", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/23be7a32-759b-4b8e-96ff-a06317d72ea2", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/545cca93-0133-47af-95dc-4032b5b9fb71", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: NetworkError when attempting to fetch resource." + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "firefox", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "firefox", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "webkit", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/dd54677a-2040-4fd9-95b0-7e1f6f70803c", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/58271a89-b04a-4c14-8bb0-4fe0ee187dcc", + "blob:http://127.0.0.1:8084/dd54677a-2040-4fd9-95b0-7e1f6f70803c" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/dd54677a-2040-4fd9-95b0-7e1f6f70803c", + "blob:http://127.0.0.1:8084/58271a89-b04a-4c14-8bb0-4fe0ee187dcc" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "webkit", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Load failed", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/896a7f66-a0e3-4ff6-b64b-6d2fe427cbd7", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/9d19914c-8db7-423b-8fe2-3ab319a2203e", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/b49edc53-604c-475c-9eff-4628e0953f0d", + "blob:http://127.0.0.1:8084/896a7f66-a0e3-4ff6-b64b-6d2fe427cbd7", + "blob:http://127.0.0.1:8084/9d19914c-8db7-423b-8fe2-3ab319a2203e" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/896a7f66-a0e3-4ff6-b64b-6d2fe427cbd7", + "blob:http://127.0.0.1:8084/9d19914c-8db7-423b-8fe2-3ab319a2203e" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/b49edc53-604c-475c-9eff-4628e0953f0d", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/896a7f66-a0e3-4ff6-b64b-6d2fe427cbd7", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/9d19914c-8db7-423b-8fe2-3ab319a2203e", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Load failed" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "webkit", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "webkit", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "webkit", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/0eda17c0-ceaf-4aff-bdbc-e688880271f1", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/f3799092-4118-491e-8563-0378426e7f7b", + "blob:http://127.0.0.1:8084/0eda17c0-ceaf-4aff-bdbc-e688880271f1" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/0eda17c0-ceaf-4aff-bdbc-e688880271f1", + "blob:http://127.0.0.1:8084/f3799092-4118-491e-8563-0378426e7f7b" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "webkit", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Load failed", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/b0af81e5-78b9-490d-8607-7bd6b15d8b45", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/f098d7da-c12f-4b1d-b4b5-22f4332ef120", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/8d19c0b6-5b76-4494-996c-312252a77571", + "blob:http://127.0.0.1:8084/b0af81e5-78b9-490d-8607-7bd6b15d8b45", + "blob:http://127.0.0.1:8084/f098d7da-c12f-4b1d-b4b5-22f4332ef120" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/b0af81e5-78b9-490d-8607-7bd6b15d8b45", + "blob:http://127.0.0.1:8084/f098d7da-c12f-4b1d-b4b5-22f4332ef120" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/8d19c0b6-5b76-4494-996c-312252a77571", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/b0af81e5-78b9-490d-8607-7bd6b15d8b45", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/f098d7da-c12f-4b1d-b4b5-22f4332ef120", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Load failed" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "webkit", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "webkit", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + } + ] + }, + { + "format": "legacy", + "file": "refactor/.cache/danmuku03-browser-legacy/report.json", + "sha256": "ebc55a046d6eea28a8e35a41e3ad0cada700d48514ff25f35553fd8a844ad82d", + "tests": 24, + "versions": [ + "chromium: 153.0.8010.12", + "firefox: 155.0", + "webkit: 26.6" + ], + "spec": { + "file": "refactor/.cache/danmuku03-browser-legacy/danmuku-input.spec.js", + "sha256": "37a96982ea7bf2fb99df5e4d829ce1c3f512145092a440586c2d5ad7d0a2d6f3" + }, + "observations": [ + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "chromium", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/3fcfcef4-5b18-464b-8f37-cd295bae1e04", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/f0752248-ce73-4a96-9811-3c81539f7d94", + "blob:http://127.0.0.1:8084/3fcfcef4-5b18-464b-8f37-cd295bae1e04" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/3fcfcef4-5b18-464b-8f37-cd295bae1e04", + "blob:http://127.0.0.1:8084/f0752248-ce73-4a96-9811-3c81539f7d94" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "chromium", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Failed to fetch", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/6ca228f3-cac3-473d-8bce-003ab1a4d050", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/cad9b870-2d0b-4663-a1b7-d0e1da5eb29b", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/662b9a07-4172-43ba-83c9-912e0931d860", + "blob:http://127.0.0.1:8084/6ca228f3-cac3-473d-8bce-003ab1a4d050", + "blob:http://127.0.0.1:8084/cad9b870-2d0b-4663-a1b7-d0e1da5eb29b" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/6ca228f3-cac3-473d-8bce-003ab1a4d050", + "blob:http://127.0.0.1:8084/cad9b870-2d0b-4663-a1b7-d0e1da5eb29b" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/662b9a07-4172-43ba-83c9-912e0931d860", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/6ca228f3-cac3-473d-8bce-003ab1a4d050", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/cad9b870-2d0b-4663-a1b7-d0e1da5eb29b", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Failed to fetch" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "chromium", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "chromium", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "chromium", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/41e13124-e4a4-4e29-91f6-65e2c46ea18c", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/3675f14d-bdd6-4044-987f-9a9f2ac1adb6", + "blob:http://127.0.0.1:8084/41e13124-e4a4-4e29-91f6-65e2c46ea18c" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/41e13124-e4a4-4e29-91f6-65e2c46ea18c", + "blob:http://127.0.0.1:8084/3675f14d-bdd6-4044-987f-9a9f2ac1adb6" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "chromium", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Failed to fetch", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/239700e3-b8b8-4146-880e-daa243e68e8b", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/05604c54-429d-42c5-9e06-9ae3e245dbca", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/55bb16d1-7f5d-46fe-92cf-b95d5e819036", + "blob:http://127.0.0.1:8084/239700e3-b8b8-4146-880e-daa243e68e8b", + "blob:http://127.0.0.1:8084/05604c54-429d-42c5-9e06-9ae3e245dbca" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/239700e3-b8b8-4146-880e-daa243e68e8b", + "blob:http://127.0.0.1:8084/05604c54-429d-42c5-9e06-9ae3e245dbca" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/55bb16d1-7f5d-46fe-92cf-b95d5e819036", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/239700e3-b8b8-4146-880e-daa243e68e8b", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/05604c54-429d-42c5-9e06-9ae3e245dbca", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Failed to fetch" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "chromium", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "chromium", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "chromium", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "firefox", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/b9c43144-0267-4280-893d-dad00c3a1ad5", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/b3e8fc5c-e5e9-4b1d-a53d-275672406c27", + "blob:http://127.0.0.1:8084/b9c43144-0267-4280-893d-dad00c3a1ad5" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/b9c43144-0267-4280-893d-dad00c3a1ad5", + "blob:http://127.0.0.1:8084/b3e8fc5c-e5e9-4b1d-a53d-275672406c27" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "firefox", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: NetworkError when attempting to fetch resource.", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/431b48a2-f5ac-416e-8e84-d08a73f21622", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/5968bf3a-1a59-47b8-be95-45b9917b1e27", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/f8967902-1ffc-41b6-9fa6-d1792bc6a115", + "blob:http://127.0.0.1:8084/431b48a2-f5ac-416e-8e84-d08a73f21622", + "blob:http://127.0.0.1:8084/5968bf3a-1a59-47b8-be95-45b9917b1e27" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/431b48a2-f5ac-416e-8e84-d08a73f21622", + "blob:http://127.0.0.1:8084/5968bf3a-1a59-47b8-be95-45b9917b1e27" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/f8967902-1ffc-41b6-9fa6-d1792bc6a115", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/431b48a2-f5ac-416e-8e84-d08a73f21622", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/5968bf3a-1a59-47b8-be95-45b9917b1e27", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: NetworkError when attempting to fetch resource." + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "firefox", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "firefox", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "firefox", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/baab6557-76e3-4a57-90b9-2be3ae117ebd", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/47f0017f-86ba-404f-8b94-8602136e5daa", + "blob:http://127.0.0.1:8084/baab6557-76e3-4a57-90b9-2be3ae117ebd" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/baab6557-76e3-4a57-90b9-2be3ae117ebd", + "blob:http://127.0.0.1:8084/47f0017f-86ba-404f-8b94-8602136e5daa" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "firefox", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: NetworkError when attempting to fetch resource.", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/514f5125-bcd8-4f42-99a3-8e185ce74d83", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/28f90c0b-324e-4187-8712-fc33f34584a9", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/1adb9657-7fc6-434b-be43-c770612b4640", + "blob:http://127.0.0.1:8084/514f5125-bcd8-4f42-99a3-8e185ce74d83", + "blob:http://127.0.0.1:8084/28f90c0b-324e-4187-8712-fc33f34584a9" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/514f5125-bcd8-4f42-99a3-8e185ce74d83", + "blob:http://127.0.0.1:8084/28f90c0b-324e-4187-8712-fc33f34584a9" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/1adb9657-7fc6-434b-be43-c770612b4640", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/514f5125-bcd8-4f42-99a3-8e185ce74d83", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/28f90c0b-324e-4187-8712-fc33f34584a9", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: NetworkError when attempting to fetch resource." + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "firefox", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "firefox", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "firefox", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "webkit", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/c528fc39-c5d8-4f1a-b38a-71415209edee", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/388a252d-70cd-439d-b09d-e82cdb4852f5", + "blob:http://127.0.0.1:8084/c528fc39-c5d8-4f1a-b38a-71415209edee" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/c528fc39-c5d8-4f1a-b38a-71415209edee", + "blob:http://127.0.0.1:8084/388a252d-70cd-439d-b09d-e82cdb4852f5" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (published)", + "browser": "webkit", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Load failed", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/e53a0834-d7da-4080-bbfc-419a75ab8f40", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/eaf7d288-0213-4b0f-8dc3-0e2ff84350c8", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/b943a887-24d7-4312-81be-b86588a529b1", + "blob:http://127.0.0.1:8084/e53a0834-d7da-4080-bbfc-419a75ab8f40", + "blob:http://127.0.0.1:8084/eaf7d288-0213-4b0f-8dc3-0e2ff84350c8" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/e53a0834-d7da-4080-bbfc-419a75ab8f40", + "blob:http://127.0.0.1:8084/eaf7d288-0213-4b0f-8dc3-0e2ff84350c8" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/b943a887-24d7-4312-81be-b86588a529b1", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/e53a0834-d7da-4080-bbfc-419a75ab8f40", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/eaf7d288-0213-4b0f-8dc3-0e2ff84350c8", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Load failed" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (published)", + "browser": "webkit", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "published", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (published)", + "browser": "webkit", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native Worker startup failure falls back locally (candidate)", + "browser": "webkit", + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/0fbc3dec-4fdf-4cc7-b8c7-c49a54edc05b", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/e7e709ee-f227-4665-be3c-fcd3d3e2795e", + "blob:http://127.0.0.1:8084/0fbc3dec-4fdf-4cc7-b8c7-c49a54edc05b" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/0fbc3dec-4fdf-4cc7-b8c7-c49a54edc05b", + "blob:http://127.0.0.1:8084/e7e709ee-f227-4665-be3c-fcd3d3e2795e" + ], + "errors": [], + "fault": "Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real." + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input native parser cleanup and rejected fetch (candidate)", + "browser": "webkit", + "name": "danmuku-native-parser", + "value": { + "parsed": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "color": "#ff", + "userID": "user", + "rowID": 456 + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "color": "#ffffff", + "userID": "u", + "rowID": 789 + } + ], + "emptyIdentity": true, + "beforeDestroy": 2, + "rejection": "TypeError: Load failed", + "parserWorkers": [ + { + "url": "blob:http://127.0.0.1:8084/87feb582-99da-4354-b433-52e291cfceba", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/5fbfe43f-eec5-4691-b030-dc43a638c4d1", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/4b6e3b19-237e-4e96-8a73-c884833b5048", + "blob:http://127.0.0.1:8084/87feb582-99da-4354-b433-52e291cfceba", + "blob:http://127.0.0.1:8084/5fbfe43f-eec5-4691-b030-dc43a638c4d1" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/87feb582-99da-4354-b433-52e291cfceba", + "blob:http://127.0.0.1:8084/5fbfe43f-eec5-4691-b030-dc43a638c4d1" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/4b6e3b19-237e-4e96-8a73-c884833b5048", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/87feb582-99da-4354-b433-52e291cfceba", + "terminated": 1, + "replies": [ + { + "danmus": [ + { + "text": "A & B", + "time": 0, + "mode": 0, + "fontSize": 25, + "color": "#ff", + "timestamp": 123, + "pool": 0, + "userID": "user", + "rowID": 456, + "style": {} + }, + { + "text": "TOP", + "time": 2, + "mode": 2, + "fontSize": 20, + "color": "#ffffff", + "timestamp": 124, + "pool": 0, + "userID": "u", + "rowID": 789, + "style": {} + } + ], + "id": 1 + } + ] + }, + { + "url": "blob:http://127.0.0.1:8084/5fbfe43f-eec5-4691-b030-dc43a638c4d1", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "error", + "error": "TypeError: Load failed" + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku input replacement append and destruction (candidate)", + "browser": "webkit", + "name": "danmuku-native-input-ownership", + "value": { + "initialEvents": [ + "show", + "config", + "reset", + "loaded" + ], + "latestIdentity": true, + "oldIdentity": true, + "destroyedIdentity": true, + "queue": [ + { + "text": "new", + "time": 0 + }, + { + "text": "append B", + "time": 0.5 + }, + { + "text": "append A", + "time": 0.5 + } + ], + "finalQueue": [ + "new", + "append B", + "append A" + ], + "loadedBefore": 4, + "loadedAfter": 4, + "events": [ + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 0 + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "show" + }, + { + "name": "config" + }, + { + "name": "reset" + }, + { + "name": "loaded", + "count": 1 + }, + { + "name": "loaded", + "count": 2 + }, + { + "name": "loaded", + "count": 3 + }, + { + "name": "destroy" + } + ] + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "webkit", + "name": "danmuku-input-build", + "value": { + "core": "candidate", + "artifact": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92", + "worker": "Native Blob Worker with observation only", + "media": "/test/pattern.mp4" + } + }, + { + "title": "Danmuku config callback replacement and validation (candidate)", + "browser": "webkit", + "name": "danmuku-native-config", + "value": { + "unchangedIdentity": true, + "count": 2, + "unchangedCount": 2, + "unchangedOption": true, + "filterIdentity": true, + "error": "Error: [Type Error]: 'option.speed' require 'number' type, but got 'string'", + "receiver": [ + true, + true + ], + "emittedIdentity": true, + "input": { + "text": "included", + "time": 0, + "mode": 0, + "style": {}, + "color": "#FFFFFF" + }, + "queue": [ + { + "text": "included", + "time": 0 + } + ] + } + } + ] + } + ], + "prior": [ + { + "format": "main", + "file": "refactor/.cache/danmuku03-candidate-main.log", + "sha256": "e01b7d5e20bfa729d4477898956238ddbf40621a16bb3758e99efa65b31451ae", + "passed": 41, + "failed": 5, + "cause": "Parser fault injection ran before factory initialization and affected the runtime-looked-up built inline track Worker. Move only parser fault injection after factory+flush; assertions unchanged; no production change needed for these failures." + }, + { + "format": "legacy", + "file": "refactor/.cache/danmuku03-candidate-legacy.log", + "sha256": "681cf27c30e09393a025aa938de90ade1b4417fa0156be32c89685de3803f13a", + "passed": 41, + "failed": 5, + "cause": "Parser fault injection ran before factory initialization and affected the runtime-looked-up built inline track Worker. Move only parser fault injection after factory+flush; assertions unchanged; no production change needed for these failures." + } + ], + "checks": [ + { + "file": "refactor/.cache/danmuku03-build.log", + "sha256": "87093054c296fd843e8c701893dd23628cafdee98499f03a76a1d59908c316fe" + }, + { + "file": "refactor/.cache/danmuku03-typecheck.log", + "sha256": "33ced90b0fff4881e9a4477aca1e8c51a5715b8170f71c860518bbdff1c4e606" + }, + { + "file": "refactor/.cache/danmuku03-lint-final.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ], + "inputs": [ + { + "path": "package.json", + "algorithm": "sha256-lf", + "sha256": "da413ff9632f9d0ac72c8dcb6ff38966a2b16bad2e2fdb60e6a949be4e5f5ef5" + }, + { + "path": "packages/artplayer-plugin-danmuku/ARCHITECTURE.md", + "algorithm": "sha256-lf", + "sha256": "72210d9d48124aeb3c73055554677c99aa226cc23c59d629ed5dd5cee60f19fe" + }, + { + "path": "packages/artplayer-plugin-danmuku/README.md", + "algorithm": "sha256-lf", + "sha256": "af74f34e9a975933954c814bb312473f43606ea1905d5b098232a38597c9b5ee" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "algorithm": "sha256-lf", + "sha256": "4d4122a3ecbc118a057e47edd4e6653db58232e78d627eabe6c5d3f9f5d8d935" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "algorithm": "sha256-lf", + "sha256": "92d289cc50477df1a5b0142ca1803dd603cd7bb4deab471eb2e7c0f10a023c92" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs", + "algorithm": "sha256-lf", + "sha256": "8c560f5eab211d5bd11d0259df70a377f863789972f788eaf714e6b455a279ba" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/bilibili-parser.js", + "algorithm": "sha256-lf", + "sha256": "46720235c8b9f986ecdda436a16b03a8215e4f3dc0151c16df17dca68f0302bc" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/bilibili.js", + "algorithm": "sha256-lf", + "sha256": "39310fb20dfe5f98a492460d7a9f7d3078367ce8f70e383d89f9dc7029d297cc" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/config.js", + "algorithm": "sha256-lf", + "sha256": "33b70af031f5a5df0b93bc2dcef74b32ce4674b6a248476dc3a6f65a29deeb1c" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/danmuku.js", + "algorithm": "sha256-lf", + "sha256": "e2af06376a1365b62d6df8bb02093bb0e9de6657a76bbf0991e8ba82d9cb60c8" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/heatmap.js", + "algorithm": "sha256-lf", + "sha256": "3cf77ff82f62958728445eaf9c044bc4cbcf04a4ea82e753e7c44e59128c16f4" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/check_off.svg", + "algorithm": "sha256-lf", + "sha256": "8cb8e31da1bf7b5be8f4065920b46e1988755910218fb121dd3d9649c50156cf" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/check_on.svg", + "algorithm": "sha256-lf", + "sha256": "c5a34f56c56b0f696b4e3740c73e803076b5bfdfdee89714793118fc4c6baa84" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/config.svg", + "algorithm": "sha256-lf", + "sha256": "bbd7c2952740ac8220f396477eaca3b01f6c249866d880ece38c565cc77c5e36" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/mode_0_off.svg", + "algorithm": "sha256-lf", + "sha256": "fe9e5e1745e8206a4eda0d0ac30815f301c37acccec2564bbda51c1fa4bd1e1e" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/mode_0_on.svg", + "algorithm": "sha256-lf", + "sha256": "96720a81f0315caf6c70664de934e49d22e6dd070a9f6ca1fec2a9a96af4d138" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/mode_1_off.svg", + "algorithm": "sha256-lf", + "sha256": "3ccd22bf3648dc260cd1d0b6a60a9226a853764ae20efb8fe9c940cd856c81b2" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/mode_1_on.svg", + "algorithm": "sha256-lf", + "sha256": "2238ca27e3c7c4c687d10de13b794d8be70ae1c5ba9225ebad27f251cfe5f12a" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/mode_2_off.svg", + "algorithm": "sha256-lf", + "sha256": "31423e3d67c00839adc77dd871dc1da55f77c8b83b5a37b445e4b4c3f46c6d6d" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/mode_2_on.svg", + "algorithm": "sha256-lf", + "sha256": "96b4c9db8d5055c8777fda74cb5e0f32b3e12e62a1e0bacbf75d2088f4505e2f" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/off.svg", + "algorithm": "sha256-lf", + "sha256": "4e6743ee28002e16117caf47ea3bffb34ca75bcc4118744b4ee720829ae3b563" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/on.svg", + "algorithm": "sha256-lf", + "sha256": "cc0fb7048bff6dc3f14a17ed549209c67e9bc34368cb1571e0abd8035c7b59b7" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/img/style.svg", + "algorithm": "sha256-lf", + "sha256": "9034ea7b9a214a6d4c086c02cf85f42b1c4487cec0906e919fc91e99dfff529a" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/index.js", + "algorithm": "sha256-lf", + "sha256": "956e30c857dbb15147506924308c363dcc4a07bcf1bb97ce3fc436afa366cd9c" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/input.js", + "algorithm": "sha256-lf", + "sha256": "6740c347018290de2a8d90925148f63ec005d294426ed8d3192e69313171b8fc" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/setting.js", + "algorithm": "sha256-lf", + "sha256": "ffbaa808551410dc015a5f3ffe4e091f5e1b1bb45eee2d9790cf15cbf82eace2" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/style.less", + "algorithm": "sha256-lf", + "sha256": "7148a1bd32f5d3d743db4a4ae8d39dc0e1406d2bff18df42cbb20e6705d5fed9" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/worker.js", + "algorithm": "sha256-lf", + "sha256": "05995a16cd728dd5ca513587a777b4be9012b8cc4b77e3a46fed1826e6769c53" + }, + { + "path": "playwright.config.js", + "algorithm": "sha256-lf", + "sha256": "df2384f5626252ec933725867e400ec22eb92bc5e6f04873059b32decf2f2d56" + }, + { + "path": "test/browser/danmuku-input.spec.js", + "algorithm": "sha256-lf", + "sha256": "37a96982ea7bf2fb99df5e4d829ce1c3f512145092a440586c2d5ad7d0a2d6f3" + }, + { + "path": "test/browser/fixtures.js", + "algorithm": "sha256-lf", + "sha256": "313414b8958eb5b03a2da8595fa0bbf6bfdc01484373898c6cb7179545df175d" + }, + { + "path": "test/browser/server.mjs", + "algorithm": "sha256-lf", + "sha256": "1406fac75eabc83b987e79ec7b5f97a948b27770bfd5f5290d7cb9155196f08f" + }, + { + "path": "test/danmuku-input.test.js", + "algorithm": "sha256-lf", + "sha256": "1201198ca091f101a1e97cd9653929a744a5ef44ae33ad35ec73e2e2b4826b53" + }, + { + "path": "test/danmuku-parser.test.js", + "algorithm": "sha256-lf", + "sha256": "ae2dae43f447c1361662f70e90009c8978e1d96bbb133b7b07baafc9d765d70e" + }, + { + "path": "test/helpers/danmuku-candidate.js", + "algorithm": "sha256-lf", + "sha256": "215aa631baa36f2434e95fc11f178fd9a4cca71834db0dd5e93d06356674445a" + } + ], + "decisions": [ + "Preserve internal instance return identity, synchronous array prefix and sequential await emit.", + "Cancel only superseded replacements and all loads on destroy; independent appends survive replacement. Cancelled public loads resolve the internal instance silently.", + "Preserve input object normalization and legacy XML fields; correct explicit time zero.", + "Commit validated configuration; compare callable/Promise identity and retain ordinary structural equality.", + "Observe automatic initial load rejection locally; public load emits original error once and rejects.", + "Rollback early destroy subscription on initial configuration or track Worker failure; correct the existing resize callback identity." + ], + "limitations": [ + "This step is JS responsibility separation; full owned TS and installed public declarations are task 06.", + "Track scheduling, pending visibility, settings lifecycle, heatmap and long-run native overlap remain open.", + "ESM built successfully but is not counted among the three controlled/native runtime variants.", + "Native tests use local XML/fault fixtures; no live Bilibili, CSP matrix, physical Safari or npm release acceptance." + ], + "releaseReady": false +} diff --git a/refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md b/refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md new file mode 100644 index 000000000..84c2a698d --- /dev/null +++ b/refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md @@ -0,0 +1,73 @@ +# PKG-DANMUKU-03 加载、解析和配置重构 + +弹幕生产源码已开始分层:`config.js`负责默认值、schema、变化比较与归一化; +`input.js`负责输入形式、替换/追加归属及取消;`bilibili-parser.js`保持原XML转换; +`bilibili.js`负责网络、解析Worker及资源收尾。原`danmuku.js`保留队列提交与事件出口, +`index.js`保留同步门面。包内[维护地图](../../packages/artplayer-plugin-danmuku/ARCHITECTURE.md) +和README说明这些边界。此步使用JS拆分,完整TS/Worker消息/公开声明归06,不能将本步 +描述成整个弹幕包已完成TS迁移。 + +## 保持的契约与明确修复 + +- `load/emit`仍返回Promise并解析为内部Danmuku,区别于同步门面;绑定方法、getter、 + 属性顺序、`config/hide/show/reset`同步返回身份和`mount`的undefined结果保持。 +- 空数组初始化仍在注册返回前发出show/config/reset/loaded;非空数组在首次await前 + 插入第一项,其后逐项await emit。过滤器接收当前option为this,加载函数无显式receiver。 +- 无参数load成功获取输入后替换;显式load追加且不修改配置。并发追加彼此独立,也可 + 在替换后继续追加。逐项错误仍保留此前已接受项,并由load发一次原始error再拒绝。 +- 修复旧替换迟到覆盖新替换;新替换只使旧替换失效。销毁取消所有加载。取消的公共 + Promise成功解析为同一内部实例,不发迟到loaded/error、不写队列;外部Promise仍可 + 运行,但其拒绝有局部观察。可用时abort fetch,不依赖AbortController才能正确取消。 +- 在reset、filter及异步返回后检查归属。只检查当前嵌套emit任务,允许旧filter里同步 + 发起的新替换正常写入,再阻止旧调用续写;不是把旧任务的取消状态传给整个嵌套调用栈。 +- `time:0`保持0,负值钳位0,缺省为currentTime+0.5;原输入对象补mode/style/color后 + 过滤及浅复制行为仍保留,未借修复时间顺便更改所有对象身份。 +- 单独更新函数配置现在生效,Promise按身份更新,普通结构相等项仍保持静默。先校验 + 临时合并值再提交,非法配置不污染当前option。初始Promise输入与既有声明对齐。 +- fetch/text拒绝不再因async Promise executor悬挂;公开load保留拒绝,自动初始load + 在实例内观察拒绝并console.warn。空XML正常结束,解析Worker失败沿相同parser降级。 + 成功/降级/协议错误/取消均清理Worker与Blob;其中一项清理抛错仍尝试另一项,原业务 + 错误优先。原XML实体顺序、模式、未补齐颜色与数值转换保持,不引入HTTP状态硬拒绝。 + +## 初始化闭环与审查结果 + +为了捕获初始show/config/loaded中同步销毁,提前建立destroy订阅并在入口跳过后续 +Setting/heatmap创建。非法配置或初始轨道Worker构造失败回滚提前订阅并重抛原错。 +新增测试实际发现旧resize解绑身份错误;本步最小修正为解绑实际this.resize,避免 +初始化已销毁仍留监听。Setting既有DOM/计时器、RAF/轨道算法与heatmap没有在本步重写。 + +三代理分工为源码、输入测试、解析测试,父代理负责审查、真实浏览器、构建及整合。 +只读复审也独立指出Worker构造失败的提前订阅泄漏,修复后通过同一原错及零新增监听 +断言。没有修改冻结历史helper或把候选修复反写进旧版证据。 + +## 验证与失败证据 + +- 冻结旧契约44项及旧失败42项,共86项父代理复跑通过。 +- 候选20项输入/配置/初始化和26项解析/取消/错误测试,源码、正式main、正式legacy + 各46项联合通过,共138次;严格子进程确认自动加载已观察、公开load仍拒绝。 +- 原生浏览器:3种实现×2核心×4场景×3引擎,共72项通过,无跳过或未处理页面错误。 + 核心为真实已发布5.4.0及候选5.4.1;Chromium、Firefox和Windows WebKit。 + 成功路径断言收到实际Worker回复,不能让fallback掩盖压缩后函数脚本失效;故障场景 + 单独替换原生解析Worker脚本为抛错脚本,确认局部错误处理、真实fallback与资源回收。 +- 使用仓库正常build生成main/legacy/esm及docs/compiled对应产物;ESM本步为构建通过, + 不把它计入三组运行用例。改动文件lint及全仓typecheck通过,后者检查366个已迁移 + 生产TS文件与消费者矩阵,未迁移JS不算TS覆盖。 + +产物单测首轮main/legacy各41通过5失败:测试在工厂前注入parser Worker故障,产物 +inline Worker查全局Worker时误伤了初始轨道Worker;source夹具先捕获构造器而未显露 +此问题。将parser专用故障移到正常初始化和flush之后,原清理/原错断言全部保持; +三组各26解析复验和最终各46联合复验均通过。原失败日志保留且机器报告引用哈希, +不把这五项错误测试归为新增生产缺陷。初始化泄漏的红测则确有源码修复。 + +完整输入指纹、构建文件、日志、浏览器版本和原始附件见 +[机器证据](../baselines/danmuku-input-validation.json)。未新增依赖或改动Yarn锁文件, +根`test:unit`与`test:danmuku`加入候选测试,浏览器沿用既有CI发现规则。 + +## 后续范围 + +INPUT-01与CONFIG-01在本步关闭;ASYNC-01只完成输入/解析子项,待04/05的可见性续体、 +轨道Worker及单RAF所有权。TYPE-01待06真实安装声明矩阵,CLEANUP-01只修resize部分。 +此前真实轨道重叠、heatmap零采样和Setting资源问题保持开放;72输入浏览器通过不代表 +它们被修复。04继续时钟/队列/轨道,05负责渲染/设置/热力图,06完整TS,07做稳定性验收。 +本地XML及故障夹具不是Bilibili实网、CSP矩阵、Safari设备或长时负载验收,也不是npm +发布就绪证明。本任务独立commit,无推送、标签或发布。 diff --git a/refactor/plan.md b/refactor/plan.md index b070bad79..cb05329bf 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 228 项,范围 22 个包及工作区/示例。 -状态:todo 72 / doing 15 / blocked 0 / done 141 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 71 / doing 15 / blocked 0 / done 142 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -311,8 +311,8 @@ | --- | --- | --- | --- | --- | --- | --- | | PKG-DANMUKU-01 | artplayer-plugin-danmuku
核对弹幕全部公开契约 | BASE-05 | emit/load/config/hide/show/reset/mount、option/isHide/isStop/icons 与事件清单 | 源码、声明、发布包、Bilibili 输入和设置入口对照 | H | done | | PKG-DANMUKU-02 | artplayer-plugin-danmuku
建立弹幕算法与浏览器基线 | PKG-DANMUKU-01, ENG-05, ENG-08 | 密集/稀疏弹幕、过滤/异步输入、seek/倍率/重载测试 | 轨道选择、发射顺序、事件、内存和可视结果可重跑 | H | done | -| PKG-DANMUKU-03 | artplayer-plugin-danmuku
整理加载、解析与配置 | PKG-DANMUKU-02, CORE-08 | bilibili/input/parser/config 的分层与取消 | 旧格式、回调、过滤、追加/替换语义保持 | M | doing | -| PKG-DANMUKU-04 | artplayer-plugin-danmuku
整理时钟、队列与轨道调度 | PKG-DANMUKU-03, CORE-10 | danmuku 调度器和确定性时钟测试 | seek/暂停/倍率/长时间运行无顺序和碰撞回归 | H | todo | +| PKG-DANMUKU-03 | artplayer-plugin-danmuku
整理加载、解析与配置 | PKG-DANMUKU-02, CORE-08 | bilibili/input/parser/config 的分层与取消 | 旧格式、回调、过滤、追加/替换语义保持 | M | done | +| PKG-DANMUKU-04 | artplayer-plugin-danmuku
整理时钟、队列与轨道调度 | PKG-DANMUKU-03, CORE-10 | danmuku 调度器和确定性时钟测试 | seek/暂停/倍率/长时间运行无顺序和碰撞回归 | H | doing | | PKG-DANMUKU-05 | artplayer-plugin-danmuku
整理 DOM 渲染、设置、热力图与 worker | PKG-DANMUKU-04, CORE-14, CORE-18 | renderer/setting/heatmap/worker 职责及资源归属 | mount/icons/设置和 worker 协议保持,销毁无后台工作 | H | todo | | PKG-DANMUKU-06 | artplayer-plugin-danmuku
迁移 TS 与公开声明 | PKG-DANMUKU-05, ENG-04, ENG-06 | 自有模块、worker 消息、option/item/result 类型 | 旧参数、扩展字段和事件消费通过,vendored 边界清楚 | H | todo | | PKG-DANMUKU-07 | artplayer-plugin-danmuku
弹幕能力稳定性验收 | PKG-DANMUKU-06 | 负载/渲染/资源对比及 mask 可依赖的稳定边界 | 在试点核心中旧插件 API 与关键帧路径通过 | H | todo | @@ -555,6 +555,7 @@ - PKG-DPIP-05: [记录](changes/2026-09-12-PKG-DPIP-05-native-checkpoint.md) [记录](baselines/dpip-native-validation.json) - PKG-DANMUKU-01: [记录](baselines/danmuku-release.json) [记录](baselines/danmuku-contract.md) [记录](baselines/danmuku-contract-validation.json) [记录](changes/2026-09-13-PKG-DANMUKU-01-contracts.md) - PKG-DANMUKU-02: [记录](changes/2026-09-13-PKG-DANMUKU-02-controlled-failures.md) [记录](baselines/danmuku-failures-validation.json) [记录](changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md) [记录](baselines/danmuku-browser-validation.json) +- PKG-DANMUKU-03: [记录](changes/2026-09-13-PKG-DANMUKU-03-input.md) [记录](baselines/danmuku-input-validation.json) - PKG-CANVAS-01: [记录](changes/2026-09-12-PKG-CANVAS-01-contract.md) [记录](baselines/canvas-contract.md) [记录](baselines/canvas-release.json) [记录](canvas-validation.md) [记录](baselines/canvas-contract-validation.json) - PKG-CANVAS-02: [记录](changes/2026-09-12-PKG-CANVAS-02-tests.md) [记录](baselines/canvas-behavior-validation.json) [记录](canvas-validation.md) - PKG-CANVAS-03: [记录](changes/2026-09-12-PKG-CANVAS-03-lifecycle.md) [记录](baselines/canvas-lifecycle-validation.json) [记录](canvas-validation.md) diff --git a/refactor/progress.md b/refactor/progress.md index 748f83e1d..e30f43e5f 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,16 @@ # 进度与证据 +## PKG-DANMUKU-03 输入、解析和配置源码完成 + +拆分input/config/XML parser模块,修复过期替换、加载拒绝、资源清理、time0、函数配置 +及初始化销毁/失败;保留同步数组前缀、内部返回身份、追加及回调约定。候选三格式 +各46单测、三格式×两核心×三引擎共72真实浏览器项、旧基线86项通过;正式构建、 +lint和366生产TS/消费者检查通过。首轮产物parser故障夹具误伤已校正,原日志保留。 +见[变更](changes/2026-09-13-PKG-DANMUKU-03-input.md)及[机器证据](baselines/danmuku-input-validation.json)。 +228项:142 done、15 doing、71 todo。04进入时钟/轨道; +本步仍是JS分层,完整TS归06,旧轨道重叠/Setting/heatmap风险继续。无推送或发布。 + + ## PKG-DANMUKU-02 历史错误和真实浏览器基线完成 42项受控旧缺陷、48项原生Worker/视频/布局/设置及6项300弹幕负载观察通过;所有 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index 1b9e9952a..203449fa2 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -228,8 +228,8 @@ | MASK-DOM-01 | open / 已复现 | Mask registration and frame processing assume Danmuku DOM, readable frames and a 2D context | PKG-MASK-02, PKG-MASK-03, PKG-MASK-05 | | MASK-NOTICE-01 | open / 源码/产物事实 | Published Mask bundles lack a separate third-party notice despite bundled SDK code | PKG-MASK-06 | | DANMUKU-TYPE-01 | open / 已复现 | Danmuku historical declared results, optional mount and initial Promise input disagree with runtime | PKG-DANMUKU-03, PKG-DANMUKU-06 | -| DANMUKU-INPUT-01 | open / 已复现 | Danmuku replaces time zero with current time plus 0.5 and mutates supplied input objects | PKG-DANMUKU-02, PKG-DANMUKU-03 | -| DANMUKU-CONFIG-01 | open / 已复现 | Danmuku JSON configuration comparison ignores a replacement filter function | PKG-DANMUKU-02, PKG-DANMUKU-03 | +| DANMUKU-INPUT-01 | resolved / 已复现 | Danmuku replaces time zero with current time plus 0.5 and mutates supplied input objects | PKG-DANMUKU-02, PKG-DANMUKU-03 | +| DANMUKU-CONFIG-01 | resolved / 已复现 | Danmuku JSON configuration comparison ignores a replacement filter function | PKG-DANMUKU-02, PKG-DANMUKU-03 | | DANMUKU-CLEANUP-01 | open / 已复现 | Danmuku removes a different resize callback and retains the settings lock timer on destroy | PKG-DANMUKU-02, PKG-DANMUKU-05 | | DANMUKU-TYPE-02 | open / 源码/产物事实 | Danmuku id, icons, Slider steps, heatmap points and partial configuration drift from declarations | PKG-DANMUKU-05, PKG-DANMUKU-06 | | DANMUKU-ASYNC-01 | open / 已复现 | Danmuku asynchronous input, worker replies and pending visibility have unverified failure and cancellation paths | PKG-DANMUKU-02, PKG-DANMUKU-03, PKG-DANMUKU-04, PKG-DANMUKU-05 | diff --git a/refactor/risks.json b/refactor/risks.json index 5379ed391..b6b9f53c1 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -5031,7 +5031,9 @@ "evidence": [ "refactor/baselines/danmuku-contract.md", "refactor/baselines/danmuku-contract-validation.json", - "refactor/baselines/danmuku-release.json" + "refactor/baselines/danmuku-release.json", + "refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md", + "refactor/baselines/danmuku-input-validation.json" ], "compatibleResolution": "Preserve latest published complete root type contracts and actual legal JS return identities; expose accurate types separately.", "closureCriteria": "Installed consumers verify latest root assignments and precise async/getter/mount contracts; initial Promise mismatch has an explicit tested resolution." @@ -5040,7 +5042,7 @@ "id": "DANMUKU-INPUT-01", "title": "Danmuku replaces time zero with current time plus 0.5 and mutates supplied input objects", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-DANMUKU-02", "PKG-DANMUKU-03" @@ -5048,16 +5050,25 @@ "evidence": [ "refactor/baselines/danmuku-contract.md", "refactor/baselines/danmuku-contract-validation.json", - "refactor/baselines/danmuku-release.json" + "refactor/baselines/danmuku-release.json", + "refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md", + "refactor/baselines/danmuku-input-validation.json" ], "compatibleResolution": "Separate the time-zero defect from observable input object identity/mutation before changing parsing or queue ownership.", - "closureCriteria": "Zero, absent, negative and explicit times plus object identity are covered with candidate expectations and compatibility decisions." + "closureCriteria": "Zero, absent, negative and explicit times plus object identity are covered with candidate expectations and compatibility decisions.", + "resolutionEvidence": [ + "refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md", + "refactor/baselines/danmuku-input-validation.json", + "test/danmuku-input.test.js", + "test/browser/danmuku-input.spec.js" + ], + "resolutionRationale": "Candidate source/main/legacy preserve input mutation and internal returns while explicitly preserving zero and clamping negative time; native browsers confirm zero." }, { "id": "DANMUKU-CONFIG-01", "title": "Danmuku JSON configuration comparison ignores a replacement filter function", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-DANMUKU-02", "PKG-DANMUKU-03" @@ -5065,10 +5076,19 @@ "evidence": [ "refactor/baselines/danmuku-contract.md", "refactor/baselines/danmuku-contract-validation.json", - "refactor/baselines/danmuku-release.json" + "refactor/baselines/danmuku-release.json", + "refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md", + "refactor/baselines/danmuku-input-validation.json" ], "compatibleResolution": "Compare relevant configuration values without dropping callable properties; retain callback receivers and config event ordering.", - "closureCriteria": "Function-only changes take effect, unchanged options remain stable and partial configuration/callback reentry has regression evidence." + "closureCriteria": "Function-only changes take effect, unchanged options remain stable and partial configuration/callback reentry has regression evidence.", + "resolutionEvidence": [ + "refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md", + "refactor/baselines/danmuku-input-validation.json", + "test/danmuku-input.test.js", + "test/browser/danmuku-input.spec.js" + ], + "resolutionRationale": "Candidate source/main/legacy apply function-only updates, preserve equal plain values and callback receivers, reject invalid updates transactionally, and withstand synchronous reentry; native browsers confirm." }, { "id": "DANMUKU-CLEANUP-01", @@ -5084,7 +5104,9 @@ "refactor/baselines/danmuku-contract-validation.json", "refactor/baselines/danmuku-release.json", "refactor/changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md", - "refactor/baselines/danmuku-browser-validation.json" + "refactor/baselines/danmuku-browser-validation.json", + "refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md", + "refactor/baselines/danmuku-input-validation.json" ], "compatibleResolution": "Track and release exact subscription and timer ownership across settings, renderer and instances.", "closureCriteria": "Repeated lifecycle and pending settings actions leave no owned subscriptions, timers or late DOM writes." @@ -5124,7 +5146,9 @@ "refactor/baselines/danmuku-failures-validation.json", "test/danmuku-failures.test.js", "refactor/changes/2026-09-13-PKG-DANMUKU-02-browser-baseline.md", - "refactor/baselines/danmuku-browser-validation.json" + "refactor/baselines/danmuku-browser-validation.json", + "refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md", + "refactor/baselines/danmuku-input-validation.json" ], "compatibleResolution": "Reproduce load ordering, Bilibili rejection and late worker/visibility paths before defining operation ownership.", "closureCriteria": "Failures settle, obsolete results cannot alter new queues or destroyed instances, and worker resources are released with browser evidence." diff --git a/refactor/tasks.json b/refactor/tasks.json index ad31220c4..04410bb68 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -3300,11 +3300,14 @@ "PKG-DANMUKU-02", "CORE-08" ], - "status": "doing", + "status": "done", "risk": "M", "deliverable": "bilibili/input/parser/config 的分层与取消", "acceptance": "旧格式、回调、过滤、追加/替换语义保持", - "evidence": [] + "evidence": [ + "changes/2026-09-13-PKG-DANMUKU-03-input.md", + "baselines/danmuku-input-validation.json" + ] }, { "id": "PKG-DANMUKU-04", @@ -3317,7 +3320,7 @@ "PKG-DANMUKU-03", "CORE-10" ], - "status": "todo", + "status": "doing", "risk": "H", "deliverable": "danmuku 调度器和确定性时钟测试", "acceptance": "seek/暂停/倍率/长时间运行无顺序和碰撞回归", diff --git a/test/browser/danmuku-input.spec.js b/test/browser/danmuku-input.spec.js new file mode 100644 index 000000000..98fbc83f4 --- /dev/null +++ b/test/browser/danmuku-input.spec.js @@ -0,0 +1,211 @@ +import fs from 'node:fs' +import process from 'node:process' +import { hash } from '../../refactor/scripts/releases.mjs' +import { compilePackage } from '../helpers/load.js' +import { expect, test } from './fixtures.js' + +let candidate +test.beforeAll(async () => { + candidate = process.env.ARTPLAYER_DANMUKU_ARTIFACT + ? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8') + : await compilePackage('artplayer-plugin-danmuku', 'umd') +}) + +async function setup(page, core, testInfo) { + const external = [] + const origin = new URL(testInfo.project.use.baseURL).origin + await page.route('**/*', (route) => { + if (new URL(route.request().url()).origin !== origin) { + external.push(route.request().url()) + return route.abort() + } + return route.continue() + }) + await page.goto(`/test/player.html?core=${core}`) + await page.evaluate(() => { + const evidence = window.inputEvidence = { urls: [], revoked: [], workers: [], events: [] } + const create = URL.createObjectURL.bind(URL) + const revoke = URL.revokeObjectURL.bind(URL) + URL.createObjectURL = (blob) => { + const url = create(blob) + evidence.urls.push(url) + return url + } + URL.revokeObjectURL = (url) => { + evidence.revoked.push(url) + return revoke(url) + } + const NativeWorker = window.Worker + window.Worker = class extends NativeWorker { + constructor(url, options) { + super(url, options) + this.observation = { url: String(url), terminated: 0, replies: [] } + evidence.workers.push(this.observation) + this.addEventListener('message', event => this.observation.replies.push(event.data)) + } + + terminate() { + this.observation.terminated++ + return super.terminate() + } + } + }) + await page.addScriptTag({ content: candidate }) + await page.evaluate(() => { + window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true }) + for (const name of ['show', 'hide', 'config', 'reset', 'loaded', 'error', 'destroy']) { + window.art.on(`artplayerPluginDanmuku:${name}`, value => window.inputEvidence.events.push({ name, count: Array.isArray(value) ? value.length : undefined, error: name === 'error' ? String(value) : undefined })) + } + window.art.plugins.add(window.artplayerPluginDanmuku({ danmuku: [], heatmap: false })) + window.inputPlugin = window.art.plugins.artplayerPluginDanmuku + }) + await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true) + await testInfo.attach('danmuku-input-build', { contentType: 'application/json', body: JSON.stringify({ core, artifact: process.env.ARTPLAYER_DANMUKU_ARTIFACT || 'in-memory source UMD', sha256: hash(candidate), worker: 'Native Blob Worker with observation only', media: '/test/pattern.mp4' }) }) + return external +} + +for (const core of ['published', 'candidate']) { + test(`Danmuku input native Worker startup failure falls back locally (${core})`, async ({ page }, testInfo) => { + const external = await setup(page, core, testInfo) + await page.route('**/test/fallback.xml', route => route.fulfill({ contentType: 'text/xml', body: 'FALLBACK' })) + const result = await page.evaluate(async () => { + const NativeWorker = window.Worker + const createdBefore = window.inputEvidence.urls.length + const workersBefore = window.inputEvidence.workers.length + window.Worker = class extends NativeWorker { + constructor(url, options) { + const fault = URL.createObjectURL(new Blob(['throw new Error("controlled parser startup failure")'], { type: 'application/javascript' })) + super(fault, options) + URL.revokeObjectURL(fault) + } + } + const internal = await window.inputPlugin.load('/test/fallback.xml') + const result = { queue: internal.queue.map(({ text, time, mode }) => ({ text, time, mode })), workers: window.inputEvidence.workers.slice(workersBefore), urls: window.inputEvidence.urls.slice(createdBefore), revoked: window.inputEvidence.revoked, errors: window.inputEvidence.events.filter(event => event.name === 'error') } + window.art.destroy() + return result + }) + expect(result.queue).toEqual([{ text: 'FALLBACK', time: 0, mode: 1 }]) + expect(result.workers).toHaveLength(1) + expect(result.workers[0].terminated).toBe(1) + expect(result.urls).toHaveLength(2) + for (const url of result.urls) + expect(result.revoked.filter(item => item === url)).toHaveLength(1) + expect(result.errors).toEqual([]) + expect(external).toEqual([]) + await testInfo.attach('danmuku-native-worker-fault', { contentType: 'application/json', body: JSON.stringify({ ...result, fault: 'Test replaces only the newly created native parser Worker script with a thrown startup Error; fetch, fallback parser and error dispatch remain real.' }) }) + }) + + test(`Danmuku input native parser cleanup and rejected fetch (${core})`, async ({ page }, testInfo) => { + const external = await setup(page, core, testInfo) + await page.route('**/test/input.xml', route => route.fulfill({ contentType: 'text/xml', body: ' A & B TOP' })) + await page.route('**/test/empty.xml', route => route.fulfill({ contentType: 'text/xml', body: '' })) + await page.route('**/test/rejected.xml', route => route.abort('failed')) + const result = await page.evaluate(async () => { + const plugin = window.inputPlugin + const start = window.inputEvidence.workers.length + const internal = await plugin.load('/test/input.xml') + const parsed = internal.queue.map(({ text, time, mode, color, userID, rowID }) => ({ text, time, mode, color, userID, rowID })) + const emptyIdentity = await plugin.load('/test/empty.xml') === internal + let rejection + try { + await plugin.load('/test/rejected.xml') + } + catch (error) { rejection = String(error) } + const parserWorkers = window.inputEvidence.workers.slice(start) + const beforeDestroy = internal.queue.length + window.art.destroy() + return { parsed, emptyIdentity, beforeDestroy, rejection, parserWorkers, ...window.inputEvidence } + }) + expect(result.parsed).toEqual([{ text: 'A & B', time: 0, mode: 0, color: '#ff', userID: 'user', rowID: 456 }, { text: 'TOP', time: 2, mode: 2, color: '#ffffff', userID: 'u', rowID: 789 }]) + expect(result.emptyIdentity).toBe(true) + expect(result.beforeDestroy).toBe(2) + expect(result.rejection).toBeTruthy() + expect(result.events.filter(event => event.name === 'error')).toHaveLength(1) + expect(result.parserWorkers.length).toBeGreaterThanOrEqual(1) + for (const worker of result.parserWorkers) { + expect(worker.terminated).toBe(1) + expect(result.revoked.filter(url => url === worker.url)).toHaveLength(1) + expect(worker.replies).toHaveLength(1) + expect(Array.isArray(worker.replies[0].danmus)).toBe(true) + } + expect(external).toEqual([]) + await testInfo.attach('danmuku-native-parser', { contentType: 'application/json', body: JSON.stringify(result) }) + }) + + test(`Danmuku input replacement append and destruction (${core})`, async ({ page }, testInfo) => { + const external = await setup(page, core, testInfo) + const result = await page.evaluate(async () => { + const plugin = window.inputPlugin + const internal = plugin.config({ danmuku: [] }) + const initialEvents = window.inputEvidence.events.map(event => event.name) + let resolveOld + plugin.config({ danmuku: () => new Promise(resolve => resolveOld = resolve) }) + const old = plugin.load() + plugin.config({ danmuku: [{ text: 'new', time: 0 }] }) + const latestIdentity = await plugin.load() === internal + resolveOld([{ text: 'obsolete' }]) + const oldIdentity = await old === internal + let resolveA + let resolveB + const a = plugin.load(() => new Promise(resolve => resolveA = resolve)) + const b = plugin.load(() => new Promise(resolve => resolveB = resolve)) + resolveB([{ text: 'append B' }]) + await b + resolveA([{ text: 'append A' }]) + await a + const queue = internal.queue.map(item => ({ text: item.text, time: item.time })) + let resolveLate + const late = plugin.load(() => new Promise(resolve => resolveLate = resolve)) + const loadedBefore = window.inputEvidence.events.filter(event => event.name === 'loaded').length + window.art.destroy() + resolveLate([{ text: 'after destroy' }]) + const destroyedIdentity = await late === internal + return { initialEvents, latestIdentity, oldIdentity, destroyedIdentity, queue, finalQueue: internal.queue.map(item => item.text), loadedBefore, loadedAfter: window.inputEvidence.events.filter(event => event.name === 'loaded').length, events: window.inputEvidence.events } + }) + expect(result.initialEvents.slice(0, 4)).toEqual(['show', 'config', 'reset', 'loaded']) + expect(result.latestIdentity && result.oldIdentity && result.destroyedIdentity).toBe(true) + expect(result.queue.map(item => item.text)).toEqual(['new', 'append B', 'append A']) + expect(result.queue[0].time).toBe(0) + expect(result.finalQueue).toEqual(['new', 'append B', 'append A']) + expect(result.loadedAfter).toBe(result.loadedBefore) + expect(result.events.filter(event => event.name === 'error')).toEqual([]) + expect(external).toEqual([]) + await testInfo.attach('danmuku-native-input-ownership', { contentType: 'application/json', body: JSON.stringify(result) }) + }) + + test(`Danmuku config callback replacement and validation (${core})`, async ({ page }, testInfo) => { + const external = await setup(page, core, testInfo) + const result = await page.evaluate(async () => { + const plugin = window.inputPlugin + const receiver = [] + const next = function (item) { + receiver.push(this === plugin.option) + return item.text !== 'excluded' + } + const internal = plugin.config({ filter: next }) + const count = window.inputEvidence.events.filter(event => event.name === 'config').length + const unchangedIdentity = plugin.config({ filter: next, margin: [...plugin.option.margin] }) === internal + const unchangedCount = window.inputEvidence.events.filter(event => event.name === 'config').length + const previous = plugin.option + let error + try { + plugin.config({ speed: 'invalid' }) + } + catch (failure) { error = String(failure) } + await plugin.emit({ text: 'excluded', time: 0 }) + const input = { text: 'included', time: 0 } + const emittedIdentity = await plugin.emit(input) === internal + const result = { unchangedIdentity, count, unchangedCount, unchangedOption: plugin.option === previous, filterIdentity: plugin.option.filter === next, error, receiver, emittedIdentity, input, queue: internal.queue.map(item => ({ text: item.text, time: item.time })) } + window.art.destroy() + return result + }) + expect(result.unchangedIdentity && result.unchangedOption && result.filterIdentity && result.emittedIdentity).toBe(true) + expect(result.unchangedCount).toBe(result.count) + expect(result.error).toBeTruthy() + expect(result.receiver).toEqual([true, true]) + expect(result.queue).toEqual([{ text: 'included', time: 0 }]) + expect(result.input).toMatchObject({ time: 0, mode: 0, color: '#FFFFFF', style: {} }) + expect(external).toEqual([]) + await testInfo.attach('danmuku-native-config', { contentType: 'application/json', body: JSON.stringify(result) }) + }) +} diff --git a/test/danmuku-input.test.js b/test/danmuku-input.test.js new file mode 100644 index 000000000..40d7e7442 --- /dev/null +++ b/test/danmuku-input.test.js @@ -0,0 +1,355 @@ +import assert from 'node:assert/strict' +// eslint-disable-next-line test/no-import-node-test -- Candidate compatibility and failure tests use the Node runner. +import test from 'node:test' +import { setImmediate as nextTurn } from 'node:timers/promises' +import { danmukuCandidate, danmukuCandidateEnvironment } from './helpers/danmuku-candidate.js' +import { deferred } from './helpers/danmuku.js' + +const implementation = await danmukuCandidate() +const prefix = `Danmuku ${implementation.name}` +const names = env => env.events.map(event => event.name) +const loaded = env => env.events.filter(event => event.name === 'artplayerPluginDanmuku:loaded') +const failures = env => env.events.filter(event => event.name === 'artplayerPluginDanmuku:error') + +function fixture(option = { danmuku: [] }) { + const env = danmukuCandidateEnvironment(implementation) + const plugin = env.factory(option)(env.art) + return { env, plugin } +} + +test(`${prefix}: synchronous empty initialization and load retain method return identity and getter surface`, async () => { + const { env, plugin } = fixture() + assert.equal(typeof plugin.then, 'undefined') + assert.deepEqual(Object.keys(plugin), ['name', 'emit', 'load', 'config', 'hide', 'show', 'reset', 'mount', 'option', 'isHide', 'isStop']) + assert.deepEqual(names(env), ['artplayerPluginDanmuku:show', 'artplayerPluginDanmuku:config', 'artplayerPluginDanmuku:reset', 'artplayerPluginDanmuku:loaded']) + const internal = plugin.show() + assert.notEqual(internal, plugin) + assert.equal(internal.name, undefined) + const start = env.events.length + const promise = plugin.load() + assert.equal(typeof promise.then, 'function') + assert.deepEqual(names(env).slice(start), ['artplayerPluginDanmuku:reset', 'artplayerPluginDanmuku:loaded'], 'Empty arrays finish the visible work before the first await') + assert.equal(await promise, internal) + const emit = plugin.emit + assert.equal(await emit({ text: 'bound', time: 12 }), internal) + assert.equal(plugin.hide(), internal) + assert.equal(plugin.reset(), internal) + env.destroy() +}) + +test(`${prefix}: nonempty array load inserts the first row synchronously and resolves to internal state`, async () => { + const { env, plugin } = fixture() + const internal = plugin.show() + const count = loaded(env).length + const pending = plugin.load([{ text: 'first', time: 1 }, { text: 'second', time: 2 }]) + assert.deepEqual(Array.from(internal.queue, row => row.text), ['first']) + assert.equal(loaded(env).length, count) + assert.equal(await pending, internal) + assert.deepEqual(Array.from(internal.queue, row => row.text), ['first', 'second']) + assert.equal(loaded(env).at(-1).args[0], internal.queue) + env.destroy() +}) + +test(`${prefix}: explicit targets append and omitted target replaces without changing configured input`, async () => { + const { env, plugin } = fixture() + const configured = [{ text: 'configured', time: 2 }] + plugin.config({ danmuku: configured }) + const internal = await plugin.load() + await plugin.load([{ text: 'append', time: 3 }]) + assert.equal(plugin.option.danmuku, configured) + assert.deepEqual(Array.from(internal.queue, row => row.text), ['configured', 'append']) + await plugin.load(undefined) + assert.deepEqual(Array.from(internal.queue, row => row.text), ['configured']) + env.destroy() +}) + +test(`${prefix}: independent asynchronous appends survive reversed completion and a replacement`, async () => { + const { env, plugin } = fixture() + const first = deferred() + const second = deferred() + const a = plugin.load(() => first.promise) + const b = plugin.load(() => second.promise) + plugin.config({ danmuku: [{ text: 'replacement', time: 1 }] }) + const internal = await plugin.load() + second.resolve([{ text: 'second completed first', time: 2 }]) + assert.equal(await b, internal) + first.resolve([{ text: 'first completed last', time: 3 }]) + assert.equal(await a, internal) + assert.deepEqual(Array.from(internal.queue, row => row.text), ['replacement', 'second completed first', 'first completed last']) + env.destroy() +}) + +test(`${prefix}: replacement supersedes only the older replacement and settles it before its input completes`, async () => { + const { env, plugin } = fixture() + const first = deferred() + const second = deferred() + const jobs = [first, second] + plugin.config({ danmuku: () => jobs.shift().promise }) + const internal = plugin.show() + let oldSettled = false + const a = plugin.load().then((value) => { + oldSettled = true + return value + }) + const b = plugin.load() + await env.flush() + assert.equal(oldSettled, true, 'Cancellation must settle the public Promise without awaiting a stale source') + assert.equal(await a, internal) + second.resolve([{ text: 'current', time: 1 }]) + await b + const count = env.events.length + first.resolve([{ text: 'obsolete', time: 2 }]) + await env.flush() + assert.deepEqual(Array.from(internal.queue, row => row.text), ['current']) + assert.equal(env.events.length, count) + env.destroy() +}) + +test(`${prefix}: destroyed pending replacement and appends settle without late state or error events`, async () => { + const { env, plugin } = fixture() + const replacement = deferred() + const append = deferred() + plugin.config({ danmuku: () => replacement.promise }) + const internal = plugin.show() + const pending = [plugin.load(), plugin.load(() => append.promise)] + let settled = false + const all = Promise.all(pending).then((results) => { + settled = true + return results + }) + env.destroy() + await env.flush() + assert.equal(settled, true, 'Destroy settles all owned loads without depending on their unresolved inputs') + assert.deepEqual(await all, [internal, internal]) + const count = env.events.length + replacement.resolve([{ text: 'late', time: 1 }]) + append.reject(new Error('late append failure')) + await nextTurn() + assert.equal(internal.queue.length, 0) + assert.equal(env.events.length, count) + assert.equal(failures(env).length, 0) +}) + +test(`${prefix}: reset event reentry can replace input without an older load clearing or adding rows`, async () => { + const { env, plugin } = fixture() + plugin.config({ danmuku: [{ text: 'old', time: 1 }] }) + let nested + let entered = false + env.art.on('artplayerPluginDanmuku:reset', () => { + if (entered) + return + entered = true + plugin.config({ danmuku: [{ text: 'new', time: 2 }] }) + nested = plugin.load() + }) + const count = loaded(env).length + const internal = await plugin.load() + await nested + assert.deepEqual(Array.from(internal.queue, row => row.text), ['new']) + assert.equal(loaded(env).length, count + 1) + env.destroy() +}) + +for (const action of ['replace', 'destroy']) { + test(`${prefix}: filter synchronous ${action} reentry prevents the obsolete row from being pushed`, async () => { + const { env, plugin } = fixture() + let nested + plugin.config({ + danmuku: [{ text: 'old1', time: 1 }, { text: 'old2', time: 2 }], + filter(row) { + if (row.text === 'old1') { + if (action === 'destroy') { + env.destroy() + } + else { + plugin.config({ danmuku: [{ text: 'new', time: 3 }] }) + nested = plugin.load() + } + } + return true + }, + }) + const count = loaded(env).length + const internal = await plugin.load() + await nested + assert.deepEqual(Array.from(internal.queue, row => row.text), action === 'destroy' ? [] : ['new']) + assert.equal(loaded(env).length, count + (action === 'destroy' ? 0 : 1)) + if (action !== 'destroy') + env.destroy() + }) +} + +test(`${prefix}: loader receiver, filter receiver, input mutation and extra fields retain the public behavior`, async () => { + let loaderReceiver = 'unset' + let filterReceiver + const { env, plugin } = fixture({ + danmuku: [], + filter() { + filterReceiver = this + return true + }, + }) + const row = { text: 'zero', time: 0, id: 'custom-id', extension: { value: 1 } } + const internal = await plugin.load(function () { + loaderReceiver = this + return [row] + }) + assert.equal(loaderReceiver, undefined) + assert.equal(filterReceiver, plugin.option) + assert.equal(row.time, 0, 'Accepted defect correction preserves an explicit zero timestamp') + assert.equal(row.mode, 0) + assert.equal(row.color, '#FFFFFF') + assert.deepEqual(Object.keys(row.style), []) + assert.equal(internal.queue[0].extension, row.extension) + assert.equal(internal.queue[0].id, row.id) + const missing = { text: 'default time' } + const negative = { text: 'negative', time: -2 } + await plugin.emit(missing) + await plugin.emit(negative) + assert.equal(missing.time, 10.5) + assert.equal(negative.time, 0) + env.destroy() +}) + +test(`${prefix}: invalid later rows preserve earlier insertions and reject with the single emitted original error`, async () => { + const { env, plugin } = fixture() + const internal = plugin.show() + let caught + const loadedCount = loaded(env).length + await assert.rejects(plugin.load([{ text: 'valid', time: 1 }, { text: 123 }]), (error) => { + caught = error + return error.message.includes('text') + }) + assert.deepEqual(Array.from(internal.queue, row => row.text), ['valid']) + assert.equal(failures(env).length, 1) + assert.equal(failures(env)[0].args[0], caught) + assert.equal(loaded(env).length, loadedCount) + env.destroy() +}) + +test(`${prefix}: function configuration changes are applied while structurally equal plain values remain silent`, () => { + const { env, plugin } = fixture() + const original = plugin.option + const filter = () => false + const before = env.events.length + plugin.config({ filter }) + assert.equal(plugin.option.filter, filter) + assert.notEqual(plugin.option, original) + assert.deepEqual(names(env).slice(before), ['artplayerPluginDanmuku:show', 'artplayerPluginDanmuku:config']) + const updated = plugin.option + const count = env.events.length + plugin.config({ filter, margin: [10, '25%'], points: [], OPACITY: {} }) + assert.equal(env.events.length, count) + assert.equal(plugin.option, updated) + env.destroy() +}) + +test(`${prefix}: invalid configuration cannot replace or partially mutate a valid option`, () => { + const { env, plugin } = fixture() + const original = plugin.option + const count = env.events.length + assert.throws(() => plugin.config({ opacity: 0.2, speed: 'invalid', visible: false }), /speed/iu) + assert.equal(plugin.option, original) + assert.equal(plugin.option.opacity, 1) + assert.equal(plugin.option.visible, true) + assert.equal(plugin.isHide, false) + assert.equal(env.events.length, count) + env.destroy() +}) + +test(`${prefix}: initial Promise source is accepted and successful completion reports loaded`, async () => { + const input = deferred() + const { env, plugin } = fixture({ danmuku: input.promise }) + assert.equal(typeof plugin.then, 'undefined') + assert.equal(loaded(env).length, 0) + input.resolve([{ text: 'initial promise', time: 0 }]) + await env.flush() + assert.equal(loaded(env).length, 1) + assert.equal(loaded(env)[0].args[0][0].text, 'initial promise') + assert.equal(loaded(env)[0].args[0][0].time, 0) + env.destroy() +}) + +test(`${prefix}: ignored constructor load rejection is locally observed while explicit load still rejects`, async () => { + const error = new Error('source failed') + const { env, plugin } = fixture({ danmuku: () => Promise.reject(error) }) + await nextTurn() + assert.equal(failures(env).length, 1) + assert.equal(failures(env)[0].args[0], error) + await assert.rejects(plugin.load(), failure => failure === error) + assert.equal(failures(env).length, 2) + assert.equal(failures(env)[1].args[0], error) + env.destroy() +}) + +for (const event of ['show', 'config', 'loaded']) { + test(`${prefix}: synchronous initial ${event} destruction leaves a complete facade without later Worker or setting creation`, async () => { + const env = danmukuCandidateEnvironment(implementation) + const destroy = () => env.destroy() + env.art.on(`artplayerPluginDanmuku:${event}`, destroy) + const before = [...env.listeners.values()].reduce((total, listeners) => total + listeners.length, 0) + const plugin = env.factory({ danmuku: [], heatmap: true })(env.art) + assert.equal(env.art.isDestroy, true) + assert.equal(typeof plugin.then, 'undefined') + assert.deepEqual(Object.keys(plugin), ['name', 'emit', 'load', 'config', 'hide', 'show', 'reset', 'mount', 'option', 'isHide', 'isStop']) + assert.equal(plugin.name, 'artplayerPluginDanmuku') + for (const key of ['option', 'isHide', 'isStop']) { + const descriptor = Object.getOwnPropertyDescriptor(plugin, key) + assert.equal(typeof descriptor.get, 'function') + assert.equal(descriptor.set, undefined) + assert.equal(descriptor.enumerable, true) + assert.equal(descriptor.configurable, true) + } + assert.equal(plugin.option.heatmap, true) + assert.equal(plugin.isStop, true) + assert.equal(plugin.isHide, false) + assert.equal(plugin.mount('#external'), undefined) + assert.equal(plugin.mount(), undefined) + assert.equal(env.workers.length, event === 'loaded' ? 1 : 0) + assert(env.workers.every(worker => worker.terminated), 'A Worker created before loaded reentry must be terminated') + assert.equal(env.art.template.$controlsCenter.children.length, 0) + assert.equal(env.art.template.$player.children.length, 0) + assert.equal(env.controls.length, 0) + assert.equal(env.proxies.length, 0) + assert.equal(env.frames.size, 0) + assert.equal(env.timers.size, 0) + assert.equal([...env.listeners.values()].reduce((total, listeners) => total + listeners.length, 0), before, 'Only the pre-existing user listener remains') + await env.flush() + assert.equal(env.consoleWarnings.length, 0) + }) +} + +test(`${prefix}: invalid initial configuration rolls back the early destroy listener before throwing`, () => { + const env = danmukuCandidateEnvironment(implementation) + const existingDestroy = () => {} + env.art.on('destroy', existingDestroy) + const before = [...env.listeners.values()].reduce((total, listeners) => total + listeners.length, 0) + assert.throws(() => env.factory({ danmuku: [], speed: 'invalid' })(env.art), /speed/iu) + assert.equal([...env.listeners.values()].reduce((total, listeners) => total + listeners.length, 0), before) + assert.deepEqual(env.listeners.get('destroy'), [existingDestroy]) + assert.equal(env.workers.length, 0) + assert.equal(env.art.template.$controlsCenter.children.length, 0) + assert.equal(env.art.template.$player.children.length, 0) + assert.equal(env.controls.length, 0) + assert.equal(env.proxies.length, 0) + assert.equal(env.frames.size, 0) + assert.equal(env.timers.size, 0) +}) + +test(`${prefix}: initial scheduler Worker construction failure preserves the error and rolls back subscriptions`, () => { + const error = new Error('initial scheduler Worker failed') + const env = danmukuCandidateEnvironment(implementation, { initialWorkerError: error }) + const existingDestroy = () => {} + env.art.on('destroy', existingDestroy) + const before = [...env.listeners.values()].reduce((total, listeners) => total + listeners.length, 0) + assert.throws(() => env.factory({ danmuku: [] })(env.art), failure => failure === error) + assert.equal([...env.listeners.values()].reduce((total, listeners) => total + listeners.length, 0), before) + assert.deepEqual(env.listeners.get('destroy'), [existingDestroy]) + assert.equal(env.workers.length, 0) + assert.equal(env.art.template.$controlsCenter.children.length, 0) + assert.equal(env.art.template.$player.children.length, 0) + assert.equal(env.controls.length, 0) + assert.equal(env.proxies.length, 0) + assert.equal(env.frames.size, 0) + assert.equal(env.timers.size, 0) +}) diff --git a/test/danmuku-parser.test.js b/test/danmuku-parser.test.js new file mode 100644 index 000000000..b092808f0 --- /dev/null +++ b/test/danmuku-parser.test.js @@ -0,0 +1,403 @@ +import assert from 'node:assert/strict' +import { spawnSync } from 'node:child_process' +import process from 'node:process' +// eslint-disable-next-line test/no-import-node-test -- Candidate parser failures require strict Node rejection checks. +import test from 'node:test' +import { fileURLToPath } from 'node:url' +import vm from 'node:vm' +import { danmukuCandidate, danmukuCandidateEnvironment } from './helpers/danmuku-candidate.js' +import { danmukuEnvironment, danmukuHistorical, deferred } from './helpers/danmuku.js' + +const implementation = await danmukuCandidate() +const historical = await danmukuHistorical() +const prefix = 'artplayerPluginDanmuku:' +const xml = ` + "quote" 'apostrophe' <tag> & &lt; +second\nline +third +bottom +top +unknown mode +short attributes +` +const expectedRows = [ + { text: '"quote" \'apostrophe\' & <', time: 1.25, mode: 0, fontSize: 25, color: '#ffffff', timestamp: 123, pool: 0, userID: 'user-a', rowID: 42 }, + { text: 'second\nline', time: 2, mode: 0, fontSize: 18, color: '#f', timestamp: 124, pool: 1, userID: 'user-b', rowID: 43 }, + { text: 'third', time: 3, mode: 0, fontSize: 19, color: '#0', timestamp: 125, pool: 2, userID: 'user-c', rowID: 44 }, + { text: 'bottom', time: 4, mode: 2, fontSize: 20, color: '#ff', timestamp: 126, pool: 3, userID: 'user-d', rowID: 45 }, + { text: 'top', time: 5, mode: 1, fontSize: 21, color: '#ffff', timestamp: 127, pool: 4, userID: 'user-e', rowID: 46 }, + { text: 'unknown mode', time: 6, mode: 0, fontSize: 22, color: '#ff', timestamp: 128, pool: 5, userID: 'user-f', rowID: 47 }, +] + +function rows(owner) { + return Array.from(owner.queue, item => Object.fromEntries(Object.keys(expectedRows[0]).map(key => [key, item[key]]))) +} + +function events(env, name) { + return env.events.filter(event => event.name === `${prefix}${name}`) +} + +async function setup(t, settings = {}, configure = () => {}) { + const env = danmukuCandidateEnvironment(implementation, settings) + const plugin = env.factory({ danmuku: [] })(env.art) + t.after(() => { + if (!env.art.isDestroy) + env.destroy() + }) + await env.flush() + // Faults belong to the parser; the built inline layout Worker reads the same global. + configure(env) + return { env, plugin } +} + +function parserWorker(env) { + const workers = env.workers.filter(worker => env.urls.has(worker.url)) + assert.equal(workers.length, 1, 'Exactly one actual parser Blob Worker is expected') + return workers[0] +} + +function released(env, worker) { + assert.equal(worker.terminated, true) + assert.deepEqual(env.revoked, [worker.url]) + for (const name of ['message', 'error', 'messageerror']) { + assert.equal(worker[`on${name}`] == null, true, `${name} property detached`) + assert.equal((worker.listeners.get(name) || []).length, 0, `${name} listener detached`) + } +} + +function dispatch(worker, name, event) { + worker[`on${name}`]?.(event) + for (const listener of [...worker.listeners.get(name) || []]) + listener(event) +} + +test('danmuku parser: real candidate and frozen release Blob scripts preserve XML fields, entities and mode mapping', { timeout: 10000 }, async (t) => { + for (const candidate of [...historical, implementation]) { + const env = candidate === implementation + ? danmukuCandidateEnvironment(candidate, { fetch: async () => ({ text: async () => xml }) }) + : danmukuEnvironment(candidate, { fetch: async () => ({ text: async () => xml }) }) + t.after(() => env.destroy()) + const plugin = env.factory({ danmuku: [] })(env.art) + await env.flush() + const pending = plugin.load('/fields.xml') + await env.flush() + const worker = parserWorker(env) + assert.equal(worker.messages.length, 1) + assert.equal(worker.messages[0].message.xml, xml) + await worker.execute(worker.messages[0].message) + const owner = await pending + assert.deepEqual(rows(owner), expectedRows, candidate.name) + if (candidate === implementation) + released(env, worker) + } +}) + +for (const input of ['', '', 'invalid fields']) { + test(`danmuku parser: empty result settles and releases resources for ${JSON.stringify(input)}`, { timeout: 3000 }, async (t) => { + const { env, plugin } = await setup(t, { fetch: async () => ({ text: async () => input }) }) + const loaded = events(env, 'loaded').length + const pending = plugin.load('/empty.xml') + await env.flush() + const worker = parserWorker(env) + await worker.execute(worker.messages[0].message) + const owner = await pending + assert.equal(owner.queue.length, 0) + assert.equal(events(env, 'loaded').length, loaded + 1) + assert.equal(events(env, 'error').length, 0) + released(env, worker) + }) +} + +for (const stage of ['fetch', 'text']) { + test(`danmuku parser: ${stage} rejection preserves public error identity and emits once`, { timeout: 3000 }, async (t) => { + const failure = new Error(`${stage}-original`) + const { env, plugin } = await setup(t, { fetch: async () => { + if (stage === 'fetch') + throw failure + return { text: async () => { + throw failure + } } + } }) + const loaded = events(env, 'loaded').length + await assert.rejects(plugin.load('/failed.xml'), error => error === failure) + assert.deepEqual(events(env, 'error').map(event => event.args), [[failure]]) + assert.equal(events(env, 'loaded').length, loaded) + assert.equal(env.urls.size, 0) + assert.equal(env.workers.length, 1, 'Network failure must not allocate the parser worker') + assert.deepEqual(env.revoked, []) + }) +} + +for (const stage of ['url', 'constructor', 'postMessage', 'error', 'messageerror']) { + test(`danmuku parser: ${stage} failure falls back to the same XML parser and releases acquired resources`, { timeout: 3000 }, async (t) => { + const failure = new Error(`${stage}-original`) + const { env, plugin } = await setup(t, { fetch: async () => ({ text: async () => xml }) }, (env) => { + const NativeWorker = env.context.Worker + if (stage === 'url') { + env.context.URL.createObjectURL = () => { + throw failure + } + } + else if (stage === 'constructor') { + env.context.Worker = class { + constructor() { throw failure } + } + } + else if (stage === 'postMessage') { + env.context.Worker = class extends NativeWorker { + postMessage() { throw failure } + } + } + }) + const pending = plugin.load('/fallback.xml') + await env.flush() + if (stage === 'error' || stage === 'messageerror') { + let prevented = 0 + dispatch(parserWorker(env), stage, { error: failure, message: failure.message, preventDefault() { + prevented++ + } }) + if (stage === 'error') + assert.equal(prevented, 1, 'Only the recovered parser Worker error is prevented locally') + } + const owner = await pending + assert.deepEqual(rows(owner), expectedRows) + assert.equal(events(env, 'error').length, 0, 'Successful fallback is not a public load error') + assert.equal(env.consoleErrors.length, 1) + assert.equal(env.consoleErrors[0][0], 'Error parsing Bilibili Danmu:') + assert.equal(env.consoleErrors[0][1], failure) + if (stage === 'url') { + assert.equal(env.urls.size, 0) + assert.deepEqual(env.revoked, []) + } + else if (stage === 'constructor') { + assert.equal(env.urls.size, 1) + assert.deepEqual(env.revoked, [...env.urls.keys()]) + } + else { + released(env, parserWorker(env)) + } + }) +} + +test('danmuku parser: ignores unrelated response IDs and disposes only after its matching result', { timeout: 3000 }, async (t) => { + const { env, plugin } = await setup(t) + let settled = false + const pending = plugin.load('/correlated.xml') + pending.then(() => settled = true, () => settled = true) + await env.flush() + const worker = parserWorker(env) + const request = worker.messages[0].message + worker.deliver({ id: `${request.id}-unrelated`, danmus: [] }) + await env.flush() + assert.equal(settled, false) + assert.equal(worker.terminated, false) + assert.deepEqual(env.revoked, []) + await worker.execute(request) + const owner = await pending + assert.equal(owner.queue[0].text, 'xml') + released(env, worker) +}) + +test('danmuku parser: a matching malformed response rejects once and cannot later publish data', { timeout: 3000 }, async (t) => { + const { env, plugin } = await setup(t) + const pending = plugin.load('/malformed.xml') + let rejectedError + const rejected = assert.rejects(pending, (error) => { + rejectedError = error + return error.name === 'Error' && error.message === 'Invalid Bilibili Danmu worker response' + }) + await env.flush() + const worker = parserWorker(env) + const { id } = worker.messages[0].message + worker.deliver({ id, danmus: { text: 'not an array' } }) + await rejected + assert.deepEqual(events(env, 'error').map(event => event.args), [[rejectedError]]) + released(env, worker) + const count = env.events.length + worker.deliver({ id, danmus: [{ text: 'late' }] }) + await env.flush() + assert.equal(env.events.length, count) +}) + +for (const outcome of ['success', 'protocol-error', 'destroy']) { + test(`danmuku parser: ${outcome} attempts every resource cleanup when termination and URL revocation throw`, { timeout: 3000 }, async (t) => { + const terminateError = new Error('terminate-original') + const revokeError = new Error('revoke-second') + let terminateCalls = 0 + const { env, plugin } = await setup(t, {}, (env) => { + const NativeWorker = env.context.Worker + const revoke = env.context.URL.revokeObjectURL + env.context.Worker = class extends NativeWorker { + terminate() { + terminateCalls++ + super.terminate() + throw terminateError + } + } + env.context.URL.revokeObjectURL = (url) => { + revoke(url) + throw revokeError + } + }) + const owner = plugin.show() + const pending = plugin.load('/cleanup.xml') + let rejectedError + const observed = outcome === 'destroy' + ? pending + : assert.rejects(pending, (error) => { + rejectedError = error + return outcome === 'success' + ? error === terminateError + : error.message === 'Invalid Bilibili Danmu worker response' + }) + await env.flush() + const worker = parserWorker(env) + if (outcome === 'destroy') + env.destroy() + else worker.deliver({ id: worker.messages[0].message.id, danmus: outcome === 'success' ? [] : null }) + const result = await observed + assert.equal(terminateCalls, 1) + released(env, worker) + if (outcome === 'destroy') { + assert.equal(result, owner) + assert.equal(events(env, 'error').length, 0) + } + else { + assert.deepEqual(events(env, 'error').map(event => event.args), [[rejectedError]]) + } + }) +} + +test('danmuku parser: fallback parser failure rejects its original error once and releases the allocated URL', { timeout: 3000 }, async (t) => { + const workerError = new Error('worker unavailable') + const parserError = new Error('fallback parser original') + const { env, plugin } = await setup(t, { fetch: async () => ({ text: async () => xml }) }, (env) => { + env.context.Worker = class { + constructor() { throw workerError } + } + env.context.parserError = parserError + vm.runInContext('String.prototype.matchAll = function () { throw parserError }', env.context) + }) + await assert.rejects(plugin.load('/fallback-failed.xml'), error => error === parserError) + assert.deepEqual(events(env, 'error').map(event => event.args), [[parserError]]) + assert.equal(env.consoleErrors[0][1], workerError) + assert.equal(env.urls.size, 1) + assert.deepEqual(env.revoked, [...env.urls.keys()]) +}) + +test('danmuku parser: successful parsing removes its abort listener before subsequent cancellation', { timeout: 3000 }, async (t) => { + const subscriptions = [] + const { env, plugin } = await setup(t, {}, (env) => { + env.context.AbortController = class extends AbortController { + constructor() { + super() + const { signal } = this + const add = signal.addEventListener.bind(signal) + const remove = signal.removeEventListener.bind(signal) + signal.addEventListener = (name, callback, ...rest) => { + subscriptions.push({ name, callback, signal, removed: 0 }) + add(name, callback, ...rest) + } + signal.removeEventListener = (name, callback, ...rest) => { + const entry = subscriptions.find(item => item.name === name && item.callback === callback && item.signal === signal) + assert(entry, 'Only an owned abort listener can be removed') + entry.removed++ + remove(name, callback, ...rest) + } + } + } + }) + const pending = plugin.load('/signal.xml') + await env.flush() + const worker = parserWorker(env) + assert.equal(subscriptions.length, 1) + assert.equal(subscriptions[0].name, 'abort') + await worker.execute(worker.messages[0].message) + await pending + assert.equal(subscriptions[0].removed, 1) + released(env, worker) + env.destroy() + assert.equal(subscriptions[0].removed, 1) + assert.deepEqual(env.revoked, [worker.url]) +}) + +for (const stage of ['fetch', 'text', 'worker']) { + for (const abortController of [true, false]) { + test(`danmuku parser: destroy during ${stage} settles silently with AbortController ${abortController}`, { timeout: 3000 }, async (t) => { + const gate = deferred() + const { env, plugin } = await setup(t, { fetch: async () => { + if (stage === 'fetch') + return gate.promise + return { text: () => stage === 'text' ? gate.promise : Promise.resolve(xml) } + } }, (env) => { + if (!abortController) { + env.context.AbortController = undefined + env.context.window.AbortController = undefined + } + }) + const owner = plugin.show() + const pending = plugin.load('/cancel.xml') + await env.flush() + const worker = stage === 'worker' ? parserWorker(env) : undefined + env.destroy() + const count = env.events.length + assert.equal(await pending, owner) + assert.equal(events(env, 'error').length, 0) + if (abortController) + assert.equal(env.fetchCalls[0].init.signal.aborted, true) + if (worker) { + released(env, worker) + worker.deliver({ id: worker.messages[0].message.id, danmus: [{ text: 'late' }] }) + } + else { + assert.equal(env.urls.size, 0) + gate.resolve(stage === 'fetch' ? { text: async () => xml } : xml) + } + await env.flush() + assert.equal(env.events.length, count, 'Cancelled parser cannot emit a late loaded/error event') + assert.equal(owner.queue.length, 0) + assert.equal(env.workers.length, stage === 'worker' ? 2 : 1) + }) + } +} + +for (const stage of ['fetch', 'text']) { + test(`danmuku parser: strict subprocess observes automatic ${stage} failure while public load still rejects`, () => { + const program = ` + import assert from 'node:assert/strict'; + import fs from 'node:fs'; + import { danmukuCandidateEnvironment } from './test/helpers/danmuku-candidate.js'; + const implementation = JSON.parse(fs.readFileSync(0, 'utf8')); + const failure = new Error(${JSON.stringify(`${stage}-original`)}); + const env = danmukuCandidateEnvironment(implementation, { fetch: async () => { + if (${JSON.stringify(stage)} === 'fetch') throw failure; + return { text: async () => { throw failure; } }; + } }); + const plugin = env.factory({ danmuku: '/automatic.xml' })(env.art); + await env.flush(); + await new Promise(resolve => setImmediate(resolve)); + assert.deepEqual(env.events.filter(event => event.name === '${prefix}error').map(event => event.args), [[failure]]); + assert.deepEqual(env.consoleWarnings, [['Failed to load initial danmuku:', failure]]); + await assert.rejects(plugin.load('/public.xml'), error => error === failure); + await new Promise(resolve => setImmediate(resolve)); + assert.deepEqual(env.events.filter(event => event.name === '${prefix}error').map(event => event.args), [[failure], [failure]]); + assert.equal(env.consoleWarnings.length, 1); + assert.equal(env.workers.length, 1); + assert.equal(env.urls.size, 0); + env.destroy(); + process.stdout.write('automatic-observed-public-rejected-once\\n'); + ` + const child = spawnSync(process.execPath, ['--unhandled-rejections=strict', '--input-type=module', '-e', program], { + cwd: fileURLToPath(new URL('../', import.meta.url)), + input: JSON.stringify(implementation), + encoding: 'utf8', + timeout: 5000, + maxBuffer: 1024 * 1024, + }) + assert.equal(child.error, undefined, child.error?.message) + assert.equal(child.signal, null) + assert.equal(child.status, 0, child.stderr) + assert.equal(child.stdout, 'automatic-observed-public-rejected-once\n') + assert.equal(child.stderr, '') + }) +} diff --git a/test/helpers/danmuku-candidate.js b/test/helpers/danmuku-candidate.js new file mode 100644 index 000000000..641384861 --- /dev/null +++ b/test/helpers/danmuku-candidate.js @@ -0,0 +1,56 @@ +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' +import { build } from 'esbuild' +import { ensureArchive, readMember, refactorDir } from '../../refactor/scripts/releases.mjs' +import { getEntryFile } from '../../scripts/projects.js' +import { danmukuEnvironment } from './danmuku.js' + +const workspace = fileURLToPath(new URL('../../', import.meta.url)) + +export async function danmukuCandidate() { + const coreRelease = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/releases.json'), 'utf8')).releases.find(item => item.name === 'artplayer') + const coreCode = readMember(await ensureArchive(coreRelease), 'package/dist/artplayer.js').toString() + const artifact = process.env.ARTPLAYER_DANMUKU_ARTIFACT + if (artifact) + return { name: path.basename(artifact), code: fs.readFileSync(path.resolve(artifact), 'utf8'), coreCode } + const project = path.join(workspace, 'packages/artplayer-plugin-danmuku') + const compiled = await build({ entryPoints: [getEntryFile(project)], bundle: true, write: false, format: 'cjs', platform: 'browser', target: 'es2020', plugins: [{ name: 'danmuku-controlled-assets', setup(build) { + build.onResolve({ filter: /\?(?:worker|raw|inline)/ }, args => ({ path: path.resolve(args.resolveDir, args.path), namespace: 'controlled-asset' })) + build.onLoad({ filter: /.*/, namespace: 'controlled-asset' }, (args) => { + if (args.path.includes('?worker')) + return { contents: 'export default Worker', loader: 'js' } + return { contents: `export default ${JSON.stringify(fs.readFileSync(args.path.split('?')[0], 'utf8'))}`, loader: 'js' } + }) + } }] }) + return { name: 'candidate-source', code: compiled.outputFiles[0].text, coreCode } +} + +export function danmukuCandidateEnvironment(implementation, settings = {}) { + // Inject before module evaluation: source worker imports capture the constructor. + const initialWorker = settings.initialWorkerError + ? { ...implementation, code: `globalThis.Worker = class { constructor() { throw globalThis.__danmukuCandidateWorkerError } };\n${implementation.code}` } + : implementation + const env = danmukuEnvironment(initialWorker, settings) + if (settings.initialWorkerError) + env.context.__danmukuCandidateWorkerError = settings.initialWorkerError + const consoleWarnings = [] + env.context.console.warn = (...args) => consoleWarnings.push(args) + env.context.AbortController = AbortController + env.context.AbortSignal = AbortSignal + env.context.window.AbortController = AbortController + env.context.window.AbortSignal = AbortSignal + env.context.Worker.prototype.removeEventListener = function (name, callback) { + this.listeners.set(name, (this.listeners.get(name) || []).filter(listener => listener !== callback)) + } + const fetchCalls = [] + env.context.fetch = async (url, init) => { + env.requests.push(url) + fetchCalls.push({ url, init }) + if (settings.fetch) + return settings.fetch(url, init) + return { ok: true, status: 200, text: async () => 'xml' } + } + return { ...env, fetchCalls, consoleWarnings } +}