diff --git a/docs/compiled/artplayer-plugin-danmuku.js b/docs/compiled/artplayer-plugin-danmuku.js index dc9308ff6..eb2bc2d08 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){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()}function h(t,e,i){const a=t.states[e]||[];for(let n=0;n{if(this.frame=null,t.timer=null,this.closed||this.fault||t.isStop||t.art.isDestroy)return;let e;const i=new Promise(t=>e=()=>t(u)),a={generation:this.generation,cancel:e,wait:t=>Promise.race([t,i]),ref:null};return this.operation=a,this.run(a).catch(t=>{this.active(a)&&this.fail(t)}).finally(()=>{this.release(a),this.operation===a&&(this.operation=null,this.schedule())})}),t.timer=this.frame)}async run(t){const e=this.owner,{setStyles:i}=e.utils;if(!e.art.playing||!this.active(t))return;e.filter("emit",t=>{const i=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=i,t.$lastStartTime=Date.now(),t.$restTime<=0&&e.makeWait(t)});const a=e.readys;for(const s of a){if(!this.active(t))return;if(this.failedItems.has(s))continue;let a;try{a=await t.wait(e.option.beforeVisible(s))}catch(n){if(!this.active(t))return;this.failedItems.add(s),this.report(n);continue}if(!this.active(t))return;if(!a)continue;const{clientWidth:o,clientHeight:r}=e.$player,l=e.$ref;if(t.danmu=s,t.ref=l,s.$ref=l,l.textContent=s.text,e.$danmuku.appendChild(l),l.style.opacity=e.option.opacity,l.style.fontSize=`${e.fontSize}px`,l.style.color=s.color,l.style.border=s.border?`1px solid ${s.color}`:null,l.style.backgroundColor=s.border?"rgb(0 0 0 / 50%)":null,i(l,s.style),!this.active(t))return;s.$lastStartTime=Date.now(),s.$restTime=e.speed;const d=o+l.clientWidth,{result:p}=await t.wait(e.postMessage({type:"getDanmuTop",target:{mode:s.mode,height:l.clientHeight,speed:d/s.$restTime},visibles:e.visibles,antiOverlap:e.option.antiOverlap,clientWidth:o,clientHeight:r,marginBottom:e.marginBottom,marginTop:e.marginTop}));if(!this.active(t)||s.$ref!==l)return;if(void 0!==p){switch(e.setState(s,"emit"),l.style.top=`${p}px`,l.style.visibility="visible",l.dataset.mode=s.mode,l.dataset.id=s.id||"",s.mode){case 0:l.style.left=`${o}px`,l.style.marginLeft="0px",l.style.transform=`translateX(${-d}px)`,l.style.transition=`transform ${s.$restTime}s linear 0s`;break;case 1:case 2:l.style.left="50%",l.style.marginLeft=`-${l.clientWidth/2}px`}t.ref=null,e.art.emit("artplayerPluginDanmuku:visible",s)}else e.setState(s,"ready"),this.release(t)}}destroy(){this.closed=!0,this.invalidate()}}let m=0;class f{constructor(t,e){this.pending=new Map,this.closed=!1,this.failed=!1,this.onFailure=e,this.worker=t();try{this.worker.onmessage=t=>{const e=this.pending.get(t.data?.id);e&&(this.pending.delete(t.data.id),e.resolve(t.data))},this.worker.onerror=t=>{t.preventDefault?.(),this.fail(t.error||t)},this.worker.onmessageerror=t=>this.fail(t.error||t)}catch(i){try{this.dispose()}catch{}throw i}}request(t){t.id=++m;const{id:e}=t;return this.closed||this.failed?Promise.resolve({id:e,result:void 0}):new Promise((i,a)=>{this.pending.set(e,{resolve:i,reject:a});try{this.worker.postMessage(t)}catch(n){this.fail(n)}})}fail(t){if(!this.closed&&!this.failed){this.failed=!0;for(const e of this.pending.values())e.reject(t);this.pending.clear();try{this.dispose()}catch{}this.onFailure(t)}}cancel(){for(const[t,e]of this.pending)e.resolve({id:t,result:void 0});this.pending.clear()}dispose(){if(this.closed)return;this.closed=!0,this.cancel();let t,e=!1;const i=i=>{try{i()}catch(a){e||(t=a),e=!0}};for(const a of["onmessage","onerror","onmessageerror"])i(()=>this.worker[a]=null);if(i(()=>this.worker.terminate()),e)throw t}}const g='/*!\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',y="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",g],{type:"text/javascript;charset=utf-8"});function v(t){let e;try{if(e=y&&(self.URL||self.webkitURL).createObjectURL(y),!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(g),{type:"module",name:t?.name})}}class k{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.workerClient=null,this.scheduler=new c(this),this.option=k.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),this.seek=this.seek.bind(this),d(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!d(this))return;this.createWorker()}catch(n){throw t.off("destroy",this.destroy),p(this),this.scheduler.destroy(),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),t.on("video:seeking",this.seek),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 k.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 k.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 k.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=k.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){return function(t){const{currentTime:e}=t.art,i=[];return h(t,"ready",t=>i.push(t)),h(t,"wait",t=>{e+.1>=t.time&&t.time>=e-.1&&i.push(t)}),i}(this)}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:k.option,validate:t=>this.validator(t,k.scheme),clamp:a,mount:n});if(!d(this))return this;const l=this.option.beforeVisible!==r.beforeVisible;return this.option=r,l&&(this.scheduler.invalidate(),this.scheduler.schedule()),e.fontSize&&(this.reset(),!d(this))||(this.option.visible?this.show():this.hide(),d(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return this.workerClient.request(t)}createWorker(){this.workerClient=new f(()=>new v,t=>this.scheduler.fail(t)),this.worker=this.workerClient.worker}filter(t,e){return h(this,t,e)}setState(t,e){!function(t,e,i){t.states[e.$state]=t.states[e.$state].filter(t=>t!==e),e.$state=i,e.$ref&&(e.$ref.dataset.state=i),t.states[i].push(e)}(this,t,e)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=k.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(){return this.scheduler.running=!0,this.scheduler.schedule(),this}resize(){const{clientWidth:t}=this.$player;this.filter("stop",e=>{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.scheduler.running=!1,this.scheduler.invalidate(),this.suspend(),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){if(this.scheduler.closed||this.art.isDestroy)return this;const t=++this.scheduler.starts,e=this.scheduler.generation;this.isStop=!1,this.scheduler.recover();return this.scheduler.closed||this.art.isDestroy||this.isStop||this.scheduler.fault||e!==this.scheduler.generation||t!==this.scheduler.starts||(this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start")),this}reset(){return this.scheduler.invalidate(),this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this.scheduler.recover(),this.scheduler.schedule(),this}seek(){this.scheduler.invalidate(),this.scheduler.schedule()}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.scheduler.invalidate(),this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this.scheduler.schedule(),this}destroy(){p(this),this.scheduler.destroy();try{try{this.stop()}finally{this.workerClient?.dispose()}}finally{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("video:seeking",this.seek),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}}const w={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,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]),f=(Math.min(...m)+Math.max(...m))/2;for(let t=0;tf?1+d.scale:1-d.scale)+d.minHeight}const g=(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=w.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]},y=p.map(t=>[w.map(t[0],d.xMin,d.xMax,0,l.w),w.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=g(i[e-1],i[e-2],t),n=g(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 $='',b='',S='',M='',z='',C='',E='',D='',F='',T='',A='',L='';class P{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:T,$config:S,$style:L,$mode_0_off:M,$mode_0_on:z,$mode_1_off:C,$mode_1_on:E,$mode_2_off:D,$mode_2_on:F,$check_on:b,$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}${T}\n
\n
\n ${S}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${M}${z}\n
\u6eda\u52a8
\n
\n
\n ${C}${E}\n
\u9876\u90e8
\n
\n
\n ${D}${F}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${b}${$}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${b}${$}\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 ${L}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${z}\n
\u6eda\u52a8
\n
\n
\n ${E}\n
\u9876\u90e8
\n
\n
\n ${F}\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 H(t){return e=>{const i=new k(e,t),a=e.isDestroy?void 0:new P(e,i);return!e.isDestroy&&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?a.mount.bind(a):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return H.icons=P.icons,H}); +!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 n({data:t}){const{xml:e,id:n}=t,a=i(e);globalThis.postMessage({danmus:a,id:n})}let a=0;function s(t,{signal:s,onCancel:r}={}){return new Promise((o,l)=>{let d,c,p,h=!1;const u=++a,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!==c){const n=c;c=void 0;try{URL.revokeObjectURL(n)}catch(i){e||(t=i),e=!0}}if(e)throw t}function g(t,e){if(h)return;h=!0;let i=arguments.length>1;s?.removeEventListener("abort",m),p?.();try{f()}catch(n){i||(e=n),i=!0}i?l(e):o(t)}function y(t,e){if(!h){console.error("Error parsing Bilibili Danmu:",e);try{g(i(t))}catch(n){g(void 0,n)}}}s?.aborted?m():(s?.addEventListener("abort",m),p=r?.(m),h?p?.():async function(){const a=s?await fetch(t,{signal:s}):await fetch(t);if(h)return;const r=await a.text();if(!h)try{const t=`\n ${e.toString()}\n ${i.toString()}\n onmessage = ${n.toString()}\n `;if(c=URL.createObjectURL(new Blob([t],{type:"application/javascript"})),h)return void f();if(d=new Worker(c),h)return void f();d.onmessage=t=>{if(h||t.data?.id!==u)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(r,t.error||t)},d.onmessageerror=t=>y(r,t.error||t),d.postMessage({xml:r,id:u})}catch(o){y(r,o)}}().catch(t=>g(void 0,t)))})}const r=new WeakMap,o=Symbol("cancelled danmuku input");function l(t){return r.has(t)||r.set(t,{closed:!1,tasks:new Set,emitting:[]}),r.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 c(t){const e=l(t);e.closed=!0;for(const i of[...e.tasks])i.cancel()}function p(t,e,i){const n=t.states[e]||[];for(let a=0;a{const a=n.$ref.offsetTop,s=this.owner.getLeft(n.$ref)-i,r=n.$ref.clientHeight,o=n.$ref.clientWidth,l=s+o,d=e-l,c=l/n.$restTime,p={};p.top=a,p.left=s,p.height=r,p.width=o,p.right=d,p.speed=c,p.distance=l,p.time=n.$restTime,p.mode=n.mode,t.push(p)}),t}left(t){const e=t.getBoundingClientRect();return this.owner.isRotate?e.top:e.left}makeWait(t){this.owner.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=this.owner.constructor.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.owner.$refs.push(t.$ref),t.$ref=null)}resize(){const{clientWidth:t}=this.owner.$player;this.owner.filter("stop",e=>{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.owner.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.owner.$player;return this.owner.filter("stop",e=>{switch(this.owner.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.owner.$player;return this.owner.filter("emit",e=>{switch(this.owner.setState(e,"stop"),e.mode){case 0:{const i=t-(this.owner.getLeft(e.$ref)-this.owner.getLeft(this.owner.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}prepare(t,e){const i=this.owner,n=i.$ref;return e.danmu=t,e.ref=n,t.$ref=n,n.textContent=t.text,i.$danmuku.appendChild(n),n.style.opacity=i.option.opacity,n.style.fontSize=`${i.fontSize}px`,n.style.color=t.color,n.style.border=t.border?`1px solid ${t.color}`:null,n.style.backgroundColor=t.border?"rgb(0 0 0 / 50%)":null,i.utils.setStyles(n,t.style),n}place(t,e,i,n,a){switch(e.style.top=`${i}px`,e.style.visibility="visible",e.dataset.mode=t.mode,e.dataset.id=t.id||"",t.mode){case 0:e.style.left=`${a}px`,e.style.marginLeft="0px",e.style.transform=`translateX(${-n}px)`,e.style.transition=`transform ${t.$restTime}s linear 0s`;break;case 1:case 2:e.style.left="50%",e.style.marginLeft=`-${e.clientWidth/2}px`}}release(t){const{danmu:e,ref:i}=t;i&&e.$ref===i&&(i.style.cssText=this.owner.constructor.cssText,i.style.visibility="hidden",this.owner.$refs.push(i),e.$ref=null),t.ref=null}clear(){this.destroy(),this.owner.$refs=[],this.owner.$danmuku.textContent=""}destroy(){for(const t of this.nodes)t.parentElement&&t.parentElement.removeChild(t);for(const t of this.owner.queue)t.$ref=null;this.owner.$refs.length=0,this.nodes.clear()}}const u=Symbol("cancelled danmuku frame");class m{constructor(t){this.owner=t,this.generation=0,this.starts=0,this.frame=null,this.operation=null,this.closed=!1,this.running=!1,this.fault=!1,this.failedItems=new Set}active(t){const e=this.owner;return!this.closed&&!this.fault&&!e.art.isDestroy&&!e.isStop&&!e.isHide&&t.generation===this.generation}release(t){this.owner.renderer.release(t)}invalidate(){if(this.generation++,null!==this.frame&&window.cancelAnimationFrame(this.frame),this.frame=null,this.owner.timer=null,this.operation){const t=this.operation;this.operation=null,t.cancel(),this.release(t)}this.owner.workerClient?.cancel(),this.failedItems.clear()}report(t){try{this.owner.art.emit("artplayerPluginDanmuku:error",t)}catch(e){console.warn("Failed to report danmuku scheduling error:",e)}}fail(t){this.closed||this.fault||(this.fault=!0,this.invalidate(),this.report(t))}recover(){if(!this.closed&&!this.owner.art.isDestroy&&(this.failedItems.clear(),this.fault))try{this.owner.workerClient?.dispose(),this.owner.createWorker(),this.fault=!1}catch(t){this.report(t)}}schedule(){const t=this.owner;!this.running||this.closed||this.fault||t.art.isDestroy||t.isStop||null!==this.frame||this.operation||(this.frame=window.requestAnimationFrame(()=>{if(this.frame=null,t.timer=null,this.closed||this.fault||t.isStop||t.art.isDestroy)return;let e;const i=new Promise(t=>e=()=>t(u)),n={generation:this.generation,cancel:e,wait:t=>Promise.race([t,i]),ref:null};return this.operation=n,this.run(n).catch(t=>{this.active(n)&&this.fail(t)}).finally(()=>{this.release(n),this.operation===n&&(this.operation=null,this.schedule())})}),t.timer=this.frame)}async run(t){const e=this.owner;if(!e.art.playing||!this.active(t))return;e.filter("emit",t=>{const i=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=i,t.$lastStartTime=Date.now(),t.$restTime<=0&&e.makeWait(t)});const i=e.readys;for(const a of i){if(!this.active(t))return;if(this.failedItems.has(a))continue;let i;try{i=await t.wait(e.option.beforeVisible(a))}catch(n){if(!this.active(t))return;this.failedItems.add(a),this.report(n);continue}if(!this.active(t))return;if(!i)continue;const{clientWidth:s,clientHeight:r}=e.$player,o=e.renderer.prepare(a,t);if(!this.active(t))return;a.$lastStartTime=Date.now(),a.$restTime=e.speed;const l=s+o.clientWidth,{result:d}=await t.wait(e.postMessage({type:"getDanmuTop",target:{mode:a.mode,height:o.clientHeight,speed:l/a.$restTime},visibles:e.visibles,antiOverlap:e.option.antiOverlap,clientWidth:s,clientHeight:r,marginBottom:e.marginBottom,marginTop:e.marginTop}));if(!this.active(t)||a.$ref!==o)return;void 0!==d?(e.setState(a,"emit"),e.renderer.place(a,o,d,l,s),t.ref=null,e.art.emit("artplayerPluginDanmuku:visible",a)):(e.setState(a,"ready"),this.release(t))}}destroy(){this.closed=!0,this.invalidate()}}let f=0;class g{constructor(t,e){this.pending=new Map,this.closed=!1,this.failed=!1,this.onFailure=e,this.worker=t();try{this.worker.onmessage=t=>{const e=this.pending.get(t.data?.id);e&&(this.pending.delete(t.data.id),e.resolve(t.data))},this.worker.onerror=t=>{t.preventDefault?.(),this.fail(t.error||t)},this.worker.onmessageerror=t=>this.fail(t.error||t)}catch(i){try{this.dispose()}catch{}throw i}}request(t){t.id=++f;const{id:e}=t;return this.closed||this.failed?Promise.resolve({id:e,result:void 0}):new Promise((i,n)=>{this.pending.set(e,{resolve:i,reject:n});try{this.worker.postMessage(t)}catch(a){this.fail(a)}})}fail(t){if(!this.closed&&!this.failed){this.failed=!0;for(const e of this.pending.values())e.reject(t);this.pending.clear();try{this.dispose()}catch{}this.onFailure(t)}}cancel(){for(const[t,e]of this.pending)e.resolve({id:t,result:void 0});this.pending.clear()}dispose(){if(this.closed)return;this.closed=!0,this.cancel();let t,e=!1;const i=i=>{try{i()}catch(n){e||(t=n),e=!0}};for(const n of["onmessage","onerror","onmessageerror"])i(()=>this.worker[n]=null);if(i(()=>this.worker.terminate()),e)throw t}}const y='/*!\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',v="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",y],{type:"text/javascript;charset=utf-8"});function w(t){let e;try{if(e=v&&(self.URL||self.webkitURL).createObjectURL(v),!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(y),{type:"module",name:t?.name})}}class k{constructor(t,e){const{constructor:i,template:n}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=n.$danmuku,this.$player=n.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.worker=null,this.workerClient=null,this.renderer=new h(this),this.scheduler=new m(this),this.option=k.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),this.seek=this.seek.bind(this),d(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!d(this))return;this.createWorker()}catch(a){throw t.off("destroy",this.destroy),c(this),this.scheduler.destroy(),a}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),t.on("video:seeking",this.seek),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 k.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 k.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 n=Number.parseFloat(i)/100;return Math.round(t(e*n,12,e))}return k.option.fontSize}get $ref(){return this.renderer.acquire()}get readys(){return function(t){const{currentTime:e}=t.art,i=[];return p(t,"ready",t=>i.push(t)),p(t,"wait",t=>{e+.1>=t.time&&t.time>=e-.1&&i.push(t)}),i}(this)}get visibles(){return this.renderer.visibles}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,n=function(t,e){const i=l(t);if(e)for(const o of[...i.tasks])o.replace&&o.cancel();let n,a=i.closed||t.art.isDestroy;const s=new Promise(t=>n=t),r=new Set,d="function"===typeof AbortController?new AbortController:void 0,c={replace:e,signal:d?.signal,active:()=>!a&&!i.closed&&!t.art.isDestroy,onCancel:t=>c.active()?(r.add(t),()=>r.delete(t)):(t(),()=>{}),cancel(){if(!a){a=!0,n(o),d?.abort();for(const t of[...r])t();r.clear()}},wait:t=>Promise.race([t,s]),emit(e){i.emitting.push(c);try{return t.emit(e)}finally{i.emitting.pop()}},finish(){i.tasks.delete(c),r.clear()}};return a?n(o):i.tasks.add(c),c}(this,void 0===e);try{if(!n.active())return this;const a=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,n),r=a.asynchronous?await n.wait(a.value):a.value;if(!n.active())return this;if(i(Array.isArray(r),"Danmuku need return an array as result"),void 0===e){if(this.reset(),!n.active())return this;this.queue=[],this.states={wait:[],ready:[],emit:[],stop:[]},this.renderer.clear()}for(let t=0;t{const i=s[e],n=r[e];return"function"===typeof i||"function"===typeof n||t(i)||t(n)?i!==n:JSON.stringify(i)!==JSON.stringify(n)}))&&!i)return this;const o=function(t,e,{defaults:i,validate:n,clamp:a,mount:s}){const r=Object.assign({},i,t,e);return n(r),r.mode=a(r.mode,0,2),r.speed=a(r.speed,1,10),r.opacity=a(r.opacity,0,1),r.lockTime=a(r.lockTime,1,60),r.maxLength=a(r.maxLength,1,1e3),r.mount=r.mount||s,r}(this.option,e,{defaults:k.option,validate:t=>this.validator(t,k.scheme),clamp:n,mount:a});if(!d(this))return this;const l=this.option.beforeVisible!==o.beforeVisible;return this.option=o,l&&(this.scheduler.invalidate(),this.scheduler.schedule()),e.fontSize&&(this.reset(),!d(this))||(this.option.visible?this.show():this.hide(),d(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this}getLeft(t){return this.renderer.left(t)}postMessage(t={}){return this.workerClient.request(t)}createWorker(){this.workerClient=new g(()=>new w,t=>this.scheduler.fail(t)),this.worker=this.workerClient.worker}filter(t,e){return p(this,t,e)}setState(t,e){!function(t,e,i){t.states[e.$state]=t.states[e.$state].filter(t=>t!==e),e.$state=i,e.$ref&&(e.$ref.dataset.state=i),t.states[i].push(e)}(this,t,e)}makeWait(t){this.renderer.makeWait(t)}update(){return this.scheduler.running=!0,this.scheduler.schedule(),this}resize(){this.renderer.resize()}continue(){return this.renderer.continue(),this}suspend(){return this.renderer.suspend(),this}stop(){return this.isStop=!0,this.scheduler.running=!1,this.scheduler.invalidate(),this.suspend(),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){if(this.scheduler.closed||this.art.isDestroy)return this;const t=++this.scheduler.starts,e=this.scheduler.generation;this.isStop=!1,this.scheduler.recover();return this.scheduler.closed||this.art.isDestroy||this.isStop||this.scheduler.fault||e!==this.scheduler.generation||t!==this.scheduler.starts||(this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start")),this}reset(){return this.scheduler.invalidate(),this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this.scheduler.recover(),this.scheduler.schedule(),this}seek(){this.scheduler.invalidate(),this.scheduler.schedule()}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.scheduler.invalidate(),this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this.scheduler.schedule(),this}destroy(){c(this),this.scheduler.destroy();try{try{this.stop()}finally{this.workerClient?.dispose()}}finally{this.renderer.destroy(),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("video:seeking",this.seek),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}}const x=4294967295;function $(t,e){let i=0,n=t.length;for(;iNumber.isFinite(t)&&t>0))return null;const r={xMin:0,xMax:t,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*e),sampling:Math.max(1,Math.floor(t/100)),smoothing:.2,flattening:.2},o=Object.assign({},r,"object"===typeof a?a:{});for(const g of Object.keys(r))Number.isFinite(o[g])||(o[g]=r[g]);if(o.sampling<=0&&(o.sampling=r.sampling),o.xMin===o.xMax||o.yMin===o.yMax)return null;const l=Array.isArray(s)&&s.length?[...s]:function(t,e,i,n){if(!Number.isFinite(e)||e<=0||!Number.isFinite(i)||i<=0||!Number.isFinite(n)||n<=0||Math.floor(e/n)+1>x)return[];const a=t.map(t=>t.time).filter(t=>"number"===typeof t&&!Number.isNaN(t)).sort((t,e)=>t-e),s=[],r=i/e;if(!Number.isFinite(r))return[];for(let o=0;o<=e;o+=n){if(s.length>=x||o+n===o)return[];const t=o*r,e=(o+n)*r;s.push([o,$(a,e)-$(a,t)])}return s}(n,t,i,o.sampling);if(!l.length||!l.every(t=>Array.isArray(t)&&Number.isFinite(t[0])&&Number.isFinite(t[1])))return null;const d=l[l.length-1];if(d[0]!==t){if(l.length===x)return null;l.push([t,d[1]])}let c=1/0,p=-1/0;for(const g of l)c=Math.min(c,g[1]),p=Math.max(p,g[1]);const h=(c+p)/2;for(const g of l){const t=g[1];g[1]=t*(t>h?1+o.scale:1-o.scale)+o.minHeight}const u=(t,e,i,n)=>{const a=function(t,e){const i=e[0]-t[0],n=e[1]-t[1];return{length:Math.sqrt(i**2+n**2),angle:Math.atan2(n,i)}}(e||t,i||t),s=b(Math.cos(a.angle)*o.flattening,0,1,1,0),r=a.angle*s+(n?Math.PI:0),l=a.length*o.smoothing;return[t[0]+Math.cos(r)*l,t[1]+Math.sin(r)*l]},m=l.map(i=>[b(i[0],o.xMin,o.xMax,0,t),b(i[1],o.yMin,o.yMax,e,0)]);if(!m.every(t=>t.every(Number.isFinite)))return null;const f=m.map((t,i,n)=>{if(0===i)return`M ${n[n.length-1][0]},${e} L ${t[0]},${e} L ${t[0]},${t[1]}`;const a=u(n[i-1],n[i-2],t),s=u(t,n[i-1],n[i+1],!0);return`C ${a[0]},${a[1]} ${s[0]},${s[1]} ${t[0]},${t[1]}${i===n.length-1?" z":""}`}).join(" ");return/NaN|Infinity/u.test(f)?null:{path:f,opacity:o.opacity,width:t,height:e}}let S=0;function z(t,e,i){if(t.isDestroy)return;const{query:n}=t.constructor.utils;let a;const s=[];let r,o,l,d=!1;const c=()=>!d&&!t.isDestroy;function p(e,i){s.push([e,i]),t.on(e,i)}function h(e){if(d)return;let i;d=!0,o=null,l=null;let n=!1;const a=t=>{try{t()}catch(e){n||(i=e),n=!0}};for(const[r,o]of s.splice(0))a(()=>t.off(r,o));if(e&&r&&t.controls.heatmap===r&&a(()=>t.controls.remove("heatmap")),r=void 0,n)throw i}function u(t){c()&&o&&l&&(o.setAttribute("offset",100*t+"%"),l.setAttribute("offset",100*t+"%"))}function m(s){try{!function(s=[]){if(!c())return;if(o=null,l=null,r.innerHTML="",t.option.isLive)return;const d=M({width:r.offsetWidth,height:r.offsetHeight,duration:t.duration,queue:e.queue,option:i,points:s});if(!d||!c())return;const p=r.ownerDocument;for(a||(a=p?"heatmap-solids":"heatmap-solids-"+ ++S);p?.getElementById(a);)a="heatmap-solids-"+ ++S;r.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,o=n("#heatmap-start",r),l=n("#heatmap-stop",r),u(t.played)}(s)}catch(d){try{h(!0)}catch{}throw d}}p("destroy",()=>h(!0));try{t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},beforeUnmount(){h(!1)},mounted(e){if(r=e,d)return t.controls.heatmap===e&&t.controls.remove("heatmap"),void(r=void 0);c()?(p("video:timeupdate",()=>u(t.played)),p("setBar",(t,e)=>{"played"===t&&u(e)}),p("ready",()=>m()),p("resize",()=>m()),p("artplayerPluginDanmuku:loaded",()=>m()),p("artplayerPluginDanmuku:points",t=>m(t))):h(!0)}})}catch(f){try{h(!0)}catch{}throw f}return()=>h(!0)}const E=new WeakMap;class C{constructor(t){this.art=t,this.closed=!1,this.disposers=new Set,this.cancelled=new Promise(t=>this.cancel=t)}get active(){return!this.closed&&!this.art.isDestroy}own(t){this.closed?t():this.disposers.add(t)}write(t,e,i,n=i=>t[e]=i){if(!this.active)return;const a=()=>"function"===typeof t.getPropertyPriority?t.getPropertyPriority(e):void 0;let s=E.get(t);s||(s=new Map,E.set(t,s));let r=s.get(e);r||(r={original:t[e],present:e in t||void 0!==t[e],priority:a(),owners:new Map,apply:n},s.set(e,r)),r.owners.has(this)||this.own(()=>{const i=[...r.owners.keys()],n=i[i.length-1]===this;if(r.owners.delete(this),n&&t[e]===r.last&&a()===r.lastPriority){const i=[...r.owners.values()];i.length?(r.apply(i[i.length-1]),r.last=t[e],r.lastPriority=a()):void 0!==r.priority?t.setProperty(e,r.original,r.priority):r.present?r.apply(r.original):delete t[e]}r.owners.size||(s.delete(e),s.size||E.delete(t))}),r.owners.delete(this),r.owners.set(this,i),r.apply(i),r.last=t[e],r.lastPriority=a()}on(t,e){if(!this.active)return;const i=this;function n(...t){if(i.active)return e.apply(this,t)}const a=()=>this.art.off(t,n);this.own(a);try{this.art.on(t,n),this.active||a()}catch(s){throw this.release(a),s}}proxy(t,e,i){if(!this.active)return;const n=this;function a(...t){if(n.active)return i.apply(this,t)}let s;const r=()=>{if("function"===typeof s){const t=this.art.events;"function"===typeof t?.remove&&t.destroyEvents?.has(s)?t.remove(s):s()}else t.removeEventListener?.(e,a)};this.own(r);try{s=this.art.proxy(t,e,a),this.active||r()}catch(o){throw this.release(r),o}}wait(t){return Promise.race([t,this.cancelled])}release(t){try{t()}catch(e){console.warn("Failed to dispose danmuku setting resource:",e)}}close(){if(!this.closed){this.closed=!0,this.cancel();for(const t of this.disposers)this.release(t);this.disposers.clear()}}}function F({min:t,max:e,container:i,findIndex:n,onChange:a,steps:s=[]}){const{lifecycle:r}=this;if(!r.active)return{reset(){}};const{query:o,clamp:l,setStyle:d}=this.utils;d(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 c=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i){if(!r.active)return;if(void 0===i&&(i=n()),ie)return;const o=(i-t)/(e-t);c.style.left=100*o+"%",0===s.length&&(p.style.width=c.style.left),a(i)}function m(n){const{top:a,height:s,left:r,width:o}=i.getBoundingClientRect();if(this.art.isRotate){const i=l(n.clientY-a,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=l(n.clientX-r,0,o);u(Math.round(i/o*(e-t)+t))}}return r.own(()=>h=!1),r.proxy(i,"click",t=>{m.call(this,t)}),r.proxy(i,"pointerdown",t=>{h=0===t.button}),r.on("document:pointermove",t=>{h&&m.call(this,t)}),r.on("document:pointerup",t=>{h&&(h=!1,m.call(this,t))}),{reset:u}}const D='',T='',P='',A='',L='',H='',O='',V='',N='',q='',I='',j='';function B(){const{option:t}=this;return`\n
\n ${I}${q}\n
\n
\n ${P}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${A}${L}\n
\u6eda\u52a8
\n
\n
\n ${H}${O}\n
\u9876\u90e8
\n
\n
\n ${V}${N}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${T}${D}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${T}${D}\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 ${j}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${L}\n
\u6eda\u52a8
\n
\n
\n ${O}\n
\u9876\u90e8
\n
\n
\n ${N}\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 `}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku",e=Symbol.for("artplayer-plugin-danmuku.pending-style");let i=document.getElementById(t)||document[e];if(!i)if(i=document.createElement("style"),i.id=t,"loading"===document.readyState){document[e]=i;const n=()=>{document.removeEventListener("DOMContentLoaded",n),document[e]===i&&delete document[e];const a=document.getElementById(t);a?a.textContent=i.textContent:(document.head||document.documentElement).appendChild(i)};document.addEventListener("DOMContentLoaded",n,{once:!0})}else(document.head||document.documentElement).appendChild(i);i.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}'}class R{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:n}=t.template;this.template={$controlsCenter:n,$mount:n,$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.lifecycle=new C(t),this.destroy=this.destroy.bind(this),this.lifecycle.own(()=>t.off("destroy",this.destroy));try{if(t.on("destroy",this.destroy),!this.lifecycle.active)return;if(this.lifecycle.write(n.style,"display","flex",t=>i(n,"display",t)),this.createTemplate(),!this.lifecycle.active)return;if(this.createSliders(),!this.lifecycle.active)return;if(this.createEvents(),this.mount(this.option.mount),!this.lifecycle.active)return;this.lifecycle.on("resize",()=>this.resize()),this.lifecycle.on("fullscreen",t=>this.onFullscreen(t)),this.lifecycle.on("fullscreenWeb",t=>this.onFullscreen(t)),this.lifecycle.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),this.lifecycle.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}catch(a){try{this.destroy()}catch(s){console.warn("Failed to roll back danmuku setting:",s)}throw a}}static get icons(){return{$on:I,$off:q,$config:P,$style:j,$mode_0_off:A,$mode_0_on:L,$mode_1_off:H,$mode_1_on:O,$mode_2_off:V,$mode_2_on:N,$check_on:T,$check_off:D}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){return B.call(this)}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){if(!this.lifecycle.active)return;const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){if(!this.lifecycle.active)return;const{$player:i}=this.art.template,{$mount:n}=this.template;this.lifecycle.write(i.dataset,t,e),this.outside&&this.lifecycle.write(n.dataset,t,e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");this.template.$danmuku=i,i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,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:n}=this.template;this.lifecycle.on("artplayerPluginDanmuku:show",()=>{e(n,"\u5173\u95ed\u5f39\u5e55")}),this.lifecycle.on("artplayerPluginDanmuku:hide",()=>{e(n,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:n,$antiOverlap:a,$syncVideo:s,$send:r,$input:o}=this.template;this.lifecycle.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.lifecycle.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.lifecycle.proxy(a,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.lifecycle.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.lifecycle.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.lifecycle.proxy(n,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.lifecycle.proxy(r,"click",()=>this.emit()),this.lifecycle.proxy(o,"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(t){return F.call(this,t)}onFullscreen(t){if(!this.lifecycle.active)return;const{$danmuku:e,$controlsCenter:i,$mount:n}=this.template;this.outside?t?this.append(i,e):this.append(n,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){if(!this.lifecycle.active)return;const{$player:i}=this.art.template,n=t.getBoundingClientRect(),a=e.getBoundingClientRect(),s=i.getBoundingClientRect(),r=a.width/2-n.width/2,o=s.left-(n.left-r),l=n.right+r-s.right;e.style.left=o>0?`${-r+o}px`:l>0?-r-l+"px":-r+"px"}emit(){return async function(t){const{lifecycle:e,template:{$input:i}}=t;if(!e.active)return;const n=i.value.trim();if(!n.length||t.isLock||t.emitting)return;const a={text:n,mode:t.option.mode,color:t.option.color,time:t.art.currentTime},s=t=>{e.active&&console.error("Error emitting danmuku:",t)};try{t.emitting=!0;const n=await e.wait(t.option.beforeEmit(a));if(!e.active)return;if(t.emitting=!1,!0!==n)return;if(a.border=!0,delete a.time,Promise.resolve(t.danmuku.emit(a)).catch(s),!e.active)return;i.value="",t.lock()}catch(r){s(r),e.active&&(t.emitting=!1)}}(this)}lock(){!function(t){if(!t.lifecycle.active)return;const{addClass:e}=t.utils,{$send:i}=t.template;clearTimeout(t.timer),t.isLock=!0;let n=t.option.lockTime;i.textContent=n,e(i,"apd-lock");const a=()=>{t.timer=setTimeout(()=>{t.lifecycle.active&&(0===n?t.unlock():(n-=1,i.textContent=n,a()))},1e3)};a()}(this)}unlock(){!function(t){if(clearTimeout(t.timer),t.timer=null,t.isLock=!1,!t.lifecycle.active)return;const{removeClass:e}=t.utils,{$send:i}=t.template;i.textContent="\u53d1\u9001",e(i,"apd-lock")}(this)}resize(){if(!this.lifecycle.active)return;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){if(!this.lifecycle.active)return;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()}destroy(){if(this.lifecycle.closed)return;this.lifecycle.close(),clearTimeout(this.timer),this.timer=null,this.emitting=!1,this.isLock=!1;const t=this.template.$danmuku;t?.parentElement&&t.parentElement.removeChild(t)}}function Z(t){return e=>{const i=new k(e,t);let n;try{n=e.isDestroy?void 0:new R(e,i),!e.isDestroy&&i.option.heatmap&&z(e,i,i.option.heatmap)}catch(a){try{n?.destroy()}catch{}try{i.destroy()}catch{}throw a}return{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:n?n.mount.bind(n):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return Z.icons=R.icons,Z}); diff --git a/docs/compiled/artplayer-plugin-danmuku.legacy.js b/docs/compiled/artplayer-plugin-danmuku.legacy.js index 0052ae2a5..206abcd73 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,n=Object.getOwnPropertySymbols,a=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,o=Math.pow,r=(e,i,n)=>i in e?t(e,i,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[i]=n,l=(t,e)=>{for(var i in e||(e={}))a.call(e,i)&&r(t,i,e[i]);if(n)for(var i of n(e))s.call(e,i)&&r(t,i,e[i]);return t},d=(t,n)=>e(t,i(n)),p=(t,e,i)=>new Promise((n,a)=>{var s=t=>{try{r(i.next(t))}catch(e){a(e)}},o=t=>{try{r(i.throw(t))}catch(e){a(e)}},r=t=>t.done?n(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,n=c(e);globalThis.postMessage({danmus:n,id:i})}let f=0;function g(t,{signal:e,onCancel:i}={}){return new Promise((n,a)=>{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 n=o;o=void 0;try{URL.revokeObjectURL(n)}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?a(i):n(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 n=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(n,t.error||t)},s.onmessageerror=t=>v(n,t.error||t),s.postMessage({xml:n,id:d})}catch(a){v(n,a)}})}().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 w(t){const e=k(t),i=e.emitting[e.emitting.length-1];return!e.closed&&!t.art.isDestroy&&(!i||i.active())}function x(t){const e=k(t);e.closed=!0;for(const i of[...e.tasks])i.cancel()}function $(t,e,i){const n=t.states[e]||[];for(let a=0;a{if(this.frame=null,t.timer=null,this.closed||this.fault||t.isStop||t.art.isDestroy)return;let e;const i=new Promise(t=>e=()=>t(b)),n={generation:this.generation,cancel:e,wait:t=>Promise.race([t,i]),ref:null};return this.operation=n,this.run(n).catch(t=>{this.active(n)&&this.fail(t)}).finally(()=>{this.release(n),this.operation===n&&(this.operation=null,this.schedule())})}),t.timer=this.frame)}run(t){return p(this,null,function*(){const e=this.owner,{setStyles:i}=e.utils;if(!e.art.playing||!this.active(t))return;e.filter("emit",t=>{const i=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=i,t.$lastStartTime=Date.now(),t.$restTime<=0&&e.makeWait(t)});const n=e.readys;for(const s of n){if(!this.active(t))return;if(this.failedItems.has(s))continue;let n;try{n=yield t.wait(e.option.beforeVisible(s))}catch(a){if(!this.active(t))return;this.failedItems.add(s),this.report(a);continue}if(!this.active(t))return;if(!n)continue;const{clientWidth:o,clientHeight:r}=e.$player,l=e.$ref;if(t.danmu=s,t.ref=l,s.$ref=l,l.textContent=s.text,e.$danmuku.appendChild(l),l.style.opacity=e.option.opacity,l.style.fontSize=`${e.fontSize}px`,l.style.color=s.color,l.style.border=s.border?`1px solid ${s.color}`:null,l.style.backgroundColor=s.border?"rgb(0 0 0 / 50%)":null,i(l,s.style),!this.active(t))return;s.$lastStartTime=Date.now(),s.$restTime=e.speed;const d=o+l.clientWidth,{result:p}=yield t.wait(e.postMessage({type:"getDanmuTop",target:{mode:s.mode,height:l.clientHeight,speed:d/s.$restTime},visibles:e.visibles,antiOverlap:e.option.antiOverlap,clientWidth:o,clientHeight:r,marginBottom:e.marginBottom,marginTop:e.marginTop}));if(!this.active(t)||s.$ref!==l)return;if(void 0!==p){switch(e.setState(s,"emit"),l.style.top=`${p}px`,l.style.visibility="visible",l.dataset.mode=s.mode,l.dataset.id=s.id||"",s.mode){case 0:l.style.left=`${o}px`,l.style.marginLeft="0px",l.style.transform=`translateX(${-d}px)`,l.style.transition=`transform ${s.$restTime}s linear 0s`;break;case 1:case 2:l.style.left="50%",l.style.marginLeft=`-${l.clientWidth/2}px`}t.ref=null,e.art.emit("artplayerPluginDanmuku:visible",s)}else e.setState(s,"ready"),this.release(t)}})}destroy(){this.closed=!0,this.invalidate()}}let M=0;class z{constructor(t,e){this.pending=new Map,this.closed=!1,this.failed=!1,this.onFailure=e,this.worker=t();try{this.worker.onmessage=t=>{var e;const i=this.pending.get(null==(e=t.data)?void 0:e.id);i&&(this.pending.delete(t.data.id),i.resolve(t.data))},this.worker.onerror=t=>{var e;null==(e=t.preventDefault)||e.call(t),this.fail(t.error||t)},this.worker.onmessageerror=t=>this.fail(t.error||t)}catch(i){try{this.dispose()}catch(n){}throw i}}request(t){t.id=++M;const{id:e}=t;return this.closed||this.failed?Promise.resolve({id:e,result:void 0}):new Promise((i,n)=>{this.pending.set(e,{resolve:i,reject:n});try{this.worker.postMessage(t)}catch(a){this.fail(a)}})}fail(t){if(!this.closed&&!this.failed){this.failed=!0;for(const e of this.pending.values())e.reject(t);this.pending.clear();try{this.dispose()}catch(e){}this.onFailure(t)}}cancel(){for(const[t,e]of this.pending)e.resolve({id:t,result:void 0});this.pending.clear()}dispose(){if(this.closed)return;this.closed=!0,this.cancel();let t,e=!1;const i=i=>{try{i()}catch(n){e||(t=n),e=!0}};for(const n of["onmessage","onerror","onmessageerror"])i(()=>this.worker[n]=null);if(i(()=>this.worker.terminate()),e)throw t}}const C='/*!\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',E="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",C],{type:"text/javascript;charset=utf-8"});function D(t){let e;try{if(e=E&&(self.URL||self.webkitURL).createObjectURL(E),!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(C),{type:"module",name:null==t?void 0:t.name})}}class F{constructor(t,e){const{constructor:i,template:n}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=n.$danmuku,this.$player=n.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.worker=null,this.workerClient=null,this.scheduler=new S(this),this.option=F.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),this.seek=this.seek.bind(this),w(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!w(this))return;this.createWorker()}catch(a){throw t.off("destroy",this.destroy),x(this),this.scheduler.destroy(),a}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),t.on("video:seeking",this.seek),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 F.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 F.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 n=Number.parseFloat(i)/100;return Math.round(t(e*n,12,e))}return F.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=F.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){return function(t){const{currentTime:e}=t.art,i=[];return $(t,"ready",t=>i.push(t)),$(t,"wait",t=>{e+.1>=t.time&&t.time>=e-.1&&i.push(t)}),i}(this)}get visibles(){const t=[],{clientWidth:e}=this.$player,i=this.getLeft(this.$player);return this.filter("emit",n=>{const a=n.$ref.offsetTop,s=this.getLeft(n.$ref)-i,o=n.$ref.clientHeight,r=n.$ref.clientWidth,l=s+r,d=e-l,p=l/n.$restTime,h={};h.top=a,h.left=s,h.height=o,h.width=r,h.right=d,h.speed=p,h.distance=l,h.time=n.$restTime,h.mode=n.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 n,a=i.closed||t.art.isDestroy;const s=new Promise(t=>n=t),o=new Set,r="function"===typeof AbortController?new AbortController:void 0,l={replace:e,signal:null==r?void 0:r.signal,active:()=>!a&&!i.closed&&!t.art.isDestroy,onCancel:t=>l.active()?(o.add(t),()=>o.delete(t)):(t(),()=>{}),cancel(){if(!a){a=!0,n(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 a?n(v):i.tasks.add(l),l}(this,void 0===t);try{if(!i.active())return this;const n=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),a=n.asynchronous?yield i.wait(n.value):n.value;if(!i.active())return this;if(e(Array.isArray(a),"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=a[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:n,clamp:a,mount:s}){const o=Object.assign({},i,t,e);return n(o),o.mode=a(o.mode,0,2),o.speed=a(o.speed,1,10),o.opacity=a(o.opacity,0,1),o.lockTime=a(o.lockTime,1,60),o.maxLength=a(o.maxLength,1,1e3),o.mount=o.mount||s,o}(this.option,t,{defaults:F.option,validate:t=>this.validator(t,F.scheme),clamp:i,mount:n});if(!w(this))return this;const r=this.option.beforeVisible!==o.beforeVisible;return this.option=o,r&&(this.scheduler.invalidate(),this.scheduler.schedule()),t.fontSize&&(this.reset(),!w(this))||(this.option.visible?this.show():this.hide(),w(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return this.workerClient.request(t)}createWorker(){this.workerClient=new z(()=>new D,t=>this.scheduler.fail(t)),this.worker=this.workerClient.worker}filter(t,e){return $(this,t,e)}setState(t,e){!function(t,e,i){t.states[e.$state]=t.states[e.$state].filter(t=>t!==e),e.$state=i,e.$ref&&(e.$ref.dataset.state=i),t.states[i].push(e)}(this,t,e)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=F.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(){return this.scheduler.running=!0,this.scheduler.schedule(),this}resize(){const{clientWidth:t}=this.$player;this.filter("stop",e=>{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.scheduler.running=!1,this.scheduler.invalidate(),this.suspend(),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){if(this.scheduler.closed||this.art.isDestroy)return this;const t=++this.scheduler.starts,e=this.scheduler.generation;this.isStop=!1,this.scheduler.recover();return this.scheduler.closed||this.art.isDestroy||this.isStop||this.scheduler.fault||e!==this.scheduler.generation||t!==this.scheduler.starts||(this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start")),this}reset(){return this.scheduler.invalidate(),this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this.scheduler.recover(),this.scheduler.schedule(),this}seek(){this.scheduler.invalidate(),this.scheduler.schedule()}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.scheduler.invalidate(),this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this.scheduler.schedule(),this}destroy(){var t;x(this),this.scheduler.destroy();try{try{this.stop()}finally{null==(t=this.workerClient)||t.dispose()}}finally{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("video:seeking",this.seek),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}}const T={map:(t,e,i,n,a)=>(t-e)*(a-n)/(i-e)+n,range(t,e,i){const n=Math.round(t/i)*i;return Array.from({length:Math.floor((e-t)/i)},(t,e)=>e*i+n)}};function A(t,e,i){const{query:n}=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(a){let s=null,r=null;function l(l=[]){if(s=null,r=null,a.innerHTML="",!t.duration||t.option.isLive)return;const d={w:a.offsetWidth,h:a.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 n=e.queue.filter(({time:e})=>e>t*i&&e<=(t+p.sampling)*i).length;h.push([t,n])}}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,n)=>{const a=function(t,e){const i=e[0]-t[0],n=e[1]-t[1];return{length:Math.sqrt(o(i,2)+o(n,2)),angle:Math.atan2(n,i)}}(e||t,i||t),s=T.map(Math.cos(a.angle)*p.flattening,0,1,1,0),r=a.angle*s+(n?Math.PI:0),l=a.length*p.smoothing;return[t[0]+Math.cos(r)*l,t[1]+Math.sin(r)*l]},v=h.map(t=>[T.map(t[0],p.xMin,p.xMax,0,d.w),T.map(t[1],p.yMin,p.yMax,d.h,0)]).reduce((t,e,i,n)=>0===i?`M ${n[n.length-1][0]},${d.h} L ${e[0]},${d.h} L ${e[0]},${e[1]}`:`${t} ${((t,e,i)=>{const n=y(i[e-1],i[e-2],t),a=y(t,i[e-1],i[e+1],!0),s=e===i.length-1?" z":"";return`C ${n[0]},${n[1]} ${a[0]},${a[1]} ${t[0]},${t[1]}${s}`})(e,i,n)}`,"");a.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,s=n("#heatmap-start",a),r=n("#heatmap-stop",a),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 P='',L='',O='',H='',V='',j='',R='',I='',q='',B='',Z='',N='';class W{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:n}=t.template;i(n,"display","flex"),this.template={$controlsCenter:n,$mount:n,$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:Z,$off:B,$config:O,$style:N,$mode_0_off:H,$mode_0_on:V,$mode_1_off:j,$mode_1_on:R,$mode_2_off:I,$mode_2_on:q,$check_on:L,$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 ${Z}${B}\n
\n
\n ${O}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${H}${V}\n
\u6eda\u52a8
\n
\n
\n ${j}${R}\n
\u9876\u90e8
\n
\n
\n ${I}${q}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${L}${P}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${L}${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 ${N}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${V}\n
\u6eda\u52a8
\n
\n
\n ${R}\n
\u9876\u90e8
\n
\n
\n ${q}\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:n}=this.template;i.dataset[t]=e,this.outside&&(n.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:n}=this.template;this.art.on("artplayerPluginDanmuku:show",()=>{e(n,"\u5173\u95ed\u5f39\u5e55")}),this.art.on("artplayerPluginDanmuku:hide",()=>{e(n,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:n,$antiOverlap:a,$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(a,"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(n,"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:n,onChange:a,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=n()){if(ie)return;const o=(i-t)/(e-t);d.style.left=100*o+"%",0===s.length&&(p.style.width=d.style.left),a(i)}function c(n){const{top:a,height:s,left:o,width:l}=i.getBoundingClientRect();if(this.art.isRotate){const i=r(n.clientY-a,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=r(n.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:n}=this.template;this.outside?t?this.append(i,e):this.append(n,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){const{$player:i}=this.art.template,n=t.getBoundingClientRect(),a=e.getBoundingClientRect(),s=i.getBoundingClientRect(),o=a.width/2-n.width/2,r=s.left-(n.left-o),l=n.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(n){console.error("Error emitting danmuku:",n),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 n=()=>{this.timer=setTimeout(()=>{0===i?this.unlock():(i-=1,e.textContent=i,n())},1e3)};n()}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 _(t){return e=>{const i=new F(e,t),n=e.isDestroy?void 0:new W(e,i);return!e.isDestroy&&i.option.heatmap&&A(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:n?n.mount.bind(n):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return _.icons=W.icons,_}); +!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,n=Object.getOwnPropertySymbols,a=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,r=Math.pow,o=(e,i,n)=>i in e?t(e,i,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[i]=n,l=(t,e)=>{for(var i in e||(e={}))a.call(e,i)&&o(t,i,e[i]);if(n)for(var i of n(e))s.call(e,i)&&o(t,i,e[i]);return t},d=(t,n)=>e(t,i(n)),c=(t,e,i)=>new Promise((n,a)=>{var s=t=>{try{o(i.next(t))}catch(e){a(e)}},r=t=>{try{o(i.throw(t))}catch(e){a(e)}},o=t=>t.done?n(t.value):Promise.resolve(t.value).then(s,r);o((i=i.apply(t,e)).next())});function p(t){return t instanceof Promise||"[object Promise]"===Object.prototype.toString.call(t)}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 m({data:t}){const{xml:e,id:i}=t,n=u(e);globalThis.postMessage({danmus:n,id:i})}let f=0;function g(t,{signal:e,onCancel:i}={}){return new Promise((n,a)=>{let s,r,o,l=!1;const d=++f,p=()=>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!==r){const n=r;r=void 0;try{URL.revokeObjectURL(n)}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",p),null==o||o();try{g()}catch(r){s||(i=r),s=!0}s?a(i):n(t)}function v(t,e){if(!l){console.error("Error parsing Bilibili Danmu:",e);try{y(u(t))}catch(i){y(void 0,i)}}}(null==e?void 0:e.aborted)?p():(null==e||e.addEventListener("abort",p),o=null==i?void 0:i(p),l?null==o||o():function(){return c(this,null,function*(){const i=e?yield fetch(t,{signal:e}):yield fetch(t);if(l)return;const n=yield i.text();if(!l)try{const t=`\n ${h.toString()}\n ${u.toString()}\n onmessage = ${m.toString()}\n `;if(r=URL.createObjectURL(new Blob([t],{type:"application/javascript"})),l)return void g();if(s=new Worker(r),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(n,t.error||t)},s.onmessageerror=t=>v(n,t.error||t),s.postMessage({xml:n,id:d})}catch(a){v(n,a)}})}().catch(t=>y(void 0,t)))})}const y=new WeakMap,v=Symbol("cancelled danmuku input");function w(t){return y.has(t)||y.set(t,{closed:!1,tasks:new Set,emitting:[]}),y.get(t)}function k(t){const e=w(t),i=e.emitting[e.emitting.length-1];return!e.closed&&!t.art.isDestroy&&(!i||i.active())}function x(t){const e=w(t);e.closed=!0;for(const i of[...e.tasks])i.cancel()}function b(t,e,i){const n=t.states[e]||[];for(let a=0;a{const a=n.$ref.offsetTop,s=this.owner.getLeft(n.$ref)-i,r=n.$ref.clientHeight,o=n.$ref.clientWidth,l=s+o,d=e-l,c=l/n.$restTime,p={};p.top=a,p.left=s,p.height=r,p.width=o,p.right=d,p.speed=c,p.distance=l,p.time=n.$restTime,p.mode=n.mode,t.push(p)}),t}left(t){const e=t.getBoundingClientRect();return this.owner.isRotate?e.top:e.left}makeWait(t){this.owner.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=this.owner.constructor.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.owner.$refs.push(t.$ref),t.$ref=null)}resize(){const{clientWidth:t}=this.owner.$player;this.owner.filter("stop",e=>{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.owner.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.owner.$player;return this.owner.filter("stop",e=>{switch(this.owner.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.owner.$player;return this.owner.filter("emit",e=>{switch(this.owner.setState(e,"stop"),e.mode){case 0:{const i=t-(this.owner.getLeft(e.$ref)-this.owner.getLeft(this.owner.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}prepare(t,e){const i=this.owner,n=i.$ref;return e.danmu=t,e.ref=n,t.$ref=n,n.textContent=t.text,i.$danmuku.appendChild(n),n.style.opacity=i.option.opacity,n.style.fontSize=`${i.fontSize}px`,n.style.color=t.color,n.style.border=t.border?`1px solid ${t.color}`:null,n.style.backgroundColor=t.border?"rgb(0 0 0 / 50%)":null,i.utils.setStyles(n,t.style),n}place(t,e,i,n,a){switch(e.style.top=`${i}px`,e.style.visibility="visible",e.dataset.mode=t.mode,e.dataset.id=t.id||"",t.mode){case 0:e.style.left=`${a}px`,e.style.marginLeft="0px",e.style.transform=`translateX(${-n}px)`,e.style.transition=`transform ${t.$restTime}s linear 0s`;break;case 1:case 2:e.style.left="50%",e.style.marginLeft=`-${e.clientWidth/2}px`}}release(t){const{danmu:e,ref:i}=t;i&&e.$ref===i&&(i.style.cssText=this.owner.constructor.cssText,i.style.visibility="hidden",this.owner.$refs.push(i),e.$ref=null),t.ref=null}clear(){this.destroy(),this.owner.$refs=[],this.owner.$danmuku.textContent=""}destroy(){for(const t of this.nodes)t.parentElement&&t.parentElement.removeChild(t);for(const t of this.owner.queue)t.$ref=null;this.owner.$refs.length=0,this.nodes.clear()}}const M=Symbol("cancelled danmuku frame");class S{constructor(t){this.owner=t,this.generation=0,this.starts=0,this.frame=null,this.operation=null,this.closed=!1,this.running=!1,this.fault=!1,this.failedItems=new Set}active(t){const e=this.owner;return!this.closed&&!this.fault&&!e.art.isDestroy&&!e.isStop&&!e.isHide&&t.generation===this.generation}release(t){this.owner.renderer.release(t)}invalidate(){var t;if(this.generation++,null!==this.frame&&window.cancelAnimationFrame(this.frame),this.frame=null,this.owner.timer=null,this.operation){const t=this.operation;this.operation=null,t.cancel(),this.release(t)}null==(t=this.owner.workerClient)||t.cancel(),this.failedItems.clear()}report(t){try{this.owner.art.emit("artplayerPluginDanmuku:error",t)}catch(e){console.warn("Failed to report danmuku scheduling error:",e)}}fail(t){this.closed||this.fault||(this.fault=!0,this.invalidate(),this.report(t))}recover(){var t;if(!this.closed&&!this.owner.art.isDestroy&&(this.failedItems.clear(),this.fault))try{null==(t=this.owner.workerClient)||t.dispose(),this.owner.createWorker(),this.fault=!1}catch(e){this.report(e)}}schedule(){const t=this.owner;!this.running||this.closed||this.fault||t.art.isDestroy||t.isStop||null!==this.frame||this.operation||(this.frame=window.requestAnimationFrame(()=>{if(this.frame=null,t.timer=null,this.closed||this.fault||t.isStop||t.art.isDestroy)return;let e;const i=new Promise(t=>e=()=>t(M)),n={generation:this.generation,cancel:e,wait:t=>Promise.race([t,i]),ref:null};return this.operation=n,this.run(n).catch(t=>{this.active(n)&&this.fail(t)}).finally(()=>{this.release(n),this.operation===n&&(this.operation=null,this.schedule())})}),t.timer=this.frame)}run(t){return c(this,null,function*(){const e=this.owner;if(!e.art.playing||!this.active(t))return;e.filter("emit",t=>{const i=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=i,t.$lastStartTime=Date.now(),t.$restTime<=0&&e.makeWait(t)});const i=e.readys;for(const a of i){if(!this.active(t))return;if(this.failedItems.has(a))continue;let i;try{i=yield t.wait(e.option.beforeVisible(a))}catch(n){if(!this.active(t))return;this.failedItems.add(a),this.report(n);continue}if(!this.active(t))return;if(!i)continue;const{clientWidth:s,clientHeight:r}=e.$player,o=e.renderer.prepare(a,t);if(!this.active(t))return;a.$lastStartTime=Date.now(),a.$restTime=e.speed;const l=s+o.clientWidth,{result:d}=yield t.wait(e.postMessage({type:"getDanmuTop",target:{mode:a.mode,height:o.clientHeight,speed:l/a.$restTime},visibles:e.visibles,antiOverlap:e.option.antiOverlap,clientWidth:s,clientHeight:r,marginBottom:e.marginBottom,marginTop:e.marginTop}));if(!this.active(t)||a.$ref!==o)return;void 0!==d?(e.setState(a,"emit"),e.renderer.place(a,o,d,l,s),t.ref=null,e.art.emit("artplayerPluginDanmuku:visible",a)):(e.setState(a,"ready"),this.release(t))}})}destroy(){this.closed=!0,this.invalidate()}}let z=0;class E{constructor(t,e){this.pending=new Map,this.closed=!1,this.failed=!1,this.onFailure=e,this.worker=t();try{this.worker.onmessage=t=>{var e;const i=this.pending.get(null==(e=t.data)?void 0:e.id);i&&(this.pending.delete(t.data.id),i.resolve(t.data))},this.worker.onerror=t=>{var e;null==(e=t.preventDefault)||e.call(t),this.fail(t.error||t)},this.worker.onmessageerror=t=>this.fail(t.error||t)}catch(i){try{this.dispose()}catch(n){}throw i}}request(t){t.id=++z;const{id:e}=t;return this.closed||this.failed?Promise.resolve({id:e,result:void 0}):new Promise((i,n)=>{this.pending.set(e,{resolve:i,reject:n});try{this.worker.postMessage(t)}catch(a){this.fail(a)}})}fail(t){if(!this.closed&&!this.failed){this.failed=!0;for(const e of this.pending.values())e.reject(t);this.pending.clear();try{this.dispose()}catch(e){}this.onFailure(t)}}cancel(){for(const[t,e]of this.pending)e.resolve({id:t,result:void 0});this.pending.clear()}dispose(){if(this.closed)return;this.closed=!0,this.cancel();let t,e=!1;const i=i=>{try{i()}catch(n){e||(t=n),e=!0}};for(const n of["onmessage","onerror","onmessageerror"])i(()=>this.worker[n]=null);if(i(()=>this.worker.terminate()),e)throw t}}const C='/*!\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',F="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",C],{type:"text/javascript;charset=utf-8"});function D(t){let e;try{if(e=F&&(self.URL||self.webkitURL).createObjectURL(F),!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(C),{type:"module",name:null==t?void 0:t.name})}}class P{constructor(t,e){const{constructor:i,template:n}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=n.$danmuku,this.$player=n.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.worker=null,this.workerClient=null,this.renderer=new $(this),this.scheduler=new S(this),this.option=P.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),this.seek=this.seek.bind(this),k(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!k(this))return;this.createWorker()}catch(a){throw t.off("destroy",this.destroy),x(this),this.scheduler.destroy(),a}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),t.on("video:seeking",this.seek),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 P.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 P.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 n=Number.parseFloat(i)/100;return Math.round(t(e*n,12,e))}return P.option.fontSize}get $ref(){return this.renderer.acquire()}get readys(){return function(t){const{currentTime:e}=t.art,i=[];return b(t,"ready",t=>i.push(t)),b(t,"wait",t=>{e+.1>=t.time&&t.time>=e-.1&&i.push(t)}),i}(this)}get visibles(){return this.renderer.visibles}get speed(){return this.option.synchronousPlayback&&this.art.playbackRate?this.option.speed/Number(this.art.playbackRate):this.option.speed}load(t){return c(this,null,function*(){const{errorHandle:e}=this.utils,i=function(t,e){const i=w(t);if(e)for(const d of[...i.tasks])d.replace&&d.cancel();let n,a=i.closed||t.art.isDestroy;const s=new Promise(t=>n=t),r=new Set,o="function"===typeof AbortController?new AbortController:void 0,l={replace:e,signal:null==o?void 0:o.signal,active:()=>!a&&!i.closed&&!t.art.isDestroy,onCancel:t=>l.active()?(r.add(t),()=>r.delete(t)):(t(),()=>{}),cancel(){if(!a){a=!0,n(v),null==o||o.abort();for(const t of[...r])t();r.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),r.clear()}};return a?n(v):i.tasks.add(l),l}(this,void 0===t);try{if(!i.active())return this;const n=function(t,e){return"function"===typeof t?{asynchronous:!0,value:t()}:p(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),a=n.asynchronous?yield i.wait(n.value):n.value;if(!i.active())return this;if(e(Array.isArray(a),"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.renderer.clear()}for(let t=0;t{const e=a[t],i=s[t];return"function"===typeof e||"function"===typeof i||p(e)||p(i)?e!==i:JSON.stringify(e)!==JSON.stringify(i)}))&&!e)return this;const r=function(t,e,{defaults:i,validate:n,clamp:a,mount:s}){const r=Object.assign({},i,t,e);return n(r),r.mode=a(r.mode,0,2),r.speed=a(r.speed,1,10),r.opacity=a(r.opacity,0,1),r.lockTime=a(r.lockTime,1,60),r.maxLength=a(r.maxLength,1,1e3),r.mount=r.mount||s,r}(this.option,t,{defaults:P.option,validate:t=>this.validator(t,P.scheme),clamp:i,mount:n});if(!k(this))return this;const o=this.option.beforeVisible!==r.beforeVisible;return this.option=r,o&&(this.scheduler.invalidate(),this.scheduler.schedule()),t.fontSize&&(this.reset(),!k(this))||(this.option.visible?this.show():this.hide(),k(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this}getLeft(t){return this.renderer.left(t)}postMessage(t={}){return this.workerClient.request(t)}createWorker(){this.workerClient=new E(()=>new D,t=>this.scheduler.fail(t)),this.worker=this.workerClient.worker}filter(t,e){return b(this,t,e)}setState(t,e){!function(t,e,i){t.states[e.$state]=t.states[e.$state].filter(t=>t!==e),e.$state=i,e.$ref&&(e.$ref.dataset.state=i),t.states[i].push(e)}(this,t,e)}makeWait(t){this.renderer.makeWait(t)}update(){return this.scheduler.running=!0,this.scheduler.schedule(),this}resize(){this.renderer.resize()}continue(){return this.renderer.continue(),this}suspend(){return this.renderer.suspend(),this}stop(){return this.isStop=!0,this.scheduler.running=!1,this.scheduler.invalidate(),this.suspend(),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){if(this.scheduler.closed||this.art.isDestroy)return this;const t=++this.scheduler.starts,e=this.scheduler.generation;this.isStop=!1,this.scheduler.recover();return this.scheduler.closed||this.art.isDestroy||this.isStop||this.scheduler.fault||e!==this.scheduler.generation||t!==this.scheduler.starts||(this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start")),this}reset(){return this.scheduler.invalidate(),this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this.scheduler.recover(),this.scheduler.schedule(),this}seek(){this.scheduler.invalidate(),this.scheduler.schedule()}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.scheduler.invalidate(),this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this.scheduler.schedule(),this}destroy(){var t;x(this),this.scheduler.destroy();try{try{this.stop()}finally{null==(t=this.workerClient)||t.dispose()}}finally{this.renderer.destroy(),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("video:seeking",this.seek),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}}const T=4294967295;function A(t,e){let i=0,n=t.length;for(;iNumber.isFinite(t)&&t>0))return null;const o={xMin:0,xMax:t,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*e),sampling:Math.max(1,Math.floor(t/100)),smoothing:.2,flattening:.2},l=Object.assign({},o,"object"===typeof a?a:{});for(const r of Object.keys(o))Number.isFinite(l[r])||(l[r]=o[r]);if(l.sampling<=0&&(l.sampling=o.sampling),l.xMin===l.xMax||l.yMin===l.yMax)return null;const d=Array.isArray(s)&&s.length?[...s]:function(t,e,i,n){if(!Number.isFinite(e)||e<=0||!Number.isFinite(i)||i<=0||!Number.isFinite(n)||n<=0||Math.floor(e/n)+1>T)return[];const a=t.map(t=>t.time).filter(t=>"number"===typeof t&&!Number.isNaN(t)).sort((t,e)=>t-e),s=[],r=i/e;if(!Number.isFinite(r))return[];for(let o=0;o<=e;o+=n){if(s.length>=T||o+n===o)return[];const t=o*r,e=(o+n)*r;s.push([o,A(a,e)-A(a,t)])}return s}(n,t,i,l.sampling);if(!d.length||!d.every(t=>Array.isArray(t)&&Number.isFinite(t[0])&&Number.isFinite(t[1])))return null;const c=d[d.length-1];if(c[0]!==t){if(d.length===T)return null;d.push([t,c[1]])}let p=1/0,h=-1/0;for(const r of d)p=Math.min(p,r[1]),h=Math.max(h,r[1]);const u=(p+h)/2;for(const r of d){const t=r[1];r[1]=t*(t>u?1+l.scale:1-l.scale)+l.minHeight}const m=(t,e,i,n)=>{const a=function(t,e){const i=e[0]-t[0],n=e[1]-t[1];return{length:Math.sqrt(r(i,2)+r(n,2)),angle:Math.atan2(n,i)}}(e||t,i||t),s=L(Math.cos(a.angle)*l.flattening,0,1,1,0),o=a.angle*s+(n?Math.PI:0),d=a.length*l.smoothing;return[t[0]+Math.cos(o)*d,t[1]+Math.sin(o)*d]},f=d.map(i=>[L(i[0],l.xMin,l.xMax,0,t),L(i[1],l.yMin,l.yMax,e,0)]);if(!f.every(t=>t.every(Number.isFinite)))return null;const g=f.map((t,i,n)=>{if(0===i)return`M ${n[n.length-1][0]},${e} L ${t[0]},${e} L ${t[0]},${t[1]}`;const a=m(n[i-1],n[i-2],t),s=m(t,n[i-1],n[i+1],!0);return`C ${a[0]},${a[1]} ${s[0]},${s[1]} ${t[0]},${t[1]}${i===n.length-1?" z":""}`}).join(" ");return/NaN|Infinity/u.test(g)?null:{path:g,opacity:l.opacity,width:t,height:e}}let H=0;function V(t,e,i){if(t.isDestroy)return;const{query:n}=t.constructor.utils;let a;const s=[];let r,o,l,d=!1;const c=()=>!d&&!t.isDestroy;function p(e,i){s.push([e,i]),t.on(e,i)}function h(e){if(d)return;let i;d=!0,o=null,l=null;let n=!1;const a=t=>{try{t()}catch(e){n||(i=e),n=!0}};for(const[r,o]of s.splice(0))a(()=>t.off(r,o));if(e&&r&&t.controls.heatmap===r&&a(()=>t.controls.remove("heatmap")),r=void 0,n)throw i}function u(t){c()&&o&&l&&(o.setAttribute("offset",100*t+"%"),l.setAttribute("offset",100*t+"%"))}function m(s){try{!function(s=[]){if(!c())return;if(o=null,l=null,r.innerHTML="",t.option.isLive)return;const d=O({width:r.offsetWidth,height:r.offsetHeight,duration:t.duration,queue:e.queue,option:i,points:s});if(!d||!c())return;const p=r.ownerDocument;for(a||(a=p?"heatmap-solids":"heatmap-solids-"+ ++H);null==p?void 0:p.getElementById(a);)a="heatmap-solids-"+ ++H;r.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,o=n("#heatmap-start",r),l=n("#heatmap-stop",r),u(t.played)}(s)}catch(d){try{h(!0)}catch(p){}throw d}}p("destroy",()=>h(!0));try{t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},beforeUnmount(){h(!1)},mounted(e){if(r=e,d)return t.controls.heatmap===e&&t.controls.remove("heatmap"),void(r=void 0);c()?(p("video:timeupdate",()=>u(t.played)),p("setBar",(t,e)=>{"played"===t&&u(e)}),p("ready",()=>m()),p("resize",()=>m()),p("artplayerPluginDanmuku:loaded",()=>m()),p("artplayerPluginDanmuku:points",t=>m(t))):h(!0)}})}catch(f){try{h(!0)}catch(g){}throw f}return()=>h(!0)}const j=new WeakMap;class N{constructor(t){this.art=t,this.closed=!1,this.disposers=new Set,this.cancelled=new Promise(t=>this.cancel=t)}get active(){return!this.closed&&!this.art.isDestroy}own(t){this.closed?t():this.disposers.add(t)}write(t,e,i,n=i=>t[e]=i){if(!this.active)return;const a=()=>"function"===typeof t.getPropertyPriority?t.getPropertyPriority(e):void 0;let s=j.get(t);s||(s=new Map,j.set(t,s));let r=s.get(e);r||(r={original:t[e],present:e in t||void 0!==t[e],priority:a(),owners:new Map,apply:n},s.set(e,r)),r.owners.has(this)||this.own(()=>{const i=[...r.owners.keys()],n=i[i.length-1]===this;if(r.owners.delete(this),n&&t[e]===r.last&&a()===r.lastPriority){const i=[...r.owners.values()];i.length?(r.apply(i[i.length-1]),r.last=t[e],r.lastPriority=a()):void 0!==r.priority?t.setProperty(e,r.original,r.priority):r.present?r.apply(r.original):delete t[e]}r.owners.size||(s.delete(e),s.size||j.delete(t))}),r.owners.delete(this),r.owners.set(this,i),r.apply(i),r.last=t[e],r.lastPriority=a()}on(t,e){if(!this.active)return;const i=this;function n(...t){if(i.active)return e.apply(this,t)}const a=()=>this.art.off(t,n);this.own(a);try{this.art.on(t,n),this.active||a()}catch(s){throw this.release(a),s}}proxy(t,e,i){if(!this.active)return;const n=this;function a(...t){if(n.active)return i.apply(this,t)}let s;const r=()=>{var i,n;if("function"===typeof s){const t=this.art.events;"function"===typeof(null==t?void 0:t.remove)&&(null==(i=t.destroyEvents)?void 0:i.has(s))?t.remove(s):s()}else null==(n=t.removeEventListener)||n.call(t,e,a)};this.own(r);try{s=this.art.proxy(t,e,a),this.active||r()}catch(o){throw this.release(r),o}}wait(t){return Promise.race([t,this.cancelled])}release(t){try{t()}catch(e){console.warn("Failed to dispose danmuku setting resource:",e)}}close(){if(!this.closed){this.closed=!0,this.cancel();for(const t of this.disposers)this.release(t);this.disposers.clear()}}}function q({min:t,max:e,container:i,findIndex:n,onChange:a,steps:s=[]}){const{lifecycle:r}=this;if(!r.active)return{reset(){}};const{query:o,clamp:l,setStyle:d}=this.utils;d(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 c=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i){if(!r.active)return;if(void 0===i&&(i=n()),ie)return;const o=(i-t)/(e-t);c.style.left=100*o+"%",0===s.length&&(p.style.width=c.style.left),a(i)}function m(n){const{top:a,height:s,left:r,width:o}=i.getBoundingClientRect();if(this.art.isRotate){const i=l(n.clientY-a,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=l(n.clientX-r,0,o);u(Math.round(i/o*(e-t)+t))}}return r.own(()=>h=!1),r.proxy(i,"click",t=>{m.call(this,t)}),r.proxy(i,"pointerdown",t=>{h=0===t.button}),r.on("document:pointermove",t=>{h&&m.call(this,t)}),r.on("document:pointerup",t=>{h&&(h=!1,m.call(this,t))}),{reset:u}}const I='',R='',B='',Z='',W='',_='',U='',G='',X='',Y='',J='',K='';function Q(){const{option:t}=this;return`\n
\n ${J}${Y}\n
\n
\n ${B}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${Z}${W}\n
\u6eda\u52a8
\n
\n
\n ${_}${U}\n
\u9876\u90e8
\n
\n
\n ${G}${X}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${R}${I}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${R}${I}\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 ${K}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${W}\n
\u6eda\u52a8
\n
\n
\n ${U}\n
\u9876\u90e8
\n
\n
\n ${X}\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 `}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku",e=Symbol.for("artplayer-plugin-danmuku.pending-style");let i=document.getElementById(t)||document[e];if(!i)if(i=document.createElement("style"),i.id=t,"loading"===document.readyState){document[e]=i;const n=()=>{document.removeEventListener("DOMContentLoaded",n),document[e]===i&&delete document[e];const a=document.getElementById(t);a?a.textContent=i.textContent:(document.head||document.documentElement).appendChild(i)};document.addEventListener("DOMContentLoaded",n,{once:!0})}else(document.head||document.documentElement).appendChild(i);i.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}'}class tt{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:n}=t.template;this.template={$controlsCenter:n,$mount:n,$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.lifecycle=new N(t),this.destroy=this.destroy.bind(this),this.lifecycle.own(()=>t.off("destroy",this.destroy));try{if(t.on("destroy",this.destroy),!this.lifecycle.active)return;if(this.lifecycle.write(n.style,"display","flex",t=>i(n,"display",t)),this.createTemplate(),!this.lifecycle.active)return;if(this.createSliders(),!this.lifecycle.active)return;if(this.createEvents(),this.mount(this.option.mount),!this.lifecycle.active)return;this.lifecycle.on("resize",()=>this.resize()),this.lifecycle.on("fullscreen",t=>this.onFullscreen(t)),this.lifecycle.on("fullscreenWeb",t=>this.onFullscreen(t)),this.lifecycle.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),this.lifecycle.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}catch(a){try{this.destroy()}catch(s){console.warn("Failed to roll back danmuku setting:",s)}throw a}}static get icons(){return{$on:J,$off:Y,$config:B,$style:K,$mode_0_off:Z,$mode_0_on:W,$mode_1_off:_,$mode_1_on:U,$mode_2_off:G,$mode_2_on:X,$check_on:R,$check_off:I}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){return Q.call(this)}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){if(!this.lifecycle.active)return;const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){if(!this.lifecycle.active)return;const{$player:i}=this.art.template,{$mount:n}=this.template;this.lifecycle.write(i.dataset,t,e),this.outside&&this.lifecycle.write(n.dataset,t,e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");this.template.$danmuku=i,i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,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:n}=this.template;this.lifecycle.on("artplayerPluginDanmuku:show",()=>{e(n,"\u5173\u95ed\u5f39\u5e55")}),this.lifecycle.on("artplayerPluginDanmuku:hide",()=>{e(n,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:n,$antiOverlap:a,$syncVideo:s,$send:r,$input:o}=this.template;this.lifecycle.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.lifecycle.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.lifecycle.proxy(a,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.lifecycle.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.lifecycle.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.lifecycle.proxy(n,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.lifecycle.proxy(r,"click",()=>this.emit()),this.lifecycle.proxy(o,"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(t){return q.call(this,t)}onFullscreen(t){if(!this.lifecycle.active)return;const{$danmuku:e,$controlsCenter:i,$mount:n}=this.template;this.outside?t?this.append(i,e):this.append(n,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){if(!this.lifecycle.active)return;const{$player:i}=this.art.template,n=t.getBoundingClientRect(),a=e.getBoundingClientRect(),s=i.getBoundingClientRect(),r=a.width/2-n.width/2,o=s.left-(n.left-r),l=n.right+r-s.right;e.style.left=o>0?`${-r+o}px`:l>0?-r-l+"px":-r+"px"}emit(){return function(t){return c(this,null,function*(){const{lifecycle:e,template:{$input:i}}=t;if(!e.active)return;const n=i.value.trim();if(!n.length||t.isLock||t.emitting)return;const a={text:n,mode:t.option.mode,color:t.option.color,time:t.art.currentTime},s=t=>{e.active&&console.error("Error emitting danmuku:",t)};try{t.emitting=!0;const n=yield e.wait(t.option.beforeEmit(a));if(!e.active)return;if(t.emitting=!1,!0!==n)return;if(a.border=!0,delete a.time,Promise.resolve(t.danmuku.emit(a)).catch(s),!e.active)return;i.value="",t.lock()}catch(r){s(r),e.active&&(t.emitting=!1)}})}(this)}lock(){!function(t){if(!t.lifecycle.active)return;const{addClass:e}=t.utils,{$send:i}=t.template;clearTimeout(t.timer),t.isLock=!0;let n=t.option.lockTime;i.textContent=n,e(i,"apd-lock");const a=()=>{t.timer=setTimeout(()=>{t.lifecycle.active&&(0===n?t.unlock():(n-=1,i.textContent=n,a()))},1e3)};a()}(this)}unlock(){!function(t){if(clearTimeout(t.timer),t.timer=null,t.isLock=!1,!t.lifecycle.active)return;const{removeClass:e}=t.utils,{$send:i}=t.template;i.textContent="\u53d1\u9001",e(i,"apd-lock")}(this)}resize(){if(!this.lifecycle.active)return;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){if(!this.lifecycle.active)return;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()}destroy(){if(this.lifecycle.closed)return;this.lifecycle.close(),clearTimeout(this.timer),this.timer=null,this.emitting=!1,this.isLock=!1;const t=this.template.$danmuku;(null==t?void 0:t.parentElement)&&t.parentElement.removeChild(t)}}function et(t){return e=>{const i=new P(e,t);let n;try{n=e.isDestroy?void 0:new tt(e,i),!e.isDestroy&&i.option.heatmap&&V(e,i,i.option.heatmap)}catch(a){try{null==n||n.destroy()}catch(s){}try{i.destroy()}catch(s){}throw a}return{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:n?n.mount.bind(n):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return et.icons=tt.icons,et}); diff --git a/docs/compiled/artplayer-plugin-danmuku.mjs b/docs/compiled/artplayer-plugin-danmuku.mjs index b6e8613cc..14f67f4da 100644 --- a/docs/compiled/artplayer-plugin-danmuku.mjs +++ b/docs/compiled/artplayer-plugin-danmuku.mjs @@ -365,6 +365,179 @@ function setItemState(owner, danmu, state) { danmu.$ref.dataset.state = state; owner.states[state].push(danmu); } +class Renderer { + constructor(owner) { + this.owner = owner; + this.nodes = /* @__PURE__ */ new Set(); + } + acquire() { + const $ref = this.owner.$refs.pop() || document.createElement("div"); + $ref.style.cssText = this.owner.constructor.cssText; + $ref.dataset.mode = ""; + $ref.dataset.id = ""; + $ref.className = ""; + this.nodes.add($ref); + return $ref; + } + get visibles() { + const result = []; + const { clientWidth } = this.owner.$player; + const clientLeft = this.owner.getLeft(this.owner.$player); + this.owner.filter("emit", (danmu) => { + const top = danmu.$ref.offsetTop; + const left = this.owner.getLeft(danmu.$ref) - clientLeft; + const height = danmu.$ref.clientHeight; + const width = danmu.$ref.clientWidth; + const distance = left + width; + const right = clientWidth - distance; + const speed = distance / danmu.$restTime; + const emit = {}; + emit.top = top; + emit.left = left; + emit.height = height; + emit.width = width; + emit.right = right; + emit.speed = speed; + emit.distance = distance; + emit.time = danmu.$restTime; + emit.mode = danmu.mode; + result.push(emit); + }); + return result; + } + left($ref) { + const rect = $ref.getBoundingClientRect(); + return this.owner.isRotate ? rect.top : rect.left; + } + makeWait(danmu) { + this.owner.setState(danmu, "wait"); + if (danmu.$ref) { + danmu.$ref.style.cssText = this.owner.constructor.cssText; + danmu.$ref.style.visibility = "hidden"; + danmu.$ref.style.marginLeft = "0px"; + danmu.$ref.style.transform = "translateX(0px)"; + danmu.$ref.style.transition = "transform 0s linear 0s"; + this.owner.$refs.push(danmu.$ref); + danmu.$ref = null; + } + } + resize() { + const { clientWidth } = this.owner.$player; + this.owner.filter("stop", (danmu) => { + switch (danmu.mode) { + // 滚动的弹幕 + case 0: + danmu.$ref.style.left = `${clientWidth}px`; + break; + } + }); + this.owner.filter("emit", (danmu) => { + danmu.$lastStartTime = Date.now(); + switch (danmu.mode) { + // 滚动的弹幕 + case 0: { + const distance = clientWidth + danmu.$ref.clientWidth; + danmu.$ref.style.left = `${clientWidth}px`; + danmu.$ref.style.transform = `translateX(${-distance}px)`; + danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; + break; + } + } + }); + } + continue() { + const { clientWidth } = this.owner.$player; + this.owner.filter("stop", (danmu) => { + this.owner.setState(danmu, "emit"); + danmu.$lastStartTime = Date.now(); + switch (danmu.mode) { + // 继续滚动的弹幕 + case 0: { + const distance = clientWidth + danmu.$ref.clientWidth; + danmu.$ref.style.transform = `translateX(${-distance}px)`; + danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; + break; + } + } + }); + return this; + } + suspend() { + const { clientWidth } = this.owner.$player; + this.owner.filter("emit", (danmu) => { + this.owner.setState(danmu, "stop"); + switch (danmu.mode) { + // 停止滚动的弹幕 + case 0: { + const translateX = clientWidth - (this.owner.getLeft(danmu.$ref) - this.owner.getLeft(this.owner.$player)); + danmu.$ref.style.transform = `translateX(${-translateX}px)`; + danmu.$ref.style.transition = "transform 0s linear 0s"; + break; + } + } + }); + return this; + } + prepare(danmu, operation) { + const owner = this.owner; + const ref = owner.$ref; + operation.danmu = danmu; + operation.ref = ref; + danmu.$ref = ref; + ref.textContent = danmu.text; + owner.$danmuku.appendChild(ref); + ref.style.opacity = owner.option.opacity; + ref.style.fontSize = `${owner.fontSize}px`; + ref.style.color = danmu.color; + ref.style.border = danmu.border ? `1px solid ${danmu.color}` : null; + ref.style.backgroundColor = danmu.border ? "rgb(0 0 0 / 50%)" : null; + owner.utils.setStyles(ref, danmu.style); + return ref; + } + place(danmu, ref, top, distance, clientWidth) { + ref.style.top = `${top}px`; + ref.style.visibility = "visible"; + ref.dataset.mode = danmu.mode; + ref.dataset.id = danmu.id || ""; + switch (danmu.mode) { + case 0: + ref.style.left = `${clientWidth}px`; + ref.style.marginLeft = "0px"; + ref.style.transform = `translateX(${-distance}px)`; + ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; + break; + case 1: + case 2: + ref.style.left = "50%"; + ref.style.marginLeft = `-${ref.clientWidth / 2}px`; + break; + } + } + release(operation) { + const { danmu, ref } = operation; + if (ref && danmu.$ref === ref) { + ref.style.cssText = this.owner.constructor.cssText; + ref.style.visibility = "hidden"; + this.owner.$refs.push(ref); + danmu.$ref = null; + } + operation.ref = null; + } + clear() { + this.destroy(); + this.owner.$refs = []; + this.owner.$danmuku.textContent = ""; + } + destroy() { + for (const node of this.nodes) { + if (node.parentElement) + node.parentElement.removeChild(node); + } + for (const danmu of this.owner.queue) danmu.$ref = null; + this.owner.$refs.length = 0; + this.nodes.clear(); + } +} const cancelled = /* @__PURE__ */ Symbol("cancelled danmuku frame"); class Scheduler { constructor(owner) { @@ -383,14 +556,7 @@ class Scheduler { return !this.closed && !this.fault && !owner.art.isDestroy && !owner.isStop && !owner.isHide && operation.generation === this.generation; } release(operation) { - const { danmu, ref } = operation; - if (ref && danmu.$ref === ref) { - ref.style.cssText = this.owner.constructor.cssText; - ref.style.visibility = "hidden"; - this.owner.$refs.push(ref); - danmu.$ref = null; - } - operation.ref = null; + this.owner.renderer.release(operation); } invalidate() { this.generation++; @@ -463,7 +629,6 @@ class Scheduler { } async run(operation) { const owner = this.owner; - const { setStyles } = owner.utils; if (!owner.art.playing || !this.active(operation)) return; owner.filter("emit", (danmu) => { @@ -494,18 +659,7 @@ class Scheduler { if (!state) continue; const { clientWidth, clientHeight } = owner.$player; - const ref = owner.$ref; - operation.danmu = danmu; - operation.ref = ref; - danmu.$ref = ref; - ref.textContent = danmu.text; - owner.$danmuku.appendChild(ref); - ref.style.opacity = owner.option.opacity; - ref.style.fontSize = `${owner.fontSize}px`; - ref.style.color = danmu.color; - ref.style.border = danmu.border ? `1px solid ${danmu.color}` : null; - ref.style.backgroundColor = danmu.border ? "rgb(0 0 0 / 50%)" : null; - setStyles(ref, danmu.style); + const ref = owner.renderer.prepare(danmu, operation); if (!this.active(operation)) return; danmu.$lastStartTime = Date.now(); @@ -525,23 +679,7 @@ class Scheduler { return; if (top !== void 0) { owner.setState(danmu, "emit"); - ref.style.top = `${top}px`; - ref.style.visibility = "visible"; - ref.dataset.mode = danmu.mode; - ref.dataset.id = danmu.id || ""; - switch (danmu.mode) { - case 0: - ref.style.left = `${clientWidth}px`; - ref.style.marginLeft = "0px"; - ref.style.transform = `translateX(${-distance}px)`; - ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; - break; - case 1: - case 2: - ref.style.left = "50%"; - ref.style.marginLeft = `-${ref.clientWidth / 2}px`; - break; - } + owner.renderer.place(danmu, ref, top, distance, clientWidth); operation.ref = null; owner.art.emit("artplayerPluginDanmuku:visible", danmu); } else { @@ -679,6 +817,7 @@ class Danmuku { this.index = 0; this.worker = null; this.workerClient = null; + this.renderer = new Renderer(this); this.scheduler = new Scheduler(this); this.option = Danmuku.option; this.states = { wait: [], ready: [], emit: [], stop: [] }; @@ -782,12 +921,7 @@ class Danmuku { } // 获取弹幕DOM节点 get $ref() { - const $ref = this.$refs.pop() || document.createElement("div"); - $ref.style.cssText = Danmuku.cssText; - $ref.dataset.mode = ""; - $ref.dataset.id = ""; - $ref.className = ""; - return $ref; + return this.renderer.acquire(); } // 获取准备好发送的弹幕 get readys() { @@ -795,30 +929,7 @@ class Danmuku { } // 可见的弹幕的数据,用于计算下一个弹幕的top值 get visibles() { - const result = []; - const { clientWidth } = this.$player; - const clientLeft = this.getLeft(this.$player); - this.filter("emit", (danmu) => { - const top = danmu.$ref.offsetTop; - const left = this.getLeft(danmu.$ref) - clientLeft; - const height = danmu.$ref.clientHeight; - const width = danmu.$ref.clientWidth; - const distance = left + width; - const right = clientWidth - distance; - const speed = distance / danmu.$restTime; - const emit = {}; - emit.top = top; - emit.left = left; - emit.height = height; - emit.width = width; - emit.right = right; - emit.speed = speed; - emit.distance = distance; - emit.time = danmu.$restTime; - emit.mode = danmu.mode; - result.push(emit); - }); - return result; + return this.renderer.visibles; } // 计算弹幕速度 get speed() { @@ -843,8 +954,7 @@ class Danmuku { return this; this.queue = []; this.states = { wait: [], ready: [], emit: [], stop: [] }; - this.$refs = []; - this.$danmuku.textContent = ""; + this.renderer.clear(); } for (let index = 0; index < danmus.length; index++) { if (!task.active()) @@ -961,8 +1071,7 @@ class Danmuku { } // 计算DOM的left值,受到旋屏影响 getLeft($ref) { - const rect = $ref.getBoundingClientRect(); - return this.isRotate ? rect.top : rect.left; + return this.renderer.left($ref); } // 复杂运算交给 Web Worker 处理 postMessage(message = {}) { @@ -982,16 +1091,7 @@ class Danmuku { } // 重置弹幕到wait状态,回收弹幕DOM节点 makeWait(danmu) { - this.setState(danmu, "wait"); - if (danmu.$ref) { - danmu.$ref.style.cssText = Danmuku.cssText; - danmu.$ref.style.visibility = "hidden"; - danmu.$ref.style.marginLeft = "0px"; - danmu.$ref.style.transform = "translateX(0px)"; - danmu.$ref.style.transition = "transform 0s linear 0s"; - this.$refs.push(danmu.$ref); - danmu.$ref = null; - } + this.renderer.makeWait(danmu); } // 实时更新弹幕 update() { @@ -1001,62 +1101,16 @@ class Danmuku { } // 重置正在显示的弹幕: stop/emit 状态的弹幕 resize() { - const { clientWidth } = this.$player; - this.filter("stop", (danmu) => { - switch (danmu.mode) { - // 滚动的弹幕 - case 0: - danmu.$ref.style.left = `${clientWidth}px`; - break; - } - }); - this.filter("emit", (danmu) => { - danmu.$lastStartTime = Date.now(); - switch (danmu.mode) { - // 滚动的弹幕 - case 0: { - const distance = clientWidth + danmu.$ref.clientWidth; - danmu.$ref.style.left = `${clientWidth}px`; - danmu.$ref.style.transform = `translateX(${-distance}px)`; - danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; - break; - } - } - }); + this.renderer.resize(); } // 继续弹幕 continue() { - const { clientWidth } = this.$player; - this.filter("stop", (danmu) => { - this.setState(danmu, "emit"); - danmu.$lastStartTime = Date.now(); - switch (danmu.mode) { - // 继续滚动的弹幕 - case 0: { - const distance = clientWidth + danmu.$ref.clientWidth; - danmu.$ref.style.transform = `translateX(${-distance}px)`; - danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; - break; - } - } - }); + this.renderer.continue(); return this; } // 暂停弹幕 suspend() { - const { clientWidth } = this.$player; - this.filter("emit", (danmu) => { - this.setState(danmu, "stop"); - switch (danmu.mode) { - // 停止滚动的弹幕 - case 0: { - const translateX = clientWidth - (this.getLeft(danmu.$ref) - this.getLeft(this.$player)); - danmu.$ref.style.transform = `translateX(${-translateX}px)`; - danmu.$ref.style.transition = "transform 0s linear 0s"; - break; - } - } - }); + this.renderer.suspend(); return this; } stop() { @@ -1120,6 +1174,7 @@ class Danmuku { this.workerClient?.dispose(); } } finally { + this.renderer.destroy(); this.art.off("video:play", this.start); this.art.off("video:playing", this.start); this.art.off("video:pause", this.stop); @@ -1131,164 +1186,529 @@ class Danmuku { } } } -const lib = { - map(value, inMin, inMax, outMin, outMax) { - return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; - }, - range(start, end, tick) { - const s = Math.round(start / tick) * tick; - return Array.from( - { - length: Math.floor((end - start) / tick) - }, - (v, k) => { - return k * tick + s; - } - ); +const MAX_HEATMAP_POINTS = 4294967295; +function upperBound(values, value) { + let start = 0; + let end = values.length; + while (start < end) { + const middle = Math.floor((start + end) / 2); + if (values[middle] <= value) + start = middle + 1; + else end = middle; } -}; + return start; +} +function sampleHeatmap(queue, width, duration, sampling) { + if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(duration) || duration <= 0 || !Number.isFinite(sampling) || sampling <= 0 || Math.floor(width / sampling) + 1 > MAX_HEATMAP_POINTS) { + return []; + } + const times = queue.map((item) => item.time).filter((time) => typeof time === "number" && !Number.isNaN(time)).sort((a, b) => a - b); + const points = []; + const gap = duration / width; + if (!Number.isFinite(gap)) + return []; + for (let x = 0; x <= width; x += sampling) { + if (points.length >= MAX_HEATMAP_POINTS || x + sampling === x) + return []; + const left = x * gap; + const right = (x + sampling) * gap; + points.push([x, upperBound(times, right) - upperBound(times, left)]); + } + return points; +} +function map(value, inMin, inMax, outMin, outMax) { + return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; +} function line(pointA, pointB) { const lengthX = pointB[0] - pointA[0]; const lengthY = pointB[1] - pointA[1]; - return { - length: Math.sqrt(lengthX ** 2 + lengthY ** 2), - angle: Math.atan2(lengthY, lengthX) - }; + return { length: Math.sqrt(lengthX ** 2 + lengthY ** 2), angle: Math.atan2(lengthY, lengthX) }; } +function heatmapGeometry({ width, height, duration, queue, option, points: input = [] }) { + if (![width, height, duration].every((value) => Number.isFinite(value) && value > 0)) + return null; + const defaults = { + xMin: 0, + xMax: width, + yMin: 0, + yMax: 128, + scale: 0.25, + opacity: 0.2, + minHeight: Math.floor(height * 0.05), + sampling: Math.max(1, Math.floor(width / 100)), + smoothing: 0.2, + flattening: 0.2 + }; + const options = Object.assign({}, defaults, typeof option === "object" ? option : {}); + for (const key of Object.keys(defaults)) { + if (!Number.isFinite(options[key])) + options[key] = defaults[key]; + } + if (options.sampling <= 0) + options.sampling = defaults.sampling; + if (options.xMin === options.xMax || options.yMin === options.yMax) + return null; + const points = Array.isArray(input) && input.length ? [...input] : sampleHeatmap(queue, width, duration, options.sampling); + if (!points.length || !points.every((point) => Array.isArray(point) && Number.isFinite(point[0]) && Number.isFinite(point[1]))) { + return null; + } + const lastPoint = points[points.length - 1]; + if (lastPoint[0] !== width) { + if (points.length === MAX_HEATMAP_POINTS) + return null; + points.push([width, lastPoint[1]]); + } + let yMin = Infinity; + let yMax = -Infinity; + for (const point of points) { + yMin = Math.min(yMin, point[1]); + yMax = Math.max(yMax, point[1]); + } + const yMid = (yMin + yMax) / 2; + for (const point of points) { + const y = point[1]; + point[1] = y * (y > yMid ? 1 + options.scale : 1 - options.scale) + options.minHeight; + } + const controlPoint = (current, previous, next, reverse) => { + const geometry = line(previous || current, next || current); + const flat = map(Math.cos(geometry.angle) * options.flattening, 0, 1, 1, 0); + const angle = geometry.angle * flat + (reverse ? Math.PI : 0); + const length = geometry.length * options.smoothing; + return [current[0] + Math.cos(angle) * length, current[1] + Math.sin(angle) * length]; + }; + const positions = points.map((point) => [map(point[0], options.xMin, options.xMax, 0, width), map(point[1], options.yMin, options.yMax, height, 0)]); + if (!positions.every((point) => point.every(Number.isFinite))) + return null; + const path = positions.map((point, index, all) => { + if (index === 0) + return `M ${all[all.length - 1][0]},${height} L ${point[0]},${height} L ${point[0]},${point[1]}`; + const start = controlPoint(all[index - 1], all[index - 2], point); + const end = controlPoint(point, all[index - 1], all[index + 1], true); + return `C ${start[0]},${start[1]} ${end[0]},${end[1]} ${point[0]},${point[1]}${index === all.length - 1 ? " z" : ""}`; + }).join(" "); + if (/NaN|Infinity/u.test(path)) + return null; + return { path, opacity: options.opacity, width, height }; +} +let nextGradient = 0; function heatmap(art, danmuku, option) { + if (art.isDestroy) + return; const { query } = art.constructor.utils; - art.controls.add({ - name: "heatmap", - position: "top", - html: "", - style: { - position: "absolute", - top: "-100px", - left: "0px", - right: "0px", - height: "100px", - width: "100%", - pointerEvents: "none" - }, - mounted($heatmap) { - let $start = null; - let $stop = null; - function update(arg = []) { - $start = null; - $stop = null; - $heatmap.innerHTML = ""; - if (!art.duration || art.option.isLive) + let gradient; + const subscriptions = []; + let element; + let start; + let stop; + let closed = false; + const active = () => !closed && !art.isDestroy; + function listen(name, callback) { + subscriptions.push([name, callback]); + art.on(name, callback); + } + function dispose(removeControl) { + if (closed) + return; + closed = true; + start = null; + stop = null; + let failure; + let failed = false; + const attempt = (callback) => { + try { + callback(); + } catch (error) { + if (!failed) + failure = error; + failed = true; + } + }; + for (const [name, callback] of subscriptions.splice(0)) + attempt(() => art.off(name, callback)); + if (removeControl && element && art.controls.heatmap === element) + attempt(() => art.controls.remove("heatmap")); + element = void 0; + if (failed) + throw failure; + } + function progress(value) { + if (active() && start && stop) { + start.setAttribute("offset", `${value * 100}%`); + stop.setAttribute("offset", `${value * 100}%`); + } + } + function draw(points = []) { + if (!active()) + return; + start = null; + stop = null; + element.innerHTML = ""; + if (art.option.isLive) + return; + const shape = heatmapGeometry({ width: element.offsetWidth, height: element.offsetHeight, duration: art.duration, queue: danmuku.queue, option, points }); + if (!shape || !active()) + return; + const document2 = element.ownerDocument; + if (!gradient) + gradient = document2 ? "heatmap-solids" : `heatmap-solids-${++nextGradient}`; + while (document2?.getElementById(gradient)) + gradient = `heatmap-solids-${++nextGradient}`; + element.innerHTML = ` + + + + + + + + + + + + `; + start = query("#heatmap-start", element); + stop = query("#heatmap-stop", element); + progress(art.played); + } + function update(points) { + try { + draw(points); + } catch (error) { + try { + dispose(true); + } catch { + } + throw error; + } + } + listen("destroy", () => dispose(true)); + try { + art.controls.add({ + name: "heatmap", + position: "top", + html: "", + style: { + position: "absolute", + top: "-100px", + left: "0px", + right: "0px", + height: "100px", + width: "100%", + pointerEvents: "none" + }, + beforeUnmount() { + dispose(false); + }, + mounted(value) { + element = value; + if (closed) { + if (art.controls.heatmap === value) + art.controls.remove("heatmap"); + element = void 0; return; - const svg = { - w: $heatmap.offsetWidth, - h: $heatmap.offsetHeight - }; - const options = { - xMin: 0, - xMax: svg.w, - yMin: 0, - yMax: 128, - scale: 0.25, - opacity: 0.2, - minHeight: Math.floor(svg.h * 0.05), - sampling: Math.floor(svg.w / 100), - smoothing: 0.2, - flattening: 0.2 - }; - if (typeof option === "object") { - Object.assign(options, option); } - let points = []; - if (Array.isArray(arg) && arg.length) { - points = [...arg]; - } else { - const gap = art.duration / svg.w; - for (let x = 0; x <= svg.w; x += options.sampling) { - const y = danmuku.queue.filter( - ({ time }) => time > x * gap && time <= (x + options.sampling) * gap - ).length; - points.push([x, y]); + if (!active()) { + dispose(true); + return; + } + listen("video:timeupdate", () => progress(art.played)); + listen("setBar", (type, percentage) => { + if (type === "played") + progress(percentage); + }); + listen("ready", () => update()); + listen("resize", () => update()); + listen("artplayerPluginDanmuku:loaded", () => update()); + listen("artplayerPluginDanmuku:points", (points) => update(points)); + } + }); + } catch (error) { + try { + dispose(true); + } catch { + } + throw error; + } + return () => dispose(true); +} +const properties = /* @__PURE__ */ new WeakMap(); +class SettingLifecycle { + constructor(art) { + this.art = art; + this.closed = false; + this.disposers = /* @__PURE__ */ new Set(); + this.cancelled = new Promise((resolve) => this.cancel = resolve); + } + get active() { + return !this.closed && !this.art.isDestroy; + } + own(dispose) { + if (this.closed) + dispose(); + else this.disposers.add(dispose); + } + write(target, key, value, apply = (value2) => target[key] = value2) { + if (!this.active) + return; + const priority = () => typeof target.getPropertyPriority === "function" ? target.getPropertyPriority(key) : void 0; + let keys = properties.get(target); + if (!keys) { + keys = /* @__PURE__ */ new Map(); + properties.set(target, keys); + } + let entry = keys.get(key); + if (!entry) { + entry = { + original: target[key], + present: key in target || target[key] !== void 0, + priority: priority(), + owners: /* @__PURE__ */ new Map(), + apply + }; + keys.set(key, entry); + } + if (!entry.owners.has(this)) { + this.own(() => { + const owners = [...entry.owners.keys()]; + const current = owners[owners.length - 1] === this; + entry.owners.delete(this); + if (current && target[key] === entry.last && priority() === entry.lastPriority) { + const values = [...entry.owners.values()]; + if (values.length) { + entry.apply(values[values.length - 1]); + entry.last = target[key]; + entry.lastPriority = priority(); + } else if (entry.priority !== void 0) { + target.setProperty(key, entry.original, entry.priority); + } else if (entry.present) { + entry.apply(entry.original); + } else { + delete target[key]; } } - if (points.length === 0) - return; - const lastPoint = points[points.length - 1]; - const lastX = lastPoint[0]; - const lastY = lastPoint[1]; - if (lastX !== svg.w) { - points.push([svg.w, lastY]); + if (!entry.owners.size) { + keys.delete(key); + if (!keys.size) + properties.delete(target); } - const yPoints = points.map((point) => point[1]); - const yMin = Math.min(...yPoints); - const yMax = Math.max(...yPoints); - const yMid = (yMin + yMax) / 2; - for (let i = 0; i < points.length; i++) { - const point = points[i]; - const y = point[1]; - point[1] = y * (y > yMid ? 1 + options.scale : 1 - options.scale) + options.minHeight; - } - const controlPoint = (current, previous, next, reverse) => { - const p = previous || current; - const n = next || current; - const o = line(p, n); - const flat = lib.map(Math.cos(o.angle) * options.flattening, 0, 1, 1, 0); - const angle = o.angle * flat + (reverse ? Math.PI : 0); - const length = o.length * options.smoothing; - const x = current[0] + Math.cos(angle) * length; - const y = current[1] + Math.sin(angle) * length; - return [x, y]; - }; - const bezierCommand = (point, i, a) => { - const cps = controlPoint(a[i - 1], a[i - 2], point); - const cpe = controlPoint(point, a[i - 1], a[i + 1], true); - const close = i === a.length - 1 ? " z" : ""; - return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}${close}`; - }; - const pointsPositions = points.map((e) => { - const x = lib.map(e[0], options.xMin, options.xMax, 0, svg.w); - const y = lib.map(e[1], options.yMin, options.yMax, svg.h, 0); - return [x, y]; - }); - const pathD = pointsPositions.reduce( - (acc, e, i, a) => i === 0 ? `M ${a[a.length - 1][0]},${svg.h} L ${e[0]},${svg.h} L ${e[0]},${e[1]}` : `${acc} ${bezierCommand(e, i, a)}`, - "" - ); - $heatmap.innerHTML = ` - - - - - - - - - - - - `; - $start = query("#heatmap-start", $heatmap); - $stop = query("#heatmap-stop", $heatmap); - $start.setAttribute("offset", `${art.played * 100}%`); - $stop.setAttribute("offset", `${art.played * 100}%`); + }); + } + entry.owners.delete(this); + entry.owners.set(this, value); + entry.apply(value); + entry.last = target[key]; + entry.lastPriority = priority(); + } + on(name, callback) { + if (!this.active) + return; + const lifecycle = this; + function listener(...args) { + if (lifecycle.active) + return callback.apply(this, args); + } + const dispose = () => this.art.off(name, listener); + this.own(dispose); + try { + this.art.on(name, listener); + if (!this.active) + dispose(); + } catch (error) { + this.release(dispose); + throw error; + } + } + proxy(target, name, callback) { + if (!this.active) + return; + const lifecycle = this; + function listener(...args) { + if (lifecycle.active) + return callback.apply(this, args); + } + let remove; + const dispose = () => { + if (typeof remove === "function") { + const registry = this.art.events; + if (typeof registry?.remove === "function" && registry.destroyEvents?.has(remove)) + registry.remove(remove); + else remove(); + } else { + target.removeEventListener?.(name, listener); } - art.on("video:timeupdate", () => { - if ($start && $stop) { - $start.setAttribute("offset", `${art.played * 100}%`); - $stop.setAttribute("offset", `${art.played * 100}%`); - } - }); - art.on("setBar", (type, percentage) => { - if ($start && $stop && type === "played") { - $start.setAttribute("offset", `${percentage * 100}%`); - $stop.setAttribute("offset", `${percentage * 100}%`); - } - }); - art.on("ready", () => update()); - art.on("resize", () => update()); - art.on("artplayerPluginDanmuku:loaded", () => update()); - art.on("artplayerPluginDanmuku:points", (points) => update(points)); + }; + this.own(dispose); + try { + remove = this.art.proxy(target, name, listener); + if (!this.active) + dispose(); + } catch (error) { + this.release(dispose); + throw error; + } + } + wait(value) { + return Promise.race([value, this.cancelled]); + } + release(dispose) { + try { + dispose(); + } catch (error) { + console.warn("Failed to dispose danmuku setting resource:", error); + } + } + close() { + if (this.closed) + return; + this.closed = true; + this.cancel(); + for (const dispose of this.disposers) { + this.release(dispose); + } + this.disposers.clear(); + } +} +async function emitSetting(setting) { + const { lifecycle, template: { $input } } = setting; + if (!lifecycle.active) + return; + const text = $input.value.trim(); + if (!text.length || setting.isLock || setting.emitting) + return; + const danmu = { + text, + mode: setting.option.mode, + color: setting.option.color, + time: setting.art.currentTime + }; + const report = (error) => { + if (lifecycle.active) + console.error("Error emitting danmuku:", error); + }; + try { + setting.emitting = true; + const state = await lifecycle.wait(setting.option.beforeEmit(danmu)); + if (!lifecycle.active) + return; + setting.emitting = false; + if (state !== true) + return; + danmu.border = true; + delete danmu.time; + Promise.resolve(setting.danmuku.emit(danmu)).catch(report); + if (!lifecycle.active) + return; + $input.value = ""; + setting.lock(); + } catch (error) { + report(error); + if (lifecycle.active) + setting.emitting = false; + } +} +function lockSetting(setting) { + if (!setting.lifecycle.active) + return; + const { addClass } = setting.utils; + const { $send } = setting.template; + clearTimeout(setting.timer); + setting.isLock = true; + let time = setting.option.lockTime; + $send.textContent = time; + addClass($send, "apd-lock"); + const loop = () => { + setting.timer = setTimeout(() => { + if (!setting.lifecycle.active) + return; + if (time === 0) { + setting.unlock(); + } else { + time -= 1; + $send.textContent = time; + loop(); + } + }, 1e3); + }; + loop(); +} +function unlockSetting(setting) { + clearTimeout(setting.timer); + setting.timer = null; + setting.isLock = false; + if (!setting.lifecycle.active) + return; + const { removeClass } = setting.utils; + const { $send } = setting.template; + $send.textContent = "发送"; + removeClass($send, "apd-lock"); +} +function createSlider({ min, max, container, findIndex, onChange, steps = [] }) { + const { lifecycle } = this; + if (!lifecycle.active) + return { reset() { + } }; + const { query, clamp, setStyle } = this.utils; + setStyle(container, "touch-action", "none"); + container.innerHTML = ` +
+
+ ${steps.map(() => `
`).join("")} +
+
+
+
+
+ ${steps.map((step) => step.hide ? "" : `
${step.name}
`).join("")} +
+ `; + const $dot = query(".apd-slider-dot", container); + const $progress = query(".apd-slider-progress", container); + let isDroging = false; + lifecycle.own(() => isDroging = false); + function reset(index) { + if (!lifecycle.active) + return; + if (index === void 0) + index = findIndex(); + if (index < min || index > max) + return; + const percentage = (index - min) / (max - min); + $dot.style.left = `${percentage * 100}%`; + if (steps.length === 0) { + $progress.style.width = $dot.style.left; + } + onChange(index); + } + function updateLeft(event) { + const { top, height, left, width } = container.getBoundingClientRect(); + if (this.art.isRotate) { + const value = clamp(event.clientY - top, 0, height); + const index = Math.round(value / height * (max - min) + min); + reset(index); + } else { + const value = clamp(event.clientX - left, 0, width); + const index = Math.round(value / width * (max - min) + min); + reset(index); + } + } + lifecycle.proxy(container, "click", (event) => { + updateLeft.call(this, event); + }); + lifecycle.proxy(container, "pointerdown", (event) => { + isDroging = event.button === 0; + }); + lifecycle.on("document:pointermove", (event) => { + if (isDroging) { + updateLeft.call(this, event); } }); + lifecycle.on("document:pointerup", (event) => { + if (isDroging) { + isDroging = false; + updateLeft.call(this, event); + } + }); + return { reset }; } const $check_off = ''; const $check_on = ''; @@ -1302,92 +1722,25 @@ const $mode_2_on = ''; const $on = ''; const $style = ''; -const style = ".artplayer-plugin-danmuku {\n display: flex;\n position: relative;\n z-index: 99;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n height: 32px;\n width: 100%;\n color: #fff;\n font-weight: 300;\n flex-shrink: 0;\n gap: 10px;\n}\n.artplayer-plugin-danmuku .apd-icon {\n cursor: pointer;\n opacity: 0.75;\n transition: all 0.2s ease;\n fill: #fff;\n}\n.artplayer-plugin-danmuku .apd-icon:hover {\n opacity: 1;\n}\n.artplayer-plugin-danmuku .apd-config {\n display: flex;\n position: relative;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 320px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-config-mode,\n.artplayer-plugin-danmuku .apd-config-slider,\n.artplayer-plugin-danmuku .apd-config-other,\n.artplayer-plugin-danmuku .apd-style-mode {\n margin-bottom: 15px;\n}\n.artplayer-plugin-danmuku .apd-modes {\n display: flex;\n align-items: center;\n margin-top: 5px;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode {\n cursor: pointer;\n text-align: center;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-config-slider {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.artplayer-plugin-danmuku .apd-config-slider .apd-value {\n width: 32px;\n text-align: right;\n}\n.artplayer-plugin-danmuku .apd-slider {\n position: relative;\n flex: 1;\n display: flex;\n height: 20px;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-line {\n position: relative;\n height: 2px;\n width: 100%;\n overflow: hidden;\n border-radius: 3px;\n background-color: rgba(255, 255, 255, 0.25);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point {\n width: 2px;\n height: 2px;\n border-radius: 50%;\n background-color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-progress {\n width: 0%;\n height: 100%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-dot {\n position: absolute;\n transform: translateX(-6px);\n left: 0%;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps {\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: absolute;\n bottom: -12px;\n width: calc(100% + 32px);\n color: #777;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step {\n flex-shrink: 0;\n width: 36px;\n text-align: center;\n scale: 0.95;\n}\n.artplayer-plugin-danmuku .apd-config-other {\n display: flex;\n align-items: center;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-check-off,\n.artplayer-plugin-danmuku .apd-config-other .apd-check-on {\n width: 16px;\n height: 16px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other {\n display: flex;\n align-items: center;\n cursor: pointer;\n gap: 2px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-emitter {\n display: flex;\n flex: 1;\n align-items: center;\n height: 100%;\n background-color: rgba(255, 255, 255, 0.25);\n border-radius: 5px;\n}\n.artplayer-plugin-danmuku .apd-style {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 200px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-colors {\n display: flex;\n flex-wrap: wrap;\n margin-top: 5px;\n gap: 8px;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color {\n width: 16px;\n height: 16px;\n border-radius: 2px;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active {\n border: 1px solid black;\n box-shadow: 0 0 0 1px #fff;\n}\n.artplayer-plugin-danmuku .apd-input {\n outline: none;\n height: 100%;\n flex: 1;\n min-width: 0;\n width: auto;\n border: none;\n line-height: 1;\n color: #fff;\n background-color: transparent;\n}\n.artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-send {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n width: 60px;\n flex-shrink: 0;\n cursor: pointer;\n text-shadow: none;\n border-top-right-radius: 5px;\n border-bottom-right-radius: 5px;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-send.apd-lock {\n cursor: not-allowed;\n color: #666;\n background-color: #e7e7e7;\n}\n.art-controls-center .apd-emitter {\n width: 260px;\n flex: none;\n}\n.art-fullscreen .artplayer-plugin-danmuku,\n.art-fullscreen-web .artplayer-plugin-danmuku {\n height: 38px;\n gap: 16px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on {\n width: 28px;\n height: 28px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter {\n width: 400px;\n flex: none;\n}\n.art-video-player > .artplayer-plugin-danmuku {\n position: absolute;\n left: 0;\n right: 0;\n bottom: -40px;\n padding: 0 10px;\n}\n.art-video-player:has(> .artplayer-plugin-danmuku) {\n margin-bottom: 40px;\n}\n[data-danmuku-emitter='false'] .apd-emitter {\n display: none !important;\n}\n[data-danmuku-emitter='false'] .art-controls-center .artplayer-plugin-danmuku {\n justify-content: flex-end;\n gap: 18px;\n}\n[data-danmuku-emitter='false'].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,\n[data-danmuku-emitter='false'].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku {\n gap: 24px;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-icon {\n fill: #333;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-emitter {\n background-color: #f1f2f3;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input {\n color: #000;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(0, 0, 0, 0.3);\n}\n[data-danmuku-visible='false'] .apd-toggle-off {\n display: block;\n}\n[data-danmuku-visible='false'] .apd-toggle-on {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-off {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-on {\n display: block;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-on {\n display: none;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-off {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-on {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-off {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-on {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-off {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-on {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-off {\n display: none;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-off {\n display: block;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-on {\n display: none;\n}\n[data-danmuku-mode0='false'] .art-danmuku [data-mode='0'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-off {\n display: none;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-on {\n display: block;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-off {\n display: block;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-on {\n display: none;\n}\n[data-danmuku-mode1='false'] .art-danmuku [data-mode='1'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-off {\n display: none;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-on {\n display: block;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-off {\n display: block;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-on {\n display: none;\n}\n[data-danmuku-mode2='false'] .art-danmuku [data-mode='2'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-off {\n display: none;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-on {\n display: block;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] path {\n fill: #00a1d6;\n}\n"; -class Setting { - constructor(art, danmuku) { - this.art = art; - this.danmuku = danmuku; - this.utils = art.constructor.utils; - const { setStyle } = this.utils; - const { $controlsCenter } = art.template; - setStyle($controlsCenter, "display", "flex"); - this.template = { - $controlsCenter, - $mount: $controlsCenter, - $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 = false; - this.isLock = false; - this.timer = null; - this.createTemplate(); - this.createSliders(); - this.createEvents(); - this.mount(this.option.mount); - art.on("resize", () => this.resize()); - art.on("fullscreen", (state) => this.onFullscreen(state)); - art.on("fullscreenWeb", (state) => this.onFullscreen(state)); - art.proxy(this.template.$config, "mouseenter", () => { - this.onMouseEnter({ - $control: this.template.$config, - $panel: this.template.$configPanel - }); - }); - art.proxy(this.template.$style, "mouseenter", () => { - this.onMouseEnter({ - $control: this.template.$style, - $panel: this.template.$stylePanel - }); - }); - } - static get icons() { - return { - $on, - $off, - $config, - $style, - $mode_0_off, - $mode_0_on, - $mode_1_off, - $mode_1_on, - $mode_2_off, - $mode_2_on, - $check_on, - $check_off - }; - } - get option() { - return this.danmuku.option; - } - get outside() { - return this.template.$mount !== this.template.$controlsCenter; - } - get TEMPLATE() { - const { option } = this; - return ` +function settingIcons() { + return { + $on, + $off, + $config, + $style, + $mode_0_off, + $mode_0_on, + $mode_1_off, + $mode_1_on, + $mode_2_off, + $mode_2_on, + $check_on, + $check_off + }; +} +function settingTemplate() { + const { option } = this; + return `
${$on}${$off}
@@ -1480,6 +1833,126 @@ class Setting {
发送
`; +} +const style = ".artplayer-plugin-danmuku {\n display: flex;\n position: relative;\n z-index: 99;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n height: 32px;\n width: 100%;\n color: #fff;\n font-weight: 300;\n flex-shrink: 0;\n gap: 10px;\n}\n.artplayer-plugin-danmuku .apd-icon {\n cursor: pointer;\n opacity: 0.75;\n transition: all 0.2s ease;\n fill: #fff;\n}\n.artplayer-plugin-danmuku .apd-icon:hover {\n opacity: 1;\n}\n.artplayer-plugin-danmuku .apd-config {\n display: flex;\n position: relative;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 320px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-config-mode,\n.artplayer-plugin-danmuku .apd-config-slider,\n.artplayer-plugin-danmuku .apd-config-other,\n.artplayer-plugin-danmuku .apd-style-mode {\n margin-bottom: 15px;\n}\n.artplayer-plugin-danmuku .apd-modes {\n display: flex;\n align-items: center;\n margin-top: 5px;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode {\n cursor: pointer;\n text-align: center;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-config-slider {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.artplayer-plugin-danmuku .apd-config-slider .apd-value {\n width: 32px;\n text-align: right;\n}\n.artplayer-plugin-danmuku .apd-slider {\n position: relative;\n flex: 1;\n display: flex;\n height: 20px;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-line {\n position: relative;\n height: 2px;\n width: 100%;\n overflow: hidden;\n border-radius: 3px;\n background-color: rgba(255, 255, 255, 0.25);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point {\n width: 2px;\n height: 2px;\n border-radius: 50%;\n background-color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-progress {\n width: 0%;\n height: 100%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-dot {\n position: absolute;\n transform: translateX(-6px);\n left: 0%;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps {\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: absolute;\n bottom: -12px;\n width: calc(100% + 32px);\n color: #777;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step {\n flex-shrink: 0;\n width: 36px;\n text-align: center;\n scale: 0.95;\n}\n.artplayer-plugin-danmuku .apd-config-other {\n display: flex;\n align-items: center;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-check-off,\n.artplayer-plugin-danmuku .apd-config-other .apd-check-on {\n width: 16px;\n height: 16px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other {\n display: flex;\n align-items: center;\n cursor: pointer;\n gap: 2px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-emitter {\n display: flex;\n flex: 1;\n align-items: center;\n height: 100%;\n background-color: rgba(255, 255, 255, 0.25);\n border-radius: 5px;\n}\n.artplayer-plugin-danmuku .apd-style {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 200px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-colors {\n display: flex;\n flex-wrap: wrap;\n margin-top: 5px;\n gap: 8px;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color {\n width: 16px;\n height: 16px;\n border-radius: 2px;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active {\n border: 1px solid black;\n box-shadow: 0 0 0 1px #fff;\n}\n.artplayer-plugin-danmuku .apd-input {\n outline: none;\n height: 100%;\n flex: 1;\n min-width: 0;\n width: auto;\n border: none;\n line-height: 1;\n color: #fff;\n background-color: transparent;\n}\n.artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-send {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n width: 60px;\n flex-shrink: 0;\n cursor: pointer;\n text-shadow: none;\n border-top-right-radius: 5px;\n border-bottom-right-radius: 5px;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-send.apd-lock {\n cursor: not-allowed;\n color: #666;\n background-color: #e7e7e7;\n}\n.art-controls-center .apd-emitter {\n width: 260px;\n flex: none;\n}\n.art-fullscreen .artplayer-plugin-danmuku,\n.art-fullscreen-web .artplayer-plugin-danmuku {\n height: 38px;\n gap: 16px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on {\n width: 28px;\n height: 28px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter {\n width: 400px;\n flex: none;\n}\n.art-video-player > .artplayer-plugin-danmuku {\n position: absolute;\n left: 0;\n right: 0;\n bottom: -40px;\n padding: 0 10px;\n}\n.art-video-player:has(> .artplayer-plugin-danmuku) {\n margin-bottom: 40px;\n}\n[data-danmuku-emitter='false'] .apd-emitter {\n display: none !important;\n}\n[data-danmuku-emitter='false'] .art-controls-center .artplayer-plugin-danmuku {\n justify-content: flex-end;\n gap: 18px;\n}\n[data-danmuku-emitter='false'].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,\n[data-danmuku-emitter='false'].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku {\n gap: 24px;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-icon {\n fill: #333;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-emitter {\n background-color: #f1f2f3;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input {\n color: #000;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(0, 0, 0, 0.3);\n}\n[data-danmuku-visible='false'] .apd-toggle-off {\n display: block;\n}\n[data-danmuku-visible='false'] .apd-toggle-on {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-off {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-on {\n display: block;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-on {\n display: none;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-off {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-on {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-off {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-on {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-off {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-on {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-off {\n display: none;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-off {\n display: block;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-on {\n display: none;\n}\n[data-danmuku-mode0='false'] .art-danmuku [data-mode='0'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-off {\n display: none;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-on {\n display: block;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-off {\n display: block;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-on {\n display: none;\n}\n[data-danmuku-mode1='false'] .art-danmuku [data-mode='1'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-off {\n display: none;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-on {\n display: block;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-off {\n display: block;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-on {\n display: none;\n}\n[data-danmuku-mode2='false'] .art-danmuku [data-mode='2'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-off {\n display: none;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-on {\n display: block;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] path {\n fill: #00a1d6;\n}\n"; +if (typeof document !== "undefined") { + const id = "artplayer-plugin-danmuku"; + const pending = /* @__PURE__ */ Symbol.for("artplayer-plugin-danmuku.pending-style"); + let $style2 = document.getElementById(id) || document[pending]; + if (!$style2) { + $style2 = document.createElement("style"); + $style2.id = id; + if (document.readyState === "loading") { + document[pending] = $style2; + const ready = () => { + document.removeEventListener("DOMContentLoaded", ready); + if (document[pending] === $style2) + delete document[pending]; + const existing = document.getElementById(id); + if (existing) + existing.textContent = $style2.textContent; + else + (document.head || document.documentElement).appendChild($style2); + }; + document.addEventListener("DOMContentLoaded", ready, { once: true }); + } else { + (document.head || document.documentElement).appendChild($style2); + } + } + $style2.textContent = style; +} +class Setting { + constructor(art, danmuku) { + this.art = art; + this.danmuku = danmuku; + this.utils = art.constructor.utils; + const { setStyle } = this.utils; + const { $controlsCenter } = art.template; + this.template = { + $controlsCenter, + $mount: $controlsCenter, + $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 = false; + this.isLock = false; + this.timer = null; + this.lifecycle = new SettingLifecycle(art); + this.destroy = this.destroy.bind(this); + this.lifecycle.own(() => art.off("destroy", this.destroy)); + try { + art.on("destroy", this.destroy); + if (!this.lifecycle.active) + return; + this.lifecycle.write($controlsCenter.style, "display", "flex", (value) => setStyle($controlsCenter, "display", value)); + this.createTemplate(); + if (!this.lifecycle.active) + return; + this.createSliders(); + if (!this.lifecycle.active) + return; + this.createEvents(); + this.mount(this.option.mount); + if (!this.lifecycle.active) + return; + this.lifecycle.on("resize", () => this.resize()); + this.lifecycle.on("fullscreen", (state) => this.onFullscreen(state)); + this.lifecycle.on("fullscreenWeb", (state) => this.onFullscreen(state)); + this.lifecycle.proxy(this.template.$config, "mouseenter", () => { + this.onMouseEnter({ + $control: this.template.$config, + $panel: this.template.$configPanel + }); + }); + this.lifecycle.proxy(this.template.$style, "mouseenter", () => { + this.onMouseEnter({ + $control: this.template.$style, + $panel: this.template.$stylePanel + }); + }); + } catch (error) { + try { + this.destroy(); + } catch (cleanupError) { + console.warn("Failed to roll back danmuku setting:", cleanupError); + } + throw error; + } + } + static get icons() { + return settingIcons(); + } + get option() { + return this.danmuku.option; + } + get outside() { + return this.template.$mount !== this.template.$controlsCenter; + } + get TEMPLATE() { + return settingTemplate.call(this); } get OPACITY() { return { @@ -1577,6 +2050,8 @@ class Setting { return query(selector, $danmuku); } append(el, target) { + if (!this.lifecycle.active) + return; const { append } = this.utils; const children = [...el.children]; if (children.includes(target)) @@ -1584,19 +2059,21 @@ class Setting { append(el, target); } setData(key, value) { + if (!this.lifecycle.active) + return; const { $player } = this.art.template; const { $mount } = this.template; - $player.dataset[key] = value; + this.lifecycle.write($player.dataset, key, value); if (this.outside) { - $mount.dataset[key] = value; + this.lifecycle.write($mount.dataset, key, value); } } createTemplate() { const { createElement, tooltip } = this.utils; const $danmuku = createElement("div"); + this.template.$danmuku = $danmuku; $danmuku.className = "artplayer-plugin-danmuku"; $danmuku.innerHTML = this.TEMPLATE; - this.template.$danmuku = $danmuku; this.template.$toggle = this.query(".apd-toggle"); this.template.$config = this.query(".apd-config"); this.template.$configPanel = this.query(".apd-config-panel"); @@ -1618,22 +2095,22 @@ class Setting { this.template.$input = this.query(".apd-input"); this.template.$send = this.query(".apd-send"); const { $toggle } = this.template; - this.art.on("artplayerPluginDanmuku:show", () => { + this.lifecycle.on("artplayerPluginDanmuku:show", () => { tooltip($toggle, "关闭弹幕"); }); - this.art.on("artplayerPluginDanmuku:hide", () => { + this.lifecycle.on("artplayerPluginDanmuku:hide", () => { tooltip($toggle, "打开弹幕"); }); } createEvents() { const { $toggle, $configModes, $styleModes, $colors, $antiOverlap, $syncVideo, $send, $input } = this.template; - this.art.proxy($toggle, "click", () => { + this.lifecycle.proxy($toggle, "click", () => { this.danmuku.config({ visible: !this.option.visible }); this.reset(); }); - this.art.proxy($configModes, "click", (event) => { + this.lifecycle.proxy($configModes, "click", (event) => { const $mode = event.target.closest(".apd-mode"); if (!$mode) return; @@ -1649,19 +2126,19 @@ class Setting { } this.reset(); }); - this.art.proxy($antiOverlap, "click", () => { + this.lifecycle.proxy($antiOverlap, "click", () => { this.danmuku.config({ antiOverlap: !this.option.antiOverlap }); this.reset(); }); - this.art.proxy($syncVideo, "click", () => { + this.lifecycle.proxy($syncVideo, "click", () => { this.danmuku.config({ synchronousPlayback: !this.option.synchronousPlayback }); this.reset(); }); - this.art.proxy($styleModes, "click", (event) => { + this.lifecycle.proxy($styleModes, "click", (event) => { const $mode = event.target.closest(".apd-mode"); if (!$mode) return; @@ -1671,7 +2148,7 @@ class Setting { }); this.reset(); }); - this.art.proxy($colors, "click", (event) => { + this.lifecycle.proxy($colors, "click", (event) => { const $color = event.target.closest(".apd-color"); if (!$color) return; @@ -1680,8 +2157,8 @@ class Setting { }); this.reset(); }); - this.art.proxy($send, "click", () => this.emit()); - this.art.proxy($input, "keypress", (event) => { + this.lifecycle.proxy($send, "click", () => this.emit()); + this.lifecycle.proxy($input, "keypress", (event) => { if (event.key === "Enter") { event.preventDefault(); this.emit(); @@ -1756,66 +2233,12 @@ class Setting { } }); } - createSlider({ min, max, container, findIndex, onChange, steps = [] }) { - const { query, clamp, setStyle } = this.utils; - setStyle(container, "touch-action", "none"); - container.innerHTML = ` -
-
- ${steps.map(() => `
`).join("")} -
-
-
-
-
- ${steps.map((step) => step.hide ? "" : `
${step.name}
`).join("")} -
- `; - const $dot = query(".apd-slider-dot", container); - const $progress = query(".apd-slider-progress", container); - let isDroging = false; - function reset(index = findIndex()) { - if (index < min || index > max) - return; - const percentage = (index - min) / (max - min); - $dot.style.left = `${percentage * 100}%`; - if (steps.length === 0) { - $progress.style.width = $dot.style.left; - } - onChange(index); - } - function updateLeft(event) { - const { top, height, left, width } = container.getBoundingClientRect(); - if (this.art.isRotate) { - const value = clamp(event.clientY - top, 0, height); - const index = Math.round(value / height * (max - min) + min); - reset(index); - } else { - const value = clamp(event.clientX - left, 0, width); - const index = Math.round(value / width * (max - min) + min); - reset(index); - } - } - this.art.proxy(container, "click", (event) => { - updateLeft.call(this, event); - }); - this.art.proxy(container, "pointerdown", (event) => { - isDroging = event.button === 0; - }); - this.art.on("document:pointermove", (event) => { - if (isDroging) { - updateLeft.call(this, event); - } - }); - this.art.on("document:pointerup", (event) => { - if (isDroging) { - isDroging = false; - updateLeft.call(this, event); - } - }); - return { reset }; + createSlider(option) { + return createSlider.call(this, option); } onFullscreen(state) { + if (!this.lifecycle.active) + return; const { $danmuku, $controlsCenter, $mount } = this.template; if (this.outside) { if (state) { @@ -1828,6 +2251,8 @@ class Setting { } } onMouseEnter({ $control, $panel }) { + if (!this.lifecycle.active) + return; const { $player } = this.art.template; const controlRect = $control.getBoundingClientRect(); const panelRect = $panel.getBoundingClientRect(); @@ -1843,66 +2268,18 @@ class Setting { $panel.style.left = `${-half}px`; } } - async emit() { - const { $input } = this.template; - const text = $input.value.trim(); - if (!text.length) - return; - if (this.isLock) - return; - if (this.emitting) - return; - const danmu = { - text, - mode: this.option.mode, - color: this.option.color, - time: this.art.currentTime - }; - try { - this.emitting = true; - const state = await this.option.beforeEmit(danmu); - this.emitting = false; - if (state !== true) - return; - danmu.border = true; - delete danmu.time; - this.danmuku.emit(danmu); - $input.value = ""; - this.lock(); - } catch (error) { - console.error("Error emitting danmuku:", error); - this.emitting = false; - } + emit() { + return emitSetting(this); } lock() { - const { addClass } = this.utils; - const { $send } = this.template; - this.isLock = true; - let time = this.option.lockTime; - $send.textContent = time; - addClass($send, "apd-lock"); - const loop = () => { - this.timer = setTimeout(() => { - if (time === 0) { - this.unlock(); - } else { - time -= 1; - $send.textContent = time; - loop(); - } - }, 1e3); - }; - loop(); + lockSetting(this); } unlock() { - const { removeClass } = this.utils; - const { $send } = this.template; - clearTimeout(this.timer); - this.isLock = false; - $send.textContent = "发送"; - removeClass($send, "apd-lock"); + unlockSetting(this); } resize() { + if (!this.lifecycle.active) + return; if (this.outside) return; if (this.art.fullscreen) @@ -1918,12 +2295,15 @@ class Setting { } } reset() { + if (!this.lifecycle.active) + return; const { inverseClass, tooltip } = this.utils; const { $toggle, $colors } = this.template; - this.slider.opacity.reset(); - this.slider.margin.reset(); - this.slider.fontSize.reset(); - this.slider.speed.reset(); + for (const slider of Object.values(this.slider)) { + slider.reset(); + if (!this.lifecycle.active) + return; + } this.setData("danmukuVisible", this.option.visible); this.setData("danmukuMode", this.option.mode); this.setData("danmukuColor", this.option.color); @@ -1941,6 +2321,8 @@ class Setting { this.resize(); } mount(target) { + if (!this.lifecycle.active) + return; const { errorHandle } = this.utils; const $el = typeof target === "string" ? document.querySelector(target) : target; errorHandle($el, `Can not find the mount point: ${target}`); @@ -1948,29 +2330,38 @@ class Setting { this.template.$mount = $el; this.reset(); } -} -if (typeof document !== "undefined") { - const id = "artplayer-plugin-danmuku"; - let $style2 = document.getElementById(id); - if (!$style2) { - $style2 = document.createElement("style"); - $style2.id = id; - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", () => { - document.head.appendChild($style2); - }); - } else { - (document.head || document.documentElement).appendChild($style2); - } + destroy() { + if (this.lifecycle.closed) + return; + this.lifecycle.close(); + clearTimeout(this.timer); + this.timer = null; + this.emitting = false; + this.isLock = false; + const $danmuku = this.template.$danmuku; + if ($danmuku?.parentElement) + $danmuku.parentElement.removeChild($danmuku); } - $style2.textContent = style; } function artplayerPluginDanmuku(option) { return (art) => { const danmuku = new Danmuku(art, option); - const setting = art.isDestroy ? void 0 : new Setting(art, danmuku); - if (!art.isDestroy && danmuku.option.heatmap) { - heatmap(art, danmuku, danmuku.option.heatmap); + let setting; + try { + setting = art.isDestroy ? void 0 : new Setting(art, danmuku); + if (!art.isDestroy && danmuku.option.heatmap) { + heatmap(art, danmuku, danmuku.option.heatmap); + } + } catch (error) { + try { + setting?.destroy(); + } catch { + } + try { + danmuku.destroy(); + } catch { + } + throw error; } return { name: "artplayerPluginDanmuku", diff --git a/docs/uncompiled/artplayer-plugin-danmuku/index.js b/docs/uncompiled/artplayer-plugin-danmuku/index.js index 3a4b0d854..e72a0e079 100644 --- a/docs/uncompiled/artplayer-plugin-danmuku/index.js +++ b/docs/uncompiled/artplayer-plugin-danmuku/index.js @@ -1,5 +1,90 @@ var artplayerPluginDanmuku = (function() { "use strict"; + 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: @@ -41,41 +126,652 @@ var artplayerPluginDanmuku = (function() { } 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$1 = 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$1; + 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$1 = /* @__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$1); + 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$1); + 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 : [] }; + } + function filterState(owner, state, callback) { + const danmus = owner.states[state] || []; + for (let index = 0; index < danmus.length; index++) callback(danmus[index]); + return danmus; + } + function readyItems(owner) { + const { currentTime } = owner.art; + const result = []; + filterState(owner, "ready", (danmu) => result.push(danmu)); + filterState(owner, "wait", (danmu) => { + if (currentTime + 0.1 >= danmu.time && danmu.time >= currentTime - 0.1) + result.push(danmu); + }); + return result; + } + function setItemState(owner, danmu, state) { + owner.states[danmu.$state] = owner.states[danmu.$state].filter((item) => item !== danmu); + danmu.$state = state; + if (danmu.$ref) + danmu.$ref.dataset.state = state; + owner.states[state].push(danmu); + } + class Renderer { + constructor(owner) { + this.owner = owner; + this.nodes = /* @__PURE__ */ new Set(); + } + acquire() { + const $ref = this.owner.$refs.pop() || document.createElement("div"); + $ref.style.cssText = this.owner.constructor.cssText; + $ref.dataset.mode = ""; + $ref.dataset.id = ""; + $ref.className = ""; + this.nodes.add($ref); + return $ref; + } + get visibles() { + const result = []; + const { clientWidth } = this.owner.$player; + const clientLeft = this.owner.getLeft(this.owner.$player); + this.owner.filter("emit", (danmu) => { + const top = danmu.$ref.offsetTop; + const left = this.owner.getLeft(danmu.$ref) - clientLeft; + const height = danmu.$ref.clientHeight; + const width = danmu.$ref.clientWidth; + const distance = left + width; + const right = clientWidth - distance; + const speed = distance / danmu.$restTime; + const emit = {}; + emit.top = top; + emit.left = left; + emit.height = height; + emit.width = width; + emit.right = right; + emit.speed = speed; + emit.distance = distance; + emit.time = danmu.$restTime; + emit.mode = danmu.mode; + result.push(emit); + }); + return result; + } + left($ref) { + const rect = $ref.getBoundingClientRect(); + return this.owner.isRotate ? rect.top : rect.left; + } + makeWait(danmu) { + this.owner.setState(danmu, "wait"); + if (danmu.$ref) { + danmu.$ref.style.cssText = this.owner.constructor.cssText; + danmu.$ref.style.visibility = "hidden"; + danmu.$ref.style.marginLeft = "0px"; + danmu.$ref.style.transform = "translateX(0px)"; + danmu.$ref.style.transition = "transform 0s linear 0s"; + this.owner.$refs.push(danmu.$ref); + danmu.$ref = null; + } + } + resize() { + const { clientWidth } = this.owner.$player; + this.owner.filter("stop", (danmu) => { + switch (danmu.mode) { + // 滚动的弹幕 + case 0: + danmu.$ref.style.left = `${clientWidth}px`; + break; + } + }); + this.owner.filter("emit", (danmu) => { + danmu.$lastStartTime = Date.now(); + switch (danmu.mode) { + // 滚动的弹幕 + case 0: { + const distance = clientWidth + danmu.$ref.clientWidth; + danmu.$ref.style.left = `${clientWidth}px`; + danmu.$ref.style.transform = `translateX(${-distance}px)`; + danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; + break; + } + } + }); + } + continue() { + const { clientWidth } = this.owner.$player; + this.owner.filter("stop", (danmu) => { + this.owner.setState(danmu, "emit"); + danmu.$lastStartTime = Date.now(); + switch (danmu.mode) { + // 继续滚动的弹幕 + case 0: { + const distance = clientWidth + danmu.$ref.clientWidth; + danmu.$ref.style.transform = `translateX(${-distance}px)`; + danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; + break; + } + } + }); + return this; + } + suspend() { + const { clientWidth } = this.owner.$player; + this.owner.filter("emit", (danmu) => { + this.owner.setState(danmu, "stop"); + switch (danmu.mode) { + // 停止滚动的弹幕 + case 0: { + const translateX = clientWidth - (this.owner.getLeft(danmu.$ref) - this.owner.getLeft(this.owner.$player)); + danmu.$ref.style.transform = `translateX(${-translateX}px)`; + danmu.$ref.style.transition = "transform 0s linear 0s"; + break; + } + } + }); + return this; + } + prepare(danmu, operation) { + const owner = this.owner; + const ref = owner.$ref; + operation.danmu = danmu; + operation.ref = ref; + danmu.$ref = ref; + ref.textContent = danmu.text; + owner.$danmuku.appendChild(ref); + ref.style.opacity = owner.option.opacity; + ref.style.fontSize = `${owner.fontSize}px`; + ref.style.color = danmu.color; + ref.style.border = danmu.border ? `1px solid ${danmu.color}` : null; + ref.style.backgroundColor = danmu.border ? "rgb(0 0 0 / 50%)" : null; + owner.utils.setStyles(ref, danmu.style); + return ref; + } + place(danmu, ref, top, distance, clientWidth) { + ref.style.top = `${top}px`; + ref.style.visibility = "visible"; + ref.dataset.mode = danmu.mode; + ref.dataset.id = danmu.id || ""; + switch (danmu.mode) { + case 0: + ref.style.left = `${clientWidth}px`; + ref.style.marginLeft = "0px"; + ref.style.transform = `translateX(${-distance}px)`; + ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; + break; + case 1: + case 2: + ref.style.left = "50%"; + ref.style.marginLeft = `-${ref.clientWidth / 2}px`; + break; + } + } + release(operation) { + const { danmu, ref } = operation; + if (ref && danmu.$ref === ref) { + ref.style.cssText = this.owner.constructor.cssText; + ref.style.visibility = "hidden"; + this.owner.$refs.push(ref); + danmu.$ref = null; + } + operation.ref = null; + } + clear() { + this.destroy(); + this.owner.$refs = []; + this.owner.$danmuku.textContent = ""; + } + destroy() { + for (const node of this.nodes) { + if (node.parentElement) + node.parentElement.removeChild(node); + } + for (const danmu of this.owner.queue) danmu.$ref = null; + this.owner.$refs.length = 0; + this.nodes.clear(); + } + } + const cancelled = /* @__PURE__ */ Symbol("cancelled danmuku frame"); + class Scheduler { + constructor(owner) { + this.owner = owner; + this.generation = 0; + this.starts = 0; + this.frame = null; + this.operation = null; + this.closed = false; + this.running = false; + this.fault = false; + this.failedItems = /* @__PURE__ */ new Set(); + } + active(operation) { + const owner = this.owner; + return !this.closed && !this.fault && !owner.art.isDestroy && !owner.isStop && !owner.isHide && operation.generation === this.generation; + } + release(operation) { + this.owner.renderer.release(operation); + } + invalidate() { + this.generation++; + if (this.frame !== null) + window.cancelAnimationFrame(this.frame); + this.frame = null; + this.owner.timer = null; + if (this.operation) { + const operation = this.operation; + this.operation = null; + operation.cancel(); + this.release(operation); + } + this.owner.workerClient?.cancel(); + this.failedItems.clear(); + } + report(error) { + try { + this.owner.art.emit("artplayerPluginDanmuku:error", error); + } catch (listenerError) { + console.warn("Failed to report danmuku scheduling error:", listenerError); + } + } + fail(error) { + if (this.closed || this.fault) + return; + this.fault = true; + this.invalidate(); + this.report(error); + } + recover() { + if (this.closed || this.owner.art.isDestroy) + return; + this.failedItems.clear(); + if (this.fault) { + try { + this.owner.workerClient?.dispose(); + this.owner.createWorker(); + this.fault = false; + } catch (error) { + this.report(error); + } + } + } + schedule() { + const owner = this.owner; + if (!this.running || this.closed || this.fault || owner.art.isDestroy || owner.isStop || this.frame !== null || this.operation) + return; + this.frame = window.requestAnimationFrame(() => { + this.frame = null; + owner.timer = null; + if (this.closed || this.fault || owner.isStop || owner.art.isDestroy) + return; + let cancel; + const cancellation = new Promise((resolve) => cancel = () => resolve(cancelled)); + const operation = { generation: this.generation, cancel, wait: (value) => Promise.race([value, cancellation]), ref: null }; + this.operation = operation; + return this.run(operation).catch((error) => { + if (this.active(operation)) + this.fail(error); + }).finally(() => { + this.release(operation); + if (this.operation === operation) { + this.operation = null; + this.schedule(); + } + }); + }); + owner.timer = this.frame; + } + async run(operation) { + const owner = this.owner; + if (!owner.art.playing || !this.active(operation)) + return; + owner.filter("emit", (danmu) => { + const emitTime = (Date.now() - danmu.$lastStartTime) / 1e3; + danmu.$restTime -= emitTime; + danmu.$lastStartTime = Date.now(); + if (danmu.$restTime <= 0) + owner.makeWait(danmu); + }); + const readys = owner.readys; + for (const danmu of readys) { + if (!this.active(operation)) + return; + if (this.failedItems.has(danmu)) + continue; + let state; + try { + state = await operation.wait(owner.option.beforeVisible(danmu)); + } catch (error) { + if (!this.active(operation)) + return; + this.failedItems.add(danmu); + this.report(error); + continue; + } + if (!this.active(operation)) + return; + if (!state) + continue; + const { clientWidth, clientHeight } = owner.$player; + const ref = owner.renderer.prepare(danmu, operation); + if (!this.active(operation)) + return; + danmu.$lastStartTime = Date.now(); + danmu.$restTime = owner.speed; + const distance = clientWidth + ref.clientWidth; + const { result: top } = await operation.wait(owner.postMessage({ + type: "getDanmuTop", + target: { mode: danmu.mode, height: ref.clientHeight, speed: distance / danmu.$restTime }, + visibles: owner.visibles, + antiOverlap: owner.option.antiOverlap, + clientWidth, + clientHeight, + marginBottom: owner.marginBottom, + marginTop: owner.marginTop + })); + if (!this.active(operation) || danmu.$ref !== ref) + return; + if (top !== void 0) { + owner.setState(danmu, "emit"); + owner.renderer.place(danmu, ref, top, distance, clientWidth); + operation.ref = null; + owner.art.emit("artplayerPluginDanmuku:visible", danmu); + } else { + owner.setState(danmu, "ready"); + this.release(operation); + } + } + } + destroy() { + this.closed = true; + this.invalidate(); + } + } + let nextRequest = 0; + class WorkerClient { + constructor(createWorker, onFailure) { + this.pending = /* @__PURE__ */ new Map(); + this.closed = false; + this.failed = false; + this.onFailure = onFailure; + this.worker = createWorker(); + try { + this.worker.onmessage = (event) => { + const request = this.pending.get(event.data?.id); + if (!request) + return; + this.pending.delete(event.data.id); + request.resolve(event.data); + }; + this.worker.onerror = (event) => { + event.preventDefault?.(); + this.fail(event.error || event); + }; + this.worker.onmessageerror = (event) => this.fail(event.error || event); + } catch (error) { + try { + this.dispose(); + } catch { + } + throw error; + } + } + request(message) { + message.id = ++nextRequest; + const { id } = message; + if (this.closed || this.failed) + return Promise.resolve({ id, result: void 0 }); + return new Promise((resolve, reject) => { + this.pending.set(id, { resolve, reject }); + try { + this.worker.postMessage(message); + } catch (error) { + this.fail(error); + } + }); + } + fail(error) { + if (this.closed || this.failed) + return; + this.failed = true; + for (const request of this.pending.values()) request.reject(error); + this.pending.clear(); + try { + this.dispose(); + } catch { + } + this.onFailure(error); + } + cancel() { + for (const [id, request] of this.pending) request.resolve({ id, result: void 0 }); + this.pending.clear(); + } + dispose() { + if (this.closed) + return; + this.closed = true; + this.cancel(); + let failed = false; + let error; + const attempt = (callback) => { + try { + callback(); + } catch (failure) { + if (!failed) + error = failure; + failed = true; + } + }; + for (const name of ["onmessage", "onerror", "onmessageerror"]) { + attempt(() => this.worker[name] = null); + } + attempt(() => this.worker.terminate()); + if (failed) + throw error; + } + } const jsContent = 'function 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) { @@ -115,113 +811,46 @@ var artplayerPluginDanmuku = (function() { this.isHide = false; this.timer = null; this.index = 0; + this.worker = null; + this.workerClient = null; + this.renderer = new Renderer(this); + this.scheduler = new Scheduler(this); 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); + this.seek = this.seek.bind(this); + inputActive(this); + art.on("destroy", this.destroy); + try { + this.config(option, true); + if (!inputActive(this)) + return; + this.createWorker(); + } catch (error) { + art.off("destroy", this.destroy); + cancelInputs(this); + this.scheduler.destroy(); + 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(); + art.on("video:seeking", this.seek); + 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() { @@ -288,51 +917,15 @@ var artplayerPluginDanmuku = (function() { } // 获取弹幕DOM节点 get $ref() { - const $ref = this.$refs.pop() || document.createElement("div"); - $ref.style.cssText = Danmuku.cssText; - $ref.dataset.mode = ""; - $ref.dataset.id = ""; - $ref.className = ""; - return $ref; + return this.renderer.acquire(); } // 获取准备好发送的弹幕 get readys() { - const { currentTime } = this.art; - const result = []; - this.filter("ready", (danmu) => result.push(danmu)); - this.filter("wait", (danmu) => { - if (currentTime + 0.1 >= danmu.time && danmu.time >= currentTime - 0.1) { - result.push(danmu); - } - }); - return result; + return readyItems(this); } // 可见的弹幕的数据,用于计算下一个弹幕的top值 get visibles() { - const result = []; - const { clientWidth } = this.$player; - const clientLeft = this.getLeft(this.$player); - this.filter("emit", (danmu) => { - const top = danmu.$ref.offsetTop; - const left = this.getLeft(danmu.$ref) - clientLeft; - const height = danmu.$ref.clientHeight; - const width = danmu.$ref.clientWidth; - const distance = left + width; - const right = clientWidth - distance; - const speed = distance / danmu.$restTime; - const emit = {}; - emit.top = top; - emit.left = left; - emit.height = height; - emit.width = width; - emit.right = right; - emit.speed = speed; - emit.distance = distance; - emit.time = danmu.$restTime; - emit.mode = danmu.mode; - result.push(emit); - }); - return result; + return this.renderer.visibles; } // 计算弹幕速度 get speed() { @@ -341,40 +934,47 @@ var artplayerPluginDanmuku = (function() { // 加载弹幕 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 = ""; + this.renderer.clear(); } 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", // 弹幕唯一标识 @@ -393,7 +993,7 @@ var artplayerPluginDanmuku = (function() { }); if (!danmu.text.trim()) return this; - if (danmu.time) { + if (danmu.time || danmu.time === 0) { danmu.time = clamp(danmu.time, 0, Infinity); } else { danmu.time = this.art.currentTime + 0.5; @@ -411,6 +1011,8 @@ var artplayerPluginDanmuku = (function() { return this; if (!this.option.filter(danmu)) return this; + if (!inputActive(this)) + return this; const item = { ...danmu, $state: "wait", @@ -432,243 +1034,116 @@ var artplayerPluginDanmuku = (function() { 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; + const beforeVisibleChanged = this.option.beforeVisible !== next.beforeVisible; + this.option = next; + if (beforeVisibleChanged) { + this.scheduler.invalidate(); + this.scheduler.schedule(); + } 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值,受到旋屏影响 getLeft($ref) { - const rect = $ref.getBoundingClientRect(); - return this.isRotate ? rect.top : rect.left; + return this.renderer.left($ref); } // 复杂运算交给 Web Worker 处理 postMessage(message = {}) { - return new Promise((resolve) => { - message.id = Date.now(); - this.worker.postMessage(message); - this.worker.onmessage = (event) => { - const { data } = event; - if (data.id === message.id) { - resolve(data); - } - }; - }); + return this.workerClient.request(message); + } + createWorker() { + this.workerClient = new WorkerClient(() => new WorkerWrapper(), (error) => this.scheduler.fail(error)); + this.worker = this.workerClient.worker; } // 根据状态获取弹幕 filter(state, callback) { - const danmus = this.states[state] || []; - for (let index = 0; index < danmus.length; index++) { - callback(danmus[index]); - } - return danmus; + return filterState(this, state, callback); } // 设置弹幕状态 setState(danmu, state) { - this.states[danmu.$state] = this.states[danmu.$state].filter((item) => item !== danmu); - danmu.$state = state; - if (danmu.$ref) { - danmu.$ref.dataset.state = state; - } - this.states[state].push(danmu); + setItemState(this, danmu, state); } // 重置弹幕到wait状态,回收弹幕DOM节点 makeWait(danmu) { - this.setState(danmu, "wait"); - if (danmu.$ref) { - danmu.$ref.style.cssText = Danmuku.cssText; - danmu.$ref.style.visibility = "hidden"; - danmu.$ref.style.marginLeft = "0px"; - danmu.$ref.style.transform = "translateX(0px)"; - danmu.$ref.style.transition = "transform 0s linear 0s"; - this.$refs.push(danmu.$ref); - danmu.$ref = null; - } + this.renderer.makeWait(danmu); } // 实时更新弹幕 update() { - const { setStyles } = this.utils; - this.timer = window.requestAnimationFrame(async () => { - if (this.art.playing && !this.isHide) { - this.filter("emit", (danmu) => { - const emitTime = (Date.now() - danmu.$lastStartTime) / 1e3; - danmu.$restTime -= emitTime; - danmu.$lastStartTime = Date.now(); - if (danmu.$restTime <= 0) { - this.makeWait(danmu); - } - }); - const readys = this.readys; - for (let index = 0; index < readys.length; index++) { - const danmu = readys[index]; - const state = await this.option.beforeVisible(danmu); - if (state) { - const { clientWidth, clientHeight } = this.$player; - danmu.$ref = this.$ref; - danmu.$ref.textContent = danmu.text; - this.$danmuku.appendChild(danmu.$ref); - danmu.$ref.style.opacity = this.option.opacity; - danmu.$ref.style.fontSize = `${this.fontSize}px`; - danmu.$ref.style.color = danmu.color; - danmu.$ref.style.border = danmu.border ? `1px solid ${danmu.color}` : null; - danmu.$ref.style.backgroundColor = danmu.border ? "rgb(0 0 0 / 50%)" : null; - setStyles(danmu.$ref, danmu.style); - danmu.$lastStartTime = Date.now(); - danmu.$restTime = this.speed; - const distance = clientWidth + danmu.$ref.clientWidth; - const { result: top } = await this.postMessage({ - type: "getDanmuTop", - target: { - mode: danmu.mode, - height: danmu.$ref.clientHeight, - speed: distance / danmu.$restTime - }, - // 当前弹幕信息 - visibles: this.visibles, - // 可见的弹幕的数据 - antiOverlap: this.option.antiOverlap, - clientWidth, - clientHeight, - marginBottom: this.marginBottom, - marginTop: this.marginTop - }); - if (danmu.$ref) { - if (!this.isStop && top !== void 0) { - this.setState(danmu, "emit"); - danmu.$ref.style.top = `${top}px`; - danmu.$ref.style.visibility = "visible"; - danmu.$ref.dataset.mode = danmu.mode; - danmu.$ref.dataset.id = danmu.id || ""; - switch (danmu.mode) { - // 滚动的弹幕 - case 0: { - danmu.$ref.style.left = `${clientWidth}px`; - danmu.$ref.style.marginLeft = "0px"; - danmu.$ref.style.transform = `translateX(${-distance}px)`; - danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; - break; - } - case 1: - // falls through - case 2: - danmu.$ref.style.left = "50%"; - danmu.$ref.style.marginLeft = `-${danmu.$ref.clientWidth / 2}px`; - break; - } - this.art.emit("artplayerPluginDanmuku:visible", danmu); - } else { - this.setState(danmu, "ready"); - this.$refs.push(danmu.$ref); - danmu.$ref = null; - } - } - } - } - } - if (!this.isStop) { - this.update(); - } - }); + this.scheduler.running = true; + this.scheduler.schedule(); return this; } // 重置正在显示的弹幕: stop/emit 状态的弹幕 resize() { - const { clientWidth } = this.$player; - this.filter("stop", (danmu) => { - switch (danmu.mode) { - // 滚动的弹幕 - case 0: - danmu.$ref.style.left = `${clientWidth}px`; - break; - } - }); - this.filter("emit", (danmu) => { - danmu.$lastStartTime = Date.now(); - switch (danmu.mode) { - // 滚动的弹幕 - case 0: { - const distance = clientWidth + danmu.$ref.clientWidth; - danmu.$ref.style.left = `${clientWidth}px`; - danmu.$ref.style.transform = `translateX(${-distance}px)`; - danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; - break; - } - } - }); + this.renderer.resize(); } // 继续弹幕 continue() { - const { clientWidth } = this.$player; - this.filter("stop", (danmu) => { - this.setState(danmu, "emit"); - danmu.$lastStartTime = Date.now(); - switch (danmu.mode) { - // 继续滚动的弹幕 - case 0: { - const distance = clientWidth + danmu.$ref.clientWidth; - danmu.$ref.style.transform = `translateX(${-distance}px)`; - danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; - break; - } - } - }); + this.renderer.continue(); return this; } // 暂停弹幕 suspend() { - const { clientWidth } = this.$player; - this.filter("emit", (danmu) => { - this.setState(danmu, "stop"); - switch (danmu.mode) { - // 停止滚动的弹幕 - case 0: { - const translateX = clientWidth - (this.getLeft(danmu.$ref) - this.getLeft(this.$player)); - danmu.$ref.style.transform = `translateX(${-translateX}px)`; - danmu.$ref.style.transition = "transform 0s linear 0s"; - break; - } - } - }); + this.renderer.suspend(); return this; } stop() { this.isStop = true; + this.scheduler.running = false; + this.scheduler.invalidate(); this.suspend(); - window.cancelAnimationFrame(this.timer); this.art.emit("artplayerPluginDanmuku:stop"); return this; } start() { + if (this.scheduler.closed || this.art.isDestroy) + return this; + const start = ++this.scheduler.starts; + const generation = this.scheduler.generation; this.isStop = false; + this.scheduler.recover(); + const obsolete = this.scheduler.closed || this.art.isDestroy || this.isStop || this.scheduler.fault || generation !== this.scheduler.generation || start !== this.scheduler.starts; + if (obsolete) + return this; this.continue(); this.update(); this.art.emit("artplayerPluginDanmuku:start"); return this; } reset() { + this.scheduler.invalidate(); this.queue.forEach((danmu) => this.makeWait(danmu)); this.art.emit("artplayerPluginDanmuku:reset"); + this.scheduler.recover(); + this.scheduler.schedule(); return this; } + seek() { + this.scheduler.invalidate(); + this.scheduler.schedule(); + } show() { this.isHide = false; this.$danmuku.style.opacity = 1; @@ -678,181 +1153,558 @@ var artplayerPluginDanmuku = (function() { } hide() { this.isHide = true; + this.scheduler.invalidate(); this.$danmuku.style.opacity = 0; this.option.visible = false; this.art.emit("artplayerPluginDanmuku:hide"); + this.scheduler.schedule(); return 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"); + cancelInputs(this); + this.scheduler.destroy(); + try { + try { + this.stop(); + } finally { + this.workerClient?.dispose(); + } + } finally { + this.renderer.destroy(); + 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("video:seeking", this.seek); + this.art.off("destroy", this.destroy); + this.art.emit("artplayerPluginDanmuku:destroy"); + } } } - const lib = { - map(value, inMin, inMax, outMin, outMax) { - return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; - }, - range(start, end, tick) { - const s = Math.round(start / tick) * tick; - return Array.from( - { - length: Math.floor((end - start) / tick) - }, - (v, k) => { - return k * tick + s; - } - ); + const MAX_HEATMAP_POINTS = 4294967295; + function upperBound(values, value) { + let start = 0; + let end = values.length; + while (start < end) { + const middle = Math.floor((start + end) / 2); + if (values[middle] <= value) + start = middle + 1; + else end = middle; } - }; + return start; + } + function sampleHeatmap(queue, width, duration, sampling) { + if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(duration) || duration <= 0 || !Number.isFinite(sampling) || sampling <= 0 || Math.floor(width / sampling) + 1 > MAX_HEATMAP_POINTS) { + return []; + } + const times = queue.map((item) => item.time).filter((time) => typeof time === "number" && !Number.isNaN(time)).sort((a, b) => a - b); + const points = []; + const gap = duration / width; + if (!Number.isFinite(gap)) + return []; + for (let x = 0; x <= width; x += sampling) { + if (points.length >= MAX_HEATMAP_POINTS || x + sampling === x) + return []; + const left = x * gap; + const right = (x + sampling) * gap; + points.push([x, upperBound(times, right) - upperBound(times, left)]); + } + return points; + } + function map(value, inMin, inMax, outMin, outMax) { + return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; + } function line(pointA, pointB) { const lengthX = pointB[0] - pointA[0]; const lengthY = pointB[1] - pointA[1]; - return { - length: Math.sqrt(lengthX ** 2 + lengthY ** 2), - angle: Math.atan2(lengthY, lengthX) - }; + return { length: Math.sqrt(lengthX ** 2 + lengthY ** 2), angle: Math.atan2(lengthY, lengthX) }; } + function heatmapGeometry({ width, height, duration, queue, option, points: input = [] }) { + if (![width, height, duration].every((value) => Number.isFinite(value) && value > 0)) + return null; + const defaults = { + xMin: 0, + xMax: width, + yMin: 0, + yMax: 128, + scale: 0.25, + opacity: 0.2, + minHeight: Math.floor(height * 0.05), + sampling: Math.max(1, Math.floor(width / 100)), + smoothing: 0.2, + flattening: 0.2 + }; + const options = Object.assign({}, defaults, typeof option === "object" ? option : {}); + for (const key of Object.keys(defaults)) { + if (!Number.isFinite(options[key])) + options[key] = defaults[key]; + } + if (options.sampling <= 0) + options.sampling = defaults.sampling; + if (options.xMin === options.xMax || options.yMin === options.yMax) + return null; + const points = Array.isArray(input) && input.length ? [...input] : sampleHeatmap(queue, width, duration, options.sampling); + if (!points.length || !points.every((point) => Array.isArray(point) && Number.isFinite(point[0]) && Number.isFinite(point[1]))) { + return null; + } + const lastPoint = points[points.length - 1]; + if (lastPoint[0] !== width) { + if (points.length === MAX_HEATMAP_POINTS) + return null; + points.push([width, lastPoint[1]]); + } + let yMin = Infinity; + let yMax = -Infinity; + for (const point of points) { + yMin = Math.min(yMin, point[1]); + yMax = Math.max(yMax, point[1]); + } + const yMid = (yMin + yMax) / 2; + for (const point of points) { + const y = point[1]; + point[1] = y * (y > yMid ? 1 + options.scale : 1 - options.scale) + options.minHeight; + } + const controlPoint = (current, previous, next, reverse) => { + const geometry = line(previous || current, next || current); + const flat = map(Math.cos(geometry.angle) * options.flattening, 0, 1, 1, 0); + const angle = geometry.angle * flat + (reverse ? Math.PI : 0); + const length = geometry.length * options.smoothing; + return [current[0] + Math.cos(angle) * length, current[1] + Math.sin(angle) * length]; + }; + const positions = points.map((point) => [map(point[0], options.xMin, options.xMax, 0, width), map(point[1], options.yMin, options.yMax, height, 0)]); + if (!positions.every((point) => point.every(Number.isFinite))) + return null; + const path = positions.map((point, index, all) => { + if (index === 0) + return `M ${all[all.length - 1][0]},${height} L ${point[0]},${height} L ${point[0]},${point[1]}`; + const start = controlPoint(all[index - 1], all[index - 2], point); + const end = controlPoint(point, all[index - 1], all[index + 1], true); + return `C ${start[0]},${start[1]} ${end[0]},${end[1]} ${point[0]},${point[1]}${index === all.length - 1 ? " z" : ""}`; + }).join(" "); + if (/NaN|Infinity/u.test(path)) + return null; + return { path, opacity: options.opacity, width, height }; + } + let nextGradient = 0; function heatmap(art, danmuku, option) { + if (art.isDestroy) + return; const { query } = art.constructor.utils; - art.controls.add({ - name: "heatmap", - position: "top", - html: "", - style: { - position: "absolute", - top: "-100px", - left: "0px", - right: "0px", - height: "100px", - width: "100%", - pointerEvents: "none" - }, - mounted($heatmap) { - let $start = null; - let $stop = null; - function update(arg = []) { - $start = null; - $stop = null; - $heatmap.innerHTML = ""; - if (!art.duration || art.option.isLive) + let gradient; + const subscriptions = []; + let element; + let start; + let stop; + let closed = false; + const active = () => !closed && !art.isDestroy; + function listen(name, callback) { + subscriptions.push([name, callback]); + art.on(name, callback); + } + function dispose(removeControl) { + if (closed) + return; + closed = true; + start = null; + stop = null; + let failure; + let failed = false; + const attempt = (callback) => { + try { + callback(); + } catch (error) { + if (!failed) + failure = error; + failed = true; + } + }; + for (const [name, callback] of subscriptions.splice(0)) + attempt(() => art.off(name, callback)); + if (removeControl && element && art.controls.heatmap === element) + attempt(() => art.controls.remove("heatmap")); + element = void 0; + if (failed) + throw failure; + } + function progress(value) { + if (active() && start && stop) { + start.setAttribute("offset", `${value * 100}%`); + stop.setAttribute("offset", `${value * 100}%`); + } + } + function draw(points = []) { + if (!active()) + return; + start = null; + stop = null; + element.innerHTML = ""; + if (art.option.isLive) + return; + const shape = heatmapGeometry({ width: element.offsetWidth, height: element.offsetHeight, duration: art.duration, queue: danmuku.queue, option, points }); + if (!shape || !active()) + return; + const document2 = element.ownerDocument; + if (!gradient) + gradient = document2 ? "heatmap-solids" : `heatmap-solids-${++nextGradient}`; + while (document2?.getElementById(gradient)) + gradient = `heatmap-solids-${++nextGradient}`; + element.innerHTML = ` + + + + + + + + + + + + `; + start = query("#heatmap-start", element); + stop = query("#heatmap-stop", element); + progress(art.played); + } + function update(points) { + try { + draw(points); + } catch (error) { + try { + dispose(true); + } catch { + } + throw error; + } + } + listen("destroy", () => dispose(true)); + try { + art.controls.add({ + name: "heatmap", + position: "top", + html: "", + style: { + position: "absolute", + top: "-100px", + left: "0px", + right: "0px", + height: "100px", + width: "100%", + pointerEvents: "none" + }, + beforeUnmount() { + dispose(false); + }, + mounted(value) { + element = value; + if (closed) { + if (art.controls.heatmap === value) + art.controls.remove("heatmap"); + element = void 0; return; - const svg = { - w: $heatmap.offsetWidth, - h: $heatmap.offsetHeight - }; - const options = { - xMin: 0, - xMax: svg.w, - yMin: 0, - yMax: 128, - scale: 0.25, - opacity: 0.2, - minHeight: Math.floor(svg.h * 0.05), - sampling: Math.floor(svg.w / 100), - smoothing: 0.2, - flattening: 0.2 - }; - if (typeof option === "object") { - Object.assign(options, option); } - let points = []; - if (Array.isArray(arg) && arg.length) { - points = [...arg]; - } else { - const gap = art.duration / svg.w; - for (let x = 0; x <= svg.w; x += options.sampling) { - const y = danmuku.queue.filter( - ({ time }) => time > x * gap && time <= (x + options.sampling) * gap - ).length; - points.push([x, y]); + if (!active()) { + dispose(true); + return; + } + listen("video:timeupdate", () => progress(art.played)); + listen("setBar", (type, percentage) => { + if (type === "played") + progress(percentage); + }); + listen("ready", () => update()); + listen("resize", () => update()); + listen("artplayerPluginDanmuku:loaded", () => update()); + listen("artplayerPluginDanmuku:points", (points) => update(points)); + } + }); + } catch (error) { + try { + dispose(true); + } catch { + } + throw error; + } + return () => dispose(true); + } + const properties = /* @__PURE__ */ new WeakMap(); + class SettingLifecycle { + constructor(art) { + this.art = art; + this.closed = false; + this.disposers = /* @__PURE__ */ new Set(); + this.cancelled = new Promise((resolve) => this.cancel = resolve); + } + get active() { + return !this.closed && !this.art.isDestroy; + } + own(dispose) { + if (this.closed) + dispose(); + else this.disposers.add(dispose); + } + write(target, key, value, apply = (value2) => target[key] = value2) { + if (!this.active) + return; + const priority = () => typeof target.getPropertyPriority === "function" ? target.getPropertyPriority(key) : void 0; + let keys = properties.get(target); + if (!keys) { + keys = /* @__PURE__ */ new Map(); + properties.set(target, keys); + } + let entry = keys.get(key); + if (!entry) { + entry = { + original: target[key], + present: key in target || target[key] !== void 0, + priority: priority(), + owners: /* @__PURE__ */ new Map(), + apply + }; + keys.set(key, entry); + } + if (!entry.owners.has(this)) { + this.own(() => { + const owners = [...entry.owners.keys()]; + const current = owners[owners.length - 1] === this; + entry.owners.delete(this); + if (current && target[key] === entry.last && priority() === entry.lastPriority) { + const values = [...entry.owners.values()]; + if (values.length) { + entry.apply(values[values.length - 1]); + entry.last = target[key]; + entry.lastPriority = priority(); + } else if (entry.priority !== void 0) { + target.setProperty(key, entry.original, entry.priority); + } else if (entry.present) { + entry.apply(entry.original); + } else { + delete target[key]; } } - if (points.length === 0) - return; - const lastPoint = points[points.length - 1]; - const lastX = lastPoint[0]; - const lastY = lastPoint[1]; - if (lastX !== svg.w) { - points.push([svg.w, lastY]); + if (!entry.owners.size) { + keys.delete(key); + if (!keys.size) + properties.delete(target); } - const yPoints = points.map((point) => point[1]); - const yMin = Math.min(...yPoints); - const yMax = Math.max(...yPoints); - const yMid = (yMin + yMax) / 2; - for (let i = 0; i < points.length; i++) { - const point = points[i]; - const y = point[1]; - point[1] = y * (y > yMid ? 1 + options.scale : 1 - options.scale) + options.minHeight; - } - const controlPoint = (current, previous, next, reverse) => { - const p = previous || current; - const n = next || current; - const o = line(p, n); - const flat = lib.map(Math.cos(o.angle) * options.flattening, 0, 1, 1, 0); - const angle = o.angle * flat + (reverse ? Math.PI : 0); - const length = o.length * options.smoothing; - const x = current[0] + Math.cos(angle) * length; - const y = current[1] + Math.sin(angle) * length; - return [x, y]; - }; - const bezierCommand = (point, i, a) => { - const cps = controlPoint(a[i - 1], a[i - 2], point); - const cpe = controlPoint(point, a[i - 1], a[i + 1], true); - const close = i === a.length - 1 ? " z" : ""; - return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}${close}`; - }; - const pointsPositions = points.map((e) => { - const x = lib.map(e[0], options.xMin, options.xMax, 0, svg.w); - const y = lib.map(e[1], options.yMin, options.yMax, svg.h, 0); - return [x, y]; - }); - const pathD = pointsPositions.reduce( - (acc, e, i, a) => i === 0 ? `M ${a[a.length - 1][0]},${svg.h} L ${e[0]},${svg.h} L ${e[0]},${e[1]}` : `${acc} ${bezierCommand(e, i, a)}`, - "" - ); - $heatmap.innerHTML = ` - - - - - - - - - - - - `; - $start = query("#heatmap-start", $heatmap); - $stop = query("#heatmap-stop", $heatmap); - $start.setAttribute("offset", `${art.played * 100}%`); - $stop.setAttribute("offset", `${art.played * 100}%`); + }); + } + entry.owners.delete(this); + entry.owners.set(this, value); + entry.apply(value); + entry.last = target[key]; + entry.lastPriority = priority(); + } + on(name, callback) { + if (!this.active) + return; + const lifecycle = this; + function listener(...args) { + if (lifecycle.active) + return callback.apply(this, args); + } + const dispose = () => this.art.off(name, listener); + this.own(dispose); + try { + this.art.on(name, listener); + if (!this.active) + dispose(); + } catch (error) { + this.release(dispose); + throw error; + } + } + proxy(target, name, callback) { + if (!this.active) + return; + const lifecycle = this; + function listener(...args) { + if (lifecycle.active) + return callback.apply(this, args); + } + let remove; + const dispose = () => { + if (typeof remove === "function") { + const registry = this.art.events; + if (typeof registry?.remove === "function" && registry.destroyEvents?.has(remove)) + registry.remove(remove); + else remove(); + } else { + target.removeEventListener?.(name, listener); } - art.on("video:timeupdate", () => { - if ($start && $stop) { - $start.setAttribute("offset", `${art.played * 100}%`); - $stop.setAttribute("offset", `${art.played * 100}%`); - } - }); - art.on("setBar", (type, percentage) => { - if ($start && $stop && type === "played") { - $start.setAttribute("offset", `${percentage * 100}%`); - $stop.setAttribute("offset", `${percentage * 100}%`); - } - }); - art.on("ready", () => update()); - art.on("resize", () => update()); - art.on("artplayerPluginDanmuku:loaded", () => update()); - art.on("artplayerPluginDanmuku:points", (points) => update(points)); + }; + this.own(dispose); + try { + remove = this.art.proxy(target, name, listener); + if (!this.active) + dispose(); + } catch (error) { + this.release(dispose); + throw error; + } + } + wait(value) { + return Promise.race([value, this.cancelled]); + } + release(dispose) { + try { + dispose(); + } catch (error) { + console.warn("Failed to dispose danmuku setting resource:", error); + } + } + close() { + if (this.closed) + return; + this.closed = true; + this.cancel(); + for (const dispose of this.disposers) { + this.release(dispose); + } + this.disposers.clear(); + } + } + async function emitSetting(setting) { + const { lifecycle, template: { $input } } = setting; + if (!lifecycle.active) + return; + const text = $input.value.trim(); + if (!text.length || setting.isLock || setting.emitting) + return; + const danmu = { + text, + mode: setting.option.mode, + color: setting.option.color, + time: setting.art.currentTime + }; + const report = (error) => { + if (lifecycle.active) + console.error("Error emitting danmuku:", error); + }; + try { + setting.emitting = true; + const state = await lifecycle.wait(setting.option.beforeEmit(danmu)); + if (!lifecycle.active) + return; + setting.emitting = false; + if (state !== true) + return; + danmu.border = true; + delete danmu.time; + Promise.resolve(setting.danmuku.emit(danmu)).catch(report); + if (!lifecycle.active) + return; + $input.value = ""; + setting.lock(); + } catch (error) { + report(error); + if (lifecycle.active) + setting.emitting = false; + } + } + function lockSetting(setting) { + if (!setting.lifecycle.active) + return; + const { addClass } = setting.utils; + const { $send } = setting.template; + clearTimeout(setting.timer); + setting.isLock = true; + let time = setting.option.lockTime; + $send.textContent = time; + addClass($send, "apd-lock"); + const loop = () => { + setting.timer = setTimeout(() => { + if (!setting.lifecycle.active) + return; + if (time === 0) { + setting.unlock(); + } else { + time -= 1; + $send.textContent = time; + loop(); + } + }, 1e3); + }; + loop(); + } + function unlockSetting(setting) { + clearTimeout(setting.timer); + setting.timer = null; + setting.isLock = false; + if (!setting.lifecycle.active) + return; + const { removeClass } = setting.utils; + const { $send } = setting.template; + $send.textContent = "发送"; + removeClass($send, "apd-lock"); + } + function createSlider({ min, max, container, findIndex, onChange, steps = [] }) { + const { lifecycle } = this; + if (!lifecycle.active) + return { reset() { + } }; + const { query, clamp, setStyle } = this.utils; + setStyle(container, "touch-action", "none"); + container.innerHTML = ` +
+
+ ${steps.map(() => `
`).join("")} +
+
+
+
+
+ ${steps.map((step) => step.hide ? "" : `
${step.name}
`).join("")} +
+ `; + const $dot = query(".apd-slider-dot", container); + const $progress = query(".apd-slider-progress", container); + let isDroging = false; + lifecycle.own(() => isDroging = false); + function reset(index) { + if (!lifecycle.active) + return; + if (index === void 0) + index = findIndex(); + if (index < min || index > max) + return; + const percentage = (index - min) / (max - min); + $dot.style.left = `${percentage * 100}%`; + if (steps.length === 0) { + $progress.style.width = $dot.style.left; + } + onChange(index); + } + function updateLeft(event) { + const { top, height, left, width } = container.getBoundingClientRect(); + if (this.art.isRotate) { + const value = clamp(event.clientY - top, 0, height); + const index = Math.round(value / height * (max - min) + min); + reset(index); + } else { + const value = clamp(event.clientX - left, 0, width); + const index = Math.round(value / width * (max - min) + min); + reset(index); + } + } + lifecycle.proxy(container, "click", (event) => { + updateLeft.call(this, event); + }); + lifecycle.proxy(container, "pointerdown", (event) => { + isDroging = event.button === 0; + }); + lifecycle.on("document:pointermove", (event) => { + if (isDroging) { + updateLeft.call(this, event); } }); + lifecycle.on("document:pointerup", (event) => { + if (isDroging) { + isDroging = false; + updateLeft.call(this, event); + } + }); + return { reset }; } const $check_off = ''; const $check_on = ''; @@ -866,92 +1718,25 @@ var artplayerPluginDanmuku = (function() { const $off = ''; const $on = ''; const $style = ''; - const style = ".artplayer-plugin-danmuku {\n display: flex;\n position: relative;\n z-index: 99;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n height: 32px;\n width: 100%;\n color: #fff;\n font-weight: 300;\n flex-shrink: 0;\n gap: 10px;\n}\n.artplayer-plugin-danmuku .apd-icon {\n cursor: pointer;\n opacity: 0.75;\n transition: all 0.2s ease;\n fill: #fff;\n}\n.artplayer-plugin-danmuku .apd-icon:hover {\n opacity: 1;\n}\n.artplayer-plugin-danmuku .apd-config {\n display: flex;\n position: relative;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 320px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-config-mode,\n.artplayer-plugin-danmuku .apd-config-slider,\n.artplayer-plugin-danmuku .apd-config-other,\n.artplayer-plugin-danmuku .apd-style-mode {\n margin-bottom: 15px;\n}\n.artplayer-plugin-danmuku .apd-modes {\n display: flex;\n align-items: center;\n margin-top: 5px;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode {\n cursor: pointer;\n text-align: center;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-config-slider {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.artplayer-plugin-danmuku .apd-config-slider .apd-value {\n width: 32px;\n text-align: right;\n}\n.artplayer-plugin-danmuku .apd-slider {\n position: relative;\n flex: 1;\n display: flex;\n height: 20px;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-line {\n position: relative;\n height: 2px;\n width: 100%;\n overflow: hidden;\n border-radius: 3px;\n background-color: rgba(255, 255, 255, 0.25);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point {\n width: 2px;\n height: 2px;\n border-radius: 50%;\n background-color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-progress {\n width: 0%;\n height: 100%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-dot {\n position: absolute;\n transform: translateX(-6px);\n left: 0%;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps {\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: absolute;\n bottom: -12px;\n width: calc(100% + 32px);\n color: #777;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step {\n flex-shrink: 0;\n width: 36px;\n text-align: center;\n scale: 0.95;\n}\n.artplayer-plugin-danmuku .apd-config-other {\n display: flex;\n align-items: center;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-check-off,\n.artplayer-plugin-danmuku .apd-config-other .apd-check-on {\n width: 16px;\n height: 16px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other {\n display: flex;\n align-items: center;\n cursor: pointer;\n gap: 2px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-emitter {\n display: flex;\n flex: 1;\n align-items: center;\n height: 100%;\n background-color: rgba(255, 255, 255, 0.25);\n border-radius: 5px;\n}\n.artplayer-plugin-danmuku .apd-style {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 200px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-colors {\n display: flex;\n flex-wrap: wrap;\n margin-top: 5px;\n gap: 8px;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color {\n width: 16px;\n height: 16px;\n border-radius: 2px;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active {\n border: 1px solid black;\n box-shadow: 0 0 0 1px #fff;\n}\n.artplayer-plugin-danmuku .apd-input {\n outline: none;\n height: 100%;\n flex: 1;\n min-width: 0;\n width: auto;\n border: none;\n line-height: 1;\n color: #fff;\n background-color: transparent;\n}\n.artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-send {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n width: 60px;\n flex-shrink: 0;\n cursor: pointer;\n text-shadow: none;\n border-top-right-radius: 5px;\n border-bottom-right-radius: 5px;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-send.apd-lock {\n cursor: not-allowed;\n color: #666;\n background-color: #e7e7e7;\n}\n.art-controls-center .apd-emitter {\n width: 260px;\n flex: none;\n}\n.art-fullscreen .artplayer-plugin-danmuku,\n.art-fullscreen-web .artplayer-plugin-danmuku {\n height: 38px;\n gap: 16px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on {\n width: 28px;\n height: 28px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter {\n width: 400px;\n flex: none;\n}\n.art-video-player > .artplayer-plugin-danmuku {\n position: absolute;\n left: 0;\n right: 0;\n bottom: -40px;\n padding: 0 10px;\n}\n.art-video-player:has(> .artplayer-plugin-danmuku) {\n margin-bottom: 40px;\n}\n[data-danmuku-emitter='false'] .apd-emitter {\n display: none !important;\n}\n[data-danmuku-emitter='false'] .art-controls-center .artplayer-plugin-danmuku {\n justify-content: flex-end;\n gap: 18px;\n}\n[data-danmuku-emitter='false'].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,\n[data-danmuku-emitter='false'].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku {\n gap: 24px;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-icon {\n fill: #333;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-emitter {\n background-color: #f1f2f3;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input {\n color: #000;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(0, 0, 0, 0.3);\n}\n[data-danmuku-visible='false'] .apd-toggle-off {\n display: block;\n}\n[data-danmuku-visible='false'] .apd-toggle-on {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-off {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-on {\n display: block;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-on {\n display: none;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-off {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-on {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-off {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-on {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-off {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-on {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-off {\n display: none;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-off {\n display: block;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-on {\n display: none;\n}\n[data-danmuku-mode0='false'] .art-danmuku [data-mode='0'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-off {\n display: none;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-on {\n display: block;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-off {\n display: block;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-on {\n display: none;\n}\n[data-danmuku-mode1='false'] .art-danmuku [data-mode='1'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-off {\n display: none;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-on {\n display: block;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-off {\n display: block;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-on {\n display: none;\n}\n[data-danmuku-mode2='false'] .art-danmuku [data-mode='2'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-off {\n display: none;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-on {\n display: block;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] path {\n fill: #00a1d6;\n}\n"; - class Setting { - constructor(art, danmuku) { - this.art = art; - this.danmuku = danmuku; - this.utils = art.constructor.utils; - const { setStyle } = this.utils; - const { $controlsCenter } = art.template; - setStyle($controlsCenter, "display", "flex"); - this.template = { - $controlsCenter, - $mount: $controlsCenter, - $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 = false; - this.isLock = false; - this.timer = null; - this.createTemplate(); - this.createSliders(); - this.createEvents(); - this.mount(this.option.mount); - art.on("resize", () => this.resize()); - art.on("fullscreen", (state) => this.onFullscreen(state)); - art.on("fullscreenWeb", (state) => this.onFullscreen(state)); - art.proxy(this.template.$config, "mouseenter", () => { - this.onMouseEnter({ - $control: this.template.$config, - $panel: this.template.$configPanel - }); - }); - art.proxy(this.template.$style, "mouseenter", () => { - this.onMouseEnter({ - $control: this.template.$style, - $panel: this.template.$stylePanel - }); - }); - } - static get icons() { - return { - $on, - $off, - $config, - $style, - $mode_0_off, - $mode_0_on, - $mode_1_off, - $mode_1_on, - $mode_2_off, - $mode_2_on, - $check_on, - $check_off - }; - } - get option() { - return this.danmuku.option; - } - get outside() { - return this.template.$mount !== this.template.$controlsCenter; - } - get TEMPLATE() { - const { option } = this; - return ` + function settingIcons() { + return { + $on, + $off, + $config, + $style, + $mode_0_off, + $mode_0_on, + $mode_1_off, + $mode_1_on, + $mode_2_off, + $mode_2_on, + $check_on, + $check_off + }; + } + function settingTemplate() { + const { option } = this; + return `
${$on}${$off}
@@ -1044,6 +1829,126 @@ var artplayerPluginDanmuku = (function() {
发送
`; + } + const style = ".artplayer-plugin-danmuku {\n display: flex;\n position: relative;\n z-index: 99;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n height: 32px;\n width: 100%;\n color: #fff;\n font-weight: 300;\n flex-shrink: 0;\n gap: 10px;\n}\n.artplayer-plugin-danmuku .apd-icon {\n cursor: pointer;\n opacity: 0.75;\n transition: all 0.2s ease;\n fill: #fff;\n}\n.artplayer-plugin-danmuku .apd-icon:hover {\n opacity: 1;\n}\n.artplayer-plugin-danmuku .apd-config {\n display: flex;\n position: relative;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 320px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-config-mode,\n.artplayer-plugin-danmuku .apd-config-slider,\n.artplayer-plugin-danmuku .apd-config-other,\n.artplayer-plugin-danmuku .apd-style-mode {\n margin-bottom: 15px;\n}\n.artplayer-plugin-danmuku .apd-modes {\n display: flex;\n align-items: center;\n margin-top: 5px;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode {\n cursor: pointer;\n text-align: center;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-config-slider {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.artplayer-plugin-danmuku .apd-config-slider .apd-value {\n width: 32px;\n text-align: right;\n}\n.artplayer-plugin-danmuku .apd-slider {\n position: relative;\n flex: 1;\n display: flex;\n height: 20px;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-line {\n position: relative;\n height: 2px;\n width: 100%;\n overflow: hidden;\n border-radius: 3px;\n background-color: rgba(255, 255, 255, 0.25);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point {\n width: 2px;\n height: 2px;\n border-radius: 50%;\n background-color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-progress {\n width: 0%;\n height: 100%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-dot {\n position: absolute;\n transform: translateX(-6px);\n left: 0%;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps {\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: absolute;\n bottom: -12px;\n width: calc(100% + 32px);\n color: #777;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step {\n flex-shrink: 0;\n width: 36px;\n text-align: center;\n scale: 0.95;\n}\n.artplayer-plugin-danmuku .apd-config-other {\n display: flex;\n align-items: center;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-check-off,\n.artplayer-plugin-danmuku .apd-config-other .apd-check-on {\n width: 16px;\n height: 16px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other {\n display: flex;\n align-items: center;\n cursor: pointer;\n gap: 2px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-emitter {\n display: flex;\n flex: 1;\n align-items: center;\n height: 100%;\n background-color: rgba(255, 255, 255, 0.25);\n border-radius: 5px;\n}\n.artplayer-plugin-danmuku .apd-style {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 200px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-colors {\n display: flex;\n flex-wrap: wrap;\n margin-top: 5px;\n gap: 8px;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color {\n width: 16px;\n height: 16px;\n border-radius: 2px;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active {\n border: 1px solid black;\n box-shadow: 0 0 0 1px #fff;\n}\n.artplayer-plugin-danmuku .apd-input {\n outline: none;\n height: 100%;\n flex: 1;\n min-width: 0;\n width: auto;\n border: none;\n line-height: 1;\n color: #fff;\n background-color: transparent;\n}\n.artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-send {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n width: 60px;\n flex-shrink: 0;\n cursor: pointer;\n text-shadow: none;\n border-top-right-radius: 5px;\n border-bottom-right-radius: 5px;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-send.apd-lock {\n cursor: not-allowed;\n color: #666;\n background-color: #e7e7e7;\n}\n.art-controls-center .apd-emitter {\n width: 260px;\n flex: none;\n}\n.art-fullscreen .artplayer-plugin-danmuku,\n.art-fullscreen-web .artplayer-plugin-danmuku {\n height: 38px;\n gap: 16px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on {\n width: 28px;\n height: 28px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter {\n width: 400px;\n flex: none;\n}\n.art-video-player > .artplayer-plugin-danmuku {\n position: absolute;\n left: 0;\n right: 0;\n bottom: -40px;\n padding: 0 10px;\n}\n.art-video-player:has(> .artplayer-plugin-danmuku) {\n margin-bottom: 40px;\n}\n[data-danmuku-emitter='false'] .apd-emitter {\n display: none !important;\n}\n[data-danmuku-emitter='false'] .art-controls-center .artplayer-plugin-danmuku {\n justify-content: flex-end;\n gap: 18px;\n}\n[data-danmuku-emitter='false'].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,\n[data-danmuku-emitter='false'].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku {\n gap: 24px;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-icon {\n fill: #333;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-emitter {\n background-color: #f1f2f3;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input {\n color: #000;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(0, 0, 0, 0.3);\n}\n[data-danmuku-visible='false'] .apd-toggle-off {\n display: block;\n}\n[data-danmuku-visible='false'] .apd-toggle-on {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-off {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-on {\n display: block;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-on {\n display: none;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-off {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-on {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-off {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-on {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-off {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-on {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-off {\n display: none;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-off {\n display: block;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-on {\n display: none;\n}\n[data-danmuku-mode0='false'] .art-danmuku [data-mode='0'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-off {\n display: none;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-on {\n display: block;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-off {\n display: block;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-on {\n display: none;\n}\n[data-danmuku-mode1='false'] .art-danmuku [data-mode='1'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-off {\n display: none;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-on {\n display: block;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-off {\n display: block;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-on {\n display: none;\n}\n[data-danmuku-mode2='false'] .art-danmuku [data-mode='2'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-off {\n display: none;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-on {\n display: block;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] path {\n fill: #00a1d6;\n}\n"; + if (typeof document !== "undefined") { + const id = "artplayer-plugin-danmuku"; + const pending = /* @__PURE__ */ Symbol.for("artplayer-plugin-danmuku.pending-style"); + let $style2 = document.getElementById(id) || document[pending]; + if (!$style2) { + $style2 = document.createElement("style"); + $style2.id = id; + if (document.readyState === "loading") { + document[pending] = $style2; + const ready = () => { + document.removeEventListener("DOMContentLoaded", ready); + if (document[pending] === $style2) + delete document[pending]; + const existing = document.getElementById(id); + if (existing) + existing.textContent = $style2.textContent; + else + (document.head || document.documentElement).appendChild($style2); + }; + document.addEventListener("DOMContentLoaded", ready, { once: true }); + } else { + (document.head || document.documentElement).appendChild($style2); + } + } + $style2.textContent = style; + } + class Setting { + constructor(art, danmuku) { + this.art = art; + this.danmuku = danmuku; + this.utils = art.constructor.utils; + const { setStyle } = this.utils; + const { $controlsCenter } = art.template; + this.template = { + $controlsCenter, + $mount: $controlsCenter, + $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 = false; + this.isLock = false; + this.timer = null; + this.lifecycle = new SettingLifecycle(art); + this.destroy = this.destroy.bind(this); + this.lifecycle.own(() => art.off("destroy", this.destroy)); + try { + art.on("destroy", this.destroy); + if (!this.lifecycle.active) + return; + this.lifecycle.write($controlsCenter.style, "display", "flex", (value) => setStyle($controlsCenter, "display", value)); + this.createTemplate(); + if (!this.lifecycle.active) + return; + this.createSliders(); + if (!this.lifecycle.active) + return; + this.createEvents(); + this.mount(this.option.mount); + if (!this.lifecycle.active) + return; + this.lifecycle.on("resize", () => this.resize()); + this.lifecycle.on("fullscreen", (state) => this.onFullscreen(state)); + this.lifecycle.on("fullscreenWeb", (state) => this.onFullscreen(state)); + this.lifecycle.proxy(this.template.$config, "mouseenter", () => { + this.onMouseEnter({ + $control: this.template.$config, + $panel: this.template.$configPanel + }); + }); + this.lifecycle.proxy(this.template.$style, "mouseenter", () => { + this.onMouseEnter({ + $control: this.template.$style, + $panel: this.template.$stylePanel + }); + }); + } catch (error) { + try { + this.destroy(); + } catch (cleanupError) { + console.warn("Failed to roll back danmuku setting:", cleanupError); + } + throw error; + } + } + static get icons() { + return settingIcons(); + } + get option() { + return this.danmuku.option; + } + get outside() { + return this.template.$mount !== this.template.$controlsCenter; + } + get TEMPLATE() { + return settingTemplate.call(this); } get OPACITY() { return { @@ -1141,6 +2046,8 @@ var artplayerPluginDanmuku = (function() { return query(selector, $danmuku); } append(el, target) { + if (!this.lifecycle.active) + return; const { append } = this.utils; const children = [...el.children]; if (children.includes(target)) @@ -1148,19 +2055,21 @@ var artplayerPluginDanmuku = (function() { append(el, target); } setData(key, value) { + if (!this.lifecycle.active) + return; const { $player } = this.art.template; const { $mount } = this.template; - $player.dataset[key] = value; + this.lifecycle.write($player.dataset, key, value); if (this.outside) { - $mount.dataset[key] = value; + this.lifecycle.write($mount.dataset, key, value); } } createTemplate() { const { createElement, tooltip } = this.utils; const $danmuku = createElement("div"); + this.template.$danmuku = $danmuku; $danmuku.className = "artplayer-plugin-danmuku"; $danmuku.innerHTML = this.TEMPLATE; - this.template.$danmuku = $danmuku; this.template.$toggle = this.query(".apd-toggle"); this.template.$config = this.query(".apd-config"); this.template.$configPanel = this.query(".apd-config-panel"); @@ -1182,22 +2091,22 @@ var artplayerPluginDanmuku = (function() { this.template.$input = this.query(".apd-input"); this.template.$send = this.query(".apd-send"); const { $toggle } = this.template; - this.art.on("artplayerPluginDanmuku:show", () => { + this.lifecycle.on("artplayerPluginDanmuku:show", () => { tooltip($toggle, "关闭弹幕"); }); - this.art.on("artplayerPluginDanmuku:hide", () => { + this.lifecycle.on("artplayerPluginDanmuku:hide", () => { tooltip($toggle, "打开弹幕"); }); } createEvents() { const { $toggle, $configModes, $styleModes, $colors, $antiOverlap, $syncVideo, $send, $input } = this.template; - this.art.proxy($toggle, "click", () => { + this.lifecycle.proxy($toggle, "click", () => { this.danmuku.config({ visible: !this.option.visible }); this.reset(); }); - this.art.proxy($configModes, "click", (event) => { + this.lifecycle.proxy($configModes, "click", (event) => { const $mode = event.target.closest(".apd-mode"); if (!$mode) return; @@ -1213,19 +2122,19 @@ var artplayerPluginDanmuku = (function() { } this.reset(); }); - this.art.proxy($antiOverlap, "click", () => { + this.lifecycle.proxy($antiOverlap, "click", () => { this.danmuku.config({ antiOverlap: !this.option.antiOverlap }); this.reset(); }); - this.art.proxy($syncVideo, "click", () => { + this.lifecycle.proxy($syncVideo, "click", () => { this.danmuku.config({ synchronousPlayback: !this.option.synchronousPlayback }); this.reset(); }); - this.art.proxy($styleModes, "click", (event) => { + this.lifecycle.proxy($styleModes, "click", (event) => { const $mode = event.target.closest(".apd-mode"); if (!$mode) return; @@ -1235,7 +2144,7 @@ var artplayerPluginDanmuku = (function() { }); this.reset(); }); - this.art.proxy($colors, "click", (event) => { + this.lifecycle.proxy($colors, "click", (event) => { const $color = event.target.closest(".apd-color"); if (!$color) return; @@ -1244,8 +2153,8 @@ var artplayerPluginDanmuku = (function() { }); this.reset(); }); - this.art.proxy($send, "click", () => this.emit()); - this.art.proxy($input, "keypress", (event) => { + this.lifecycle.proxy($send, "click", () => this.emit()); + this.lifecycle.proxy($input, "keypress", (event) => { if (event.key === "Enter") { event.preventDefault(); this.emit(); @@ -1320,66 +2229,12 @@ var artplayerPluginDanmuku = (function() { } }); } - createSlider({ min, max, container, findIndex, onChange, steps = [] }) { - const { query, clamp, setStyle } = this.utils; - setStyle(container, "touch-action", "none"); - container.innerHTML = ` -
-
- ${steps.map(() => `
`).join("")} -
-
-
-
-
- ${steps.map((step) => step.hide ? "" : `
${step.name}
`).join("")} -
- `; - const $dot = query(".apd-slider-dot", container); - const $progress = query(".apd-slider-progress", container); - let isDroging = false; - function reset(index = findIndex()) { - if (index < min || index > max) - return; - const percentage = (index - min) / (max - min); - $dot.style.left = `${percentage * 100}%`; - if (steps.length === 0) { - $progress.style.width = $dot.style.left; - } - onChange(index); - } - function updateLeft(event) { - const { top, height, left, width } = container.getBoundingClientRect(); - if (this.art.isRotate) { - const value = clamp(event.clientY - top, 0, height); - const index = Math.round(value / height * (max - min) + min); - reset(index); - } else { - const value = clamp(event.clientX - left, 0, width); - const index = Math.round(value / width * (max - min) + min); - reset(index); - } - } - this.art.proxy(container, "click", (event) => { - updateLeft.call(this, event); - }); - this.art.proxy(container, "pointerdown", (event) => { - isDroging = event.button === 0; - }); - this.art.on("document:pointermove", (event) => { - if (isDroging) { - updateLeft.call(this, event); - } - }); - this.art.on("document:pointerup", (event) => { - if (isDroging) { - isDroging = false; - updateLeft.call(this, event); - } - }); - return { reset }; + createSlider(option) { + return createSlider.call(this, option); } onFullscreen(state) { + if (!this.lifecycle.active) + return; const { $danmuku, $controlsCenter, $mount } = this.template; if (this.outside) { if (state) { @@ -1392,6 +2247,8 @@ var artplayerPluginDanmuku = (function() { } } onMouseEnter({ $control, $panel }) { + if (!this.lifecycle.active) + return; const { $player } = this.art.template; const controlRect = $control.getBoundingClientRect(); const panelRect = $panel.getBoundingClientRect(); @@ -1407,66 +2264,18 @@ var artplayerPluginDanmuku = (function() { $panel.style.left = `${-half}px`; } } - async emit() { - const { $input } = this.template; - const text = $input.value.trim(); - if (!text.length) - return; - if (this.isLock) - return; - if (this.emitting) - return; - const danmu = { - text, - mode: this.option.mode, - color: this.option.color, - time: this.art.currentTime - }; - try { - this.emitting = true; - const state = await this.option.beforeEmit(danmu); - this.emitting = false; - if (state !== true) - return; - danmu.border = true; - delete danmu.time; - this.danmuku.emit(danmu); - $input.value = ""; - this.lock(); - } catch (error) { - console.error("Error emitting danmuku:", error); - this.emitting = false; - } + emit() { + return emitSetting(this); } lock() { - const { addClass } = this.utils; - const { $send } = this.template; - this.isLock = true; - let time = this.option.lockTime; - $send.textContent = time; - addClass($send, "apd-lock"); - const loop = () => { - this.timer = setTimeout(() => { - if (time === 0) { - this.unlock(); - } else { - time -= 1; - $send.textContent = time; - loop(); - } - }, 1e3); - }; - loop(); + lockSetting(this); } unlock() { - const { removeClass } = this.utils; - const { $send } = this.template; - clearTimeout(this.timer); - this.isLock = false; - $send.textContent = "发送"; - removeClass($send, "apd-lock"); + unlockSetting(this); } resize() { + if (!this.lifecycle.active) + return; if (this.outside) return; if (this.art.fullscreen) @@ -1482,12 +2291,15 @@ var artplayerPluginDanmuku = (function() { } } reset() { + if (!this.lifecycle.active) + return; const { inverseClass, tooltip } = this.utils; const { $toggle, $colors } = this.template; - this.slider.opacity.reset(); - this.slider.margin.reset(); - this.slider.fontSize.reset(); - this.slider.speed.reset(); + for (const slider of Object.values(this.slider)) { + slider.reset(); + if (!this.lifecycle.active) + return; + } this.setData("danmukuVisible", this.option.visible); this.setData("danmukuMode", this.option.mode); this.setData("danmukuColor", this.option.color); @@ -1505,6 +2317,8 @@ var artplayerPluginDanmuku = (function() { this.resize(); } mount(target) { + if (!this.lifecycle.active) + return; const { errorHandle } = this.utils; const $el = typeof target === "string" ? document.querySelector(target) : target; errorHandle($el, `Can not find the mount point: ${target}`); @@ -1512,29 +2326,38 @@ var artplayerPluginDanmuku = (function() { this.template.$mount = $el; this.reset(); } - } - if (typeof document !== "undefined") { - const id = "artplayer-plugin-danmuku"; - let $style2 = document.getElementById(id); - if (!$style2) { - $style2 = document.createElement("style"); - $style2.id = id; - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", () => { - document.head.appendChild($style2); - }); - } else { - (document.head || document.documentElement).appendChild($style2); - } + destroy() { + if (this.lifecycle.closed) + return; + this.lifecycle.close(); + clearTimeout(this.timer); + this.timer = null; + this.emitting = false; + this.isLock = false; + const $danmuku = this.template.$danmuku; + if ($danmuku?.parentElement) + $danmuku.parentElement.removeChild($danmuku); } - $style2.textContent = style; } function artplayerPluginDanmuku2(option) { return (art) => { const danmuku = new Danmuku(art, option); - const setting = new Setting(art, danmuku); - if (danmuku.option.heatmap) { - heatmap(art, danmuku, danmuku.option.heatmap); + let setting; + try { + setting = art.isDestroy ? void 0 : new Setting(art, danmuku); + if (!art.isDestroy && danmuku.option.heatmap) { + heatmap(art, danmuku, danmuku.option.heatmap); + } + } catch (error) { + try { + setting?.destroy(); + } catch { + } + try { + danmuku.destroy(); + } catch { + } + throw error; } return { name: "artplayerPluginDanmuku", @@ -1544,7 +2367,8 @@ var artplayerPluginDanmuku = (function() { 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 64acad491..d8d78161a 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-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/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-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/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-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js refactor/scripts/danmuku-contract.test.mjs" + "test:danmuku": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js refactor/scripts/danmuku-contract.test.mjs" }, "browserslist": "last 1 Chrome version", "devDependencies": { @@ -120,6 +120,7 @@ "glob": "13.0.6", "lerna": "8.2.4", "less": "4.5.1", + "linkedom": "0.18.13", "prompts": "2.4.2", "semver": "7.7.4", "servor": "4.0.2", diff --git a/packages/artplayer-plugin-danmuku/ARCHITECTURE.md b/packages/artplayer-plugin-danmuku/ARCHITECTURE.md index c06bb9664..b45a6c2e2 100644 --- a/packages/artplayer-plugin-danmuku/ARCHITECTURE.md +++ b/packages/artplayer-plugin-danmuku/ARCHITECTURE.md @@ -15,16 +15,25 @@ repository root. Do not infer runtime contracts from the old declaration file. | `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/scheduler.js` | One RAF or asynchronous frame, cancellation generations, preparation ownership and failure recovery; DOM preparation moves to a renderer in PKG-DANMUKU-05 | +| `src/scheduler.js` | One RAF or asynchronous frame, cancellation generations, preparation ownership and failure recovery | +| `src/renderer.js` | Owned node allocation, preparation, geometry snapshots, placement, pause/resume styling and disposal | | `src/queue.js` | Ordered state pools, eligibility and state transitions | | `src/worker-client.js` | Track Worker lifecycle, a shared response dispatcher, unique request IDs and pending requests | | `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 | +| `src/setting.js` | Settings coordinator, configuration controls, mount and fullscreen layout | +| `src/setting-template.js` | Existing template HTML and static icon values | +| `src/setting-slider.js` | Slider index, pointer and rotation behavior | +| `src/setting-send.js` | Pending send, original error outlet and lock countdown | +| `src/setting-lifecycle.js` | Exact subscription/proxy disposal, cancellation and conditional host-property restoration | +| `src/setting-style.js` | Shared document style and one pending DOMContentLoaded installation | +| `src/heatmap.js` | Owned control, progress stops, subscriptions and per-instance gradient | +| `src/heatmap-sampling.js` | Sorted-time bin counts with historical numeric boundaries | +| `src/heatmap-geometry.js` | Legacy point normalization, caller-visible point writes and SVG curve calculation | The package is undergoing staged migration. PKG-DANMUKU-03 separates input and configuration responsibilities in JavaScript; PKG-DANMUKU-04 separates scheduling -and track requests; PKG-DANMUKU-06 converts all owned +and track requests; PKG-DANMUKU-05 separates rendering and UI resources; +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. @@ -134,7 +143,61 @@ three modes, strict dense geometry, one request per prepared item, native pause/seek/rate and visible-node reuse. Preserve both geometric and protocol assertions; unique IDs alone do not prove non-overlapping tracks. -Settings cleanup, heatmap sampling and long-run load behavior still require -PKG-DANMUKU-05/07. Check task evidence and the risk register before changing -completion or release status; short native regressions are not package-wide -stability acceptance. +Long-run native load behavior still requires PKG-DANMUKU-07. Check task evidence +and the risk register before changing completion or release status; short native +regressions are not package-wide stability acceptance. + +## Rendering and UI resources + +The scheduler owns whether work may proceed; the renderer owns the actual nodes. +Destroy removes those nodes even when the core retains its HTML. It does not +remove unrelated children. Replacement loading retains the historical whole-layer +clear and starts a fresh node pool. Keep cancellation identity checks in the +scheduler/renderer boundary when moving preparation logic. +Replacement also removes owned nodes moved outside the layer before releasing +their ownership records. Unrelated nodes outside the layer are left in place. + +Setting registers its destruction boundary before creating UI. Initialization +failure releases its own resources; the factory also rolls back the previously +created Danmuku and Worker, preserving the original error. Store exact art +subscriptions and proxy disposers. On old cores, use `events.remove` when the +disposer remains registered; calling only the returned function can leave a +registry entry. A disposal failure must not prevent other owned cleanup. + +Pending `beforeEmit` is raced against destruction. Preserve its option receiver +and strict `true` acceptance. Ordinary send inserts synchronously, clears input +and starts the existing countdown; observing the emitted Promise must not delay +that UI behavior. Retain `console.error('Error emitting danmuku:', error)` as the +existing send-error outlet. Destruction prevents late input writes and timers. + +Mount and fullscreen move the same Setting node. Destroy removes only that node. +Dataset and display writes restore the preceding owner/original value only while +the current value is still owned. Shared mounts must retain the surviving +instance, and subsequent user writes must survive. The injected stylesheet is +document-wide, stays available to other instances and retains import-time +installation. Pending DOMContentLoaded installation is shared and released once +it runs; it is not an instance leak. + +Heatmap owns its control and listeners. Removal/replacement detaches them and +must not remove a newer same-name control. The gradient ID is instance-specific; +the first available `heatmap-solids` and local `heatmap-start`/`heatmap-stop` hooks +remain, including repeated bundle evaluation. Zero or invalid sampling +uses a positive default, and invalid dimensions/duration render no SVG. Preserve +fractional step progression and strict-left/inclusive-right time bins. The +original points event clones only the outer array and mutates inner y values; +preserve that observable behavior, including repeated updates. + +`test/danmuku-setting.test.js` uses Linkedom and real published core utilities +for template parsing, selectors, events and lifecycle tests. Linkedom has no +layout; its measured rectangles are controlled inputs, not browser evidence. +`test/browser/danmuku-resources.spec.js` covers native external/shared mounts, +pending sends, timers, retained-core HTML, initialization rollback and independent +heatmaps. Run it with the scheduler/input specs for source and built artifacts. +Dedicated recovery/new-continuation cases establish a ready item as a test +precondition; obsolete gates and NaN use the actual media clock at 0.25 rate. +These lifecycle preconditions do not establish stress-load timestamp delivery. +The renderer resource cases also begin with an explicitly eligible row, then +require native Worker completion and visible DOM before testing replacement or +destruction. The separate pause/seek/rate case observes the original ready getter +without changing its result and records bounded native media-time/queue samples +to distinguish eligibility misses from placement failures. diff --git a/packages/artplayer-plugin-danmuku/README.md b/packages/artplayer-plugin-danmuku/README.md index 835fd40cd..3a1fe9c39 100644 --- a/packages/artplayer-plugin-danmuku/README.md +++ b/packages/artplayer-plugin-danmuku/README.md @@ -45,6 +45,19 @@ If an error listener synchronously stops, destroys, resets or hides the instance the interrupted recovery respects that action. A nested start can recover once without the outer call resuming it again or emitting another start event. +## Settings and heatmap cleanup + +Destroy removes the plugin's external settings panel, cancels its send countdown +and prevents an unfinished `beforeEmit` from clearing input or starting another +send. Other instances and later user changes to their shared mount remain intact. +The shared stylesheet remains installed for other players. Setting initialization +failure also releases the already-created plugin Worker. + +Heatmap supports narrow containers with a positive sampling step and uses an +independent gradient for each instance. Its existing points event still changes +the inner point arrays as before; copy those arrays first if the original values +must be retained. Removing or replacing its control releases its listeners. + See [ARCHITECTURE.md](./ARCHITECTURE.md) for module ownership, compatibility constraints, tests and the remaining staged TypeScript migration. diff --git a/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js b/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js index dc9308ff6..eb2bc2d08 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){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()}function h(t,e,i){const a=t.states[e]||[];for(let n=0;n{if(this.frame=null,t.timer=null,this.closed||this.fault||t.isStop||t.art.isDestroy)return;let e;const i=new Promise(t=>e=()=>t(u)),a={generation:this.generation,cancel:e,wait:t=>Promise.race([t,i]),ref:null};return this.operation=a,this.run(a).catch(t=>{this.active(a)&&this.fail(t)}).finally(()=>{this.release(a),this.operation===a&&(this.operation=null,this.schedule())})}),t.timer=this.frame)}async run(t){const e=this.owner,{setStyles:i}=e.utils;if(!e.art.playing||!this.active(t))return;e.filter("emit",t=>{const i=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=i,t.$lastStartTime=Date.now(),t.$restTime<=0&&e.makeWait(t)});const a=e.readys;for(const s of a){if(!this.active(t))return;if(this.failedItems.has(s))continue;let a;try{a=await t.wait(e.option.beforeVisible(s))}catch(n){if(!this.active(t))return;this.failedItems.add(s),this.report(n);continue}if(!this.active(t))return;if(!a)continue;const{clientWidth:o,clientHeight:r}=e.$player,l=e.$ref;if(t.danmu=s,t.ref=l,s.$ref=l,l.textContent=s.text,e.$danmuku.appendChild(l),l.style.opacity=e.option.opacity,l.style.fontSize=`${e.fontSize}px`,l.style.color=s.color,l.style.border=s.border?`1px solid ${s.color}`:null,l.style.backgroundColor=s.border?"rgb(0 0 0 / 50%)":null,i(l,s.style),!this.active(t))return;s.$lastStartTime=Date.now(),s.$restTime=e.speed;const d=o+l.clientWidth,{result:p}=await t.wait(e.postMessage({type:"getDanmuTop",target:{mode:s.mode,height:l.clientHeight,speed:d/s.$restTime},visibles:e.visibles,antiOverlap:e.option.antiOverlap,clientWidth:o,clientHeight:r,marginBottom:e.marginBottom,marginTop:e.marginTop}));if(!this.active(t)||s.$ref!==l)return;if(void 0!==p){switch(e.setState(s,"emit"),l.style.top=`${p}px`,l.style.visibility="visible",l.dataset.mode=s.mode,l.dataset.id=s.id||"",s.mode){case 0:l.style.left=`${o}px`,l.style.marginLeft="0px",l.style.transform=`translateX(${-d}px)`,l.style.transition=`transform ${s.$restTime}s linear 0s`;break;case 1:case 2:l.style.left="50%",l.style.marginLeft=`-${l.clientWidth/2}px`}t.ref=null,e.art.emit("artplayerPluginDanmuku:visible",s)}else e.setState(s,"ready"),this.release(t)}}destroy(){this.closed=!0,this.invalidate()}}let m=0;class f{constructor(t,e){this.pending=new Map,this.closed=!1,this.failed=!1,this.onFailure=e,this.worker=t();try{this.worker.onmessage=t=>{const e=this.pending.get(t.data?.id);e&&(this.pending.delete(t.data.id),e.resolve(t.data))},this.worker.onerror=t=>{t.preventDefault?.(),this.fail(t.error||t)},this.worker.onmessageerror=t=>this.fail(t.error||t)}catch(i){try{this.dispose()}catch{}throw i}}request(t){t.id=++m;const{id:e}=t;return this.closed||this.failed?Promise.resolve({id:e,result:void 0}):new Promise((i,a)=>{this.pending.set(e,{resolve:i,reject:a});try{this.worker.postMessage(t)}catch(n){this.fail(n)}})}fail(t){if(!this.closed&&!this.failed){this.failed=!0;for(const e of this.pending.values())e.reject(t);this.pending.clear();try{this.dispose()}catch{}this.onFailure(t)}}cancel(){for(const[t,e]of this.pending)e.resolve({id:t,result:void 0});this.pending.clear()}dispose(){if(this.closed)return;this.closed=!0,this.cancel();let t,e=!1;const i=i=>{try{i()}catch(a){e||(t=a),e=!0}};for(const a of["onmessage","onerror","onmessageerror"])i(()=>this.worker[a]=null);if(i(()=>this.worker.terminate()),e)throw t}}const g='/*!\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',y="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",g],{type:"text/javascript;charset=utf-8"});function v(t){let e;try{if(e=y&&(self.URL||self.webkitURL).createObjectURL(y),!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(g),{type:"module",name:t?.name})}}class k{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.workerClient=null,this.scheduler=new c(this),this.option=k.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),this.seek=this.seek.bind(this),d(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!d(this))return;this.createWorker()}catch(n){throw t.off("destroy",this.destroy),p(this),this.scheduler.destroy(),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),t.on("video:seeking",this.seek),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 k.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 k.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 k.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=k.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){return function(t){const{currentTime:e}=t.art,i=[];return h(t,"ready",t=>i.push(t)),h(t,"wait",t=>{e+.1>=t.time&&t.time>=e-.1&&i.push(t)}),i}(this)}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:k.option,validate:t=>this.validator(t,k.scheme),clamp:a,mount:n});if(!d(this))return this;const l=this.option.beforeVisible!==r.beforeVisible;return this.option=r,l&&(this.scheduler.invalidate(),this.scheduler.schedule()),e.fontSize&&(this.reset(),!d(this))||(this.option.visible?this.show():this.hide(),d(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return this.workerClient.request(t)}createWorker(){this.workerClient=new f(()=>new v,t=>this.scheduler.fail(t)),this.worker=this.workerClient.worker}filter(t,e){return h(this,t,e)}setState(t,e){!function(t,e,i){t.states[e.$state]=t.states[e.$state].filter(t=>t!==e),e.$state=i,e.$ref&&(e.$ref.dataset.state=i),t.states[i].push(e)}(this,t,e)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=k.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(){return this.scheduler.running=!0,this.scheduler.schedule(),this}resize(){const{clientWidth:t}=this.$player;this.filter("stop",e=>{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.scheduler.running=!1,this.scheduler.invalidate(),this.suspend(),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){if(this.scheduler.closed||this.art.isDestroy)return this;const t=++this.scheduler.starts,e=this.scheduler.generation;this.isStop=!1,this.scheduler.recover();return this.scheduler.closed||this.art.isDestroy||this.isStop||this.scheduler.fault||e!==this.scheduler.generation||t!==this.scheduler.starts||(this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start")),this}reset(){return this.scheduler.invalidate(),this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this.scheduler.recover(),this.scheduler.schedule(),this}seek(){this.scheduler.invalidate(),this.scheduler.schedule()}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.scheduler.invalidate(),this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this.scheduler.schedule(),this}destroy(){p(this),this.scheduler.destroy();try{try{this.stop()}finally{this.workerClient?.dispose()}}finally{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("video:seeking",this.seek),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}}const w={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,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]),f=(Math.min(...m)+Math.max(...m))/2;for(let t=0;tf?1+d.scale:1-d.scale)+d.minHeight}const g=(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=w.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]},y=p.map(t=>[w.map(t[0],d.xMin,d.xMax,0,l.w),w.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=g(i[e-1],i[e-2],t),n=g(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 $='',b='',S='',M='',z='',C='',E='',D='',F='',T='',A='',L='';class P{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:T,$config:S,$style:L,$mode_0_off:M,$mode_0_on:z,$mode_1_off:C,$mode_1_on:E,$mode_2_off:D,$mode_2_on:F,$check_on:b,$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}${T}\n
\n
\n ${S}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${M}${z}\n
\u6eda\u52a8
\n
\n
\n ${C}${E}\n
\u9876\u90e8
\n
\n
\n ${D}${F}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${b}${$}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${b}${$}\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 ${L}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${z}\n
\u6eda\u52a8
\n
\n
\n ${E}\n
\u9876\u90e8
\n
\n
\n ${F}\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 H(t){return e=>{const i=new k(e,t),a=e.isDestroy?void 0:new P(e,i);return!e.isDestroy&&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?a.mount.bind(a):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return H.icons=P.icons,H}); +!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 n({data:t}){const{xml:e,id:n}=t,a=i(e);globalThis.postMessage({danmus:a,id:n})}let a=0;function s(t,{signal:s,onCancel:r}={}){return new Promise((o,l)=>{let d,c,p,h=!1;const u=++a,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!==c){const n=c;c=void 0;try{URL.revokeObjectURL(n)}catch(i){e||(t=i),e=!0}}if(e)throw t}function g(t,e){if(h)return;h=!0;let i=arguments.length>1;s?.removeEventListener("abort",m),p?.();try{f()}catch(n){i||(e=n),i=!0}i?l(e):o(t)}function y(t,e){if(!h){console.error("Error parsing Bilibili Danmu:",e);try{g(i(t))}catch(n){g(void 0,n)}}}s?.aborted?m():(s?.addEventListener("abort",m),p=r?.(m),h?p?.():async function(){const a=s?await fetch(t,{signal:s}):await fetch(t);if(h)return;const r=await a.text();if(!h)try{const t=`\n ${e.toString()}\n ${i.toString()}\n onmessage = ${n.toString()}\n `;if(c=URL.createObjectURL(new Blob([t],{type:"application/javascript"})),h)return void f();if(d=new Worker(c),h)return void f();d.onmessage=t=>{if(h||t.data?.id!==u)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(r,t.error||t)},d.onmessageerror=t=>y(r,t.error||t),d.postMessage({xml:r,id:u})}catch(o){y(r,o)}}().catch(t=>g(void 0,t)))})}const r=new WeakMap,o=Symbol("cancelled danmuku input");function l(t){return r.has(t)||r.set(t,{closed:!1,tasks:new Set,emitting:[]}),r.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 c(t){const e=l(t);e.closed=!0;for(const i of[...e.tasks])i.cancel()}function p(t,e,i){const n=t.states[e]||[];for(let a=0;a{const a=n.$ref.offsetTop,s=this.owner.getLeft(n.$ref)-i,r=n.$ref.clientHeight,o=n.$ref.clientWidth,l=s+o,d=e-l,c=l/n.$restTime,p={};p.top=a,p.left=s,p.height=r,p.width=o,p.right=d,p.speed=c,p.distance=l,p.time=n.$restTime,p.mode=n.mode,t.push(p)}),t}left(t){const e=t.getBoundingClientRect();return this.owner.isRotate?e.top:e.left}makeWait(t){this.owner.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=this.owner.constructor.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.owner.$refs.push(t.$ref),t.$ref=null)}resize(){const{clientWidth:t}=this.owner.$player;this.owner.filter("stop",e=>{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.owner.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.owner.$player;return this.owner.filter("stop",e=>{switch(this.owner.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.owner.$player;return this.owner.filter("emit",e=>{switch(this.owner.setState(e,"stop"),e.mode){case 0:{const i=t-(this.owner.getLeft(e.$ref)-this.owner.getLeft(this.owner.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}prepare(t,e){const i=this.owner,n=i.$ref;return e.danmu=t,e.ref=n,t.$ref=n,n.textContent=t.text,i.$danmuku.appendChild(n),n.style.opacity=i.option.opacity,n.style.fontSize=`${i.fontSize}px`,n.style.color=t.color,n.style.border=t.border?`1px solid ${t.color}`:null,n.style.backgroundColor=t.border?"rgb(0 0 0 / 50%)":null,i.utils.setStyles(n,t.style),n}place(t,e,i,n,a){switch(e.style.top=`${i}px`,e.style.visibility="visible",e.dataset.mode=t.mode,e.dataset.id=t.id||"",t.mode){case 0:e.style.left=`${a}px`,e.style.marginLeft="0px",e.style.transform=`translateX(${-n}px)`,e.style.transition=`transform ${t.$restTime}s linear 0s`;break;case 1:case 2:e.style.left="50%",e.style.marginLeft=`-${e.clientWidth/2}px`}}release(t){const{danmu:e,ref:i}=t;i&&e.$ref===i&&(i.style.cssText=this.owner.constructor.cssText,i.style.visibility="hidden",this.owner.$refs.push(i),e.$ref=null),t.ref=null}clear(){this.destroy(),this.owner.$refs=[],this.owner.$danmuku.textContent=""}destroy(){for(const t of this.nodes)t.parentElement&&t.parentElement.removeChild(t);for(const t of this.owner.queue)t.$ref=null;this.owner.$refs.length=0,this.nodes.clear()}}const u=Symbol("cancelled danmuku frame");class m{constructor(t){this.owner=t,this.generation=0,this.starts=0,this.frame=null,this.operation=null,this.closed=!1,this.running=!1,this.fault=!1,this.failedItems=new Set}active(t){const e=this.owner;return!this.closed&&!this.fault&&!e.art.isDestroy&&!e.isStop&&!e.isHide&&t.generation===this.generation}release(t){this.owner.renderer.release(t)}invalidate(){if(this.generation++,null!==this.frame&&window.cancelAnimationFrame(this.frame),this.frame=null,this.owner.timer=null,this.operation){const t=this.operation;this.operation=null,t.cancel(),this.release(t)}this.owner.workerClient?.cancel(),this.failedItems.clear()}report(t){try{this.owner.art.emit("artplayerPluginDanmuku:error",t)}catch(e){console.warn("Failed to report danmuku scheduling error:",e)}}fail(t){this.closed||this.fault||(this.fault=!0,this.invalidate(),this.report(t))}recover(){if(!this.closed&&!this.owner.art.isDestroy&&(this.failedItems.clear(),this.fault))try{this.owner.workerClient?.dispose(),this.owner.createWorker(),this.fault=!1}catch(t){this.report(t)}}schedule(){const t=this.owner;!this.running||this.closed||this.fault||t.art.isDestroy||t.isStop||null!==this.frame||this.operation||(this.frame=window.requestAnimationFrame(()=>{if(this.frame=null,t.timer=null,this.closed||this.fault||t.isStop||t.art.isDestroy)return;let e;const i=new Promise(t=>e=()=>t(u)),n={generation:this.generation,cancel:e,wait:t=>Promise.race([t,i]),ref:null};return this.operation=n,this.run(n).catch(t=>{this.active(n)&&this.fail(t)}).finally(()=>{this.release(n),this.operation===n&&(this.operation=null,this.schedule())})}),t.timer=this.frame)}async run(t){const e=this.owner;if(!e.art.playing||!this.active(t))return;e.filter("emit",t=>{const i=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=i,t.$lastStartTime=Date.now(),t.$restTime<=0&&e.makeWait(t)});const i=e.readys;for(const a of i){if(!this.active(t))return;if(this.failedItems.has(a))continue;let i;try{i=await t.wait(e.option.beforeVisible(a))}catch(n){if(!this.active(t))return;this.failedItems.add(a),this.report(n);continue}if(!this.active(t))return;if(!i)continue;const{clientWidth:s,clientHeight:r}=e.$player,o=e.renderer.prepare(a,t);if(!this.active(t))return;a.$lastStartTime=Date.now(),a.$restTime=e.speed;const l=s+o.clientWidth,{result:d}=await t.wait(e.postMessage({type:"getDanmuTop",target:{mode:a.mode,height:o.clientHeight,speed:l/a.$restTime},visibles:e.visibles,antiOverlap:e.option.antiOverlap,clientWidth:s,clientHeight:r,marginBottom:e.marginBottom,marginTop:e.marginTop}));if(!this.active(t)||a.$ref!==o)return;void 0!==d?(e.setState(a,"emit"),e.renderer.place(a,o,d,l,s),t.ref=null,e.art.emit("artplayerPluginDanmuku:visible",a)):(e.setState(a,"ready"),this.release(t))}}destroy(){this.closed=!0,this.invalidate()}}let f=0;class g{constructor(t,e){this.pending=new Map,this.closed=!1,this.failed=!1,this.onFailure=e,this.worker=t();try{this.worker.onmessage=t=>{const e=this.pending.get(t.data?.id);e&&(this.pending.delete(t.data.id),e.resolve(t.data))},this.worker.onerror=t=>{t.preventDefault?.(),this.fail(t.error||t)},this.worker.onmessageerror=t=>this.fail(t.error||t)}catch(i){try{this.dispose()}catch{}throw i}}request(t){t.id=++f;const{id:e}=t;return this.closed||this.failed?Promise.resolve({id:e,result:void 0}):new Promise((i,n)=>{this.pending.set(e,{resolve:i,reject:n});try{this.worker.postMessage(t)}catch(a){this.fail(a)}})}fail(t){if(!this.closed&&!this.failed){this.failed=!0;for(const e of this.pending.values())e.reject(t);this.pending.clear();try{this.dispose()}catch{}this.onFailure(t)}}cancel(){for(const[t,e]of this.pending)e.resolve({id:t,result:void 0});this.pending.clear()}dispose(){if(this.closed)return;this.closed=!0,this.cancel();let t,e=!1;const i=i=>{try{i()}catch(n){e||(t=n),e=!0}};for(const n of["onmessage","onerror","onmessageerror"])i(()=>this.worker[n]=null);if(i(()=>this.worker.terminate()),e)throw t}}const y='/*!\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',v="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",y],{type:"text/javascript;charset=utf-8"});function w(t){let e;try{if(e=v&&(self.URL||self.webkitURL).createObjectURL(v),!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(y),{type:"module",name:t?.name})}}class k{constructor(t,e){const{constructor:i,template:n}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=n.$danmuku,this.$player=n.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.worker=null,this.workerClient=null,this.renderer=new h(this),this.scheduler=new m(this),this.option=k.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),this.seek=this.seek.bind(this),d(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!d(this))return;this.createWorker()}catch(a){throw t.off("destroy",this.destroy),c(this),this.scheduler.destroy(),a}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),t.on("video:seeking",this.seek),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 k.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 k.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 n=Number.parseFloat(i)/100;return Math.round(t(e*n,12,e))}return k.option.fontSize}get $ref(){return this.renderer.acquire()}get readys(){return function(t){const{currentTime:e}=t.art,i=[];return p(t,"ready",t=>i.push(t)),p(t,"wait",t=>{e+.1>=t.time&&t.time>=e-.1&&i.push(t)}),i}(this)}get visibles(){return this.renderer.visibles}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,n=function(t,e){const i=l(t);if(e)for(const o of[...i.tasks])o.replace&&o.cancel();let n,a=i.closed||t.art.isDestroy;const s=new Promise(t=>n=t),r=new Set,d="function"===typeof AbortController?new AbortController:void 0,c={replace:e,signal:d?.signal,active:()=>!a&&!i.closed&&!t.art.isDestroy,onCancel:t=>c.active()?(r.add(t),()=>r.delete(t)):(t(),()=>{}),cancel(){if(!a){a=!0,n(o),d?.abort();for(const t of[...r])t();r.clear()}},wait:t=>Promise.race([t,s]),emit(e){i.emitting.push(c);try{return t.emit(e)}finally{i.emitting.pop()}},finish(){i.tasks.delete(c),r.clear()}};return a?n(o):i.tasks.add(c),c}(this,void 0===e);try{if(!n.active())return this;const a=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,n),r=a.asynchronous?await n.wait(a.value):a.value;if(!n.active())return this;if(i(Array.isArray(r),"Danmuku need return an array as result"),void 0===e){if(this.reset(),!n.active())return this;this.queue=[],this.states={wait:[],ready:[],emit:[],stop:[]},this.renderer.clear()}for(let t=0;t{const i=s[e],n=r[e];return"function"===typeof i||"function"===typeof n||t(i)||t(n)?i!==n:JSON.stringify(i)!==JSON.stringify(n)}))&&!i)return this;const o=function(t,e,{defaults:i,validate:n,clamp:a,mount:s}){const r=Object.assign({},i,t,e);return n(r),r.mode=a(r.mode,0,2),r.speed=a(r.speed,1,10),r.opacity=a(r.opacity,0,1),r.lockTime=a(r.lockTime,1,60),r.maxLength=a(r.maxLength,1,1e3),r.mount=r.mount||s,r}(this.option,e,{defaults:k.option,validate:t=>this.validator(t,k.scheme),clamp:n,mount:a});if(!d(this))return this;const l=this.option.beforeVisible!==o.beforeVisible;return this.option=o,l&&(this.scheduler.invalidate(),this.scheduler.schedule()),e.fontSize&&(this.reset(),!d(this))||(this.option.visible?this.show():this.hide(),d(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this}getLeft(t){return this.renderer.left(t)}postMessage(t={}){return this.workerClient.request(t)}createWorker(){this.workerClient=new g(()=>new w,t=>this.scheduler.fail(t)),this.worker=this.workerClient.worker}filter(t,e){return p(this,t,e)}setState(t,e){!function(t,e,i){t.states[e.$state]=t.states[e.$state].filter(t=>t!==e),e.$state=i,e.$ref&&(e.$ref.dataset.state=i),t.states[i].push(e)}(this,t,e)}makeWait(t){this.renderer.makeWait(t)}update(){return this.scheduler.running=!0,this.scheduler.schedule(),this}resize(){this.renderer.resize()}continue(){return this.renderer.continue(),this}suspend(){return this.renderer.suspend(),this}stop(){return this.isStop=!0,this.scheduler.running=!1,this.scheduler.invalidate(),this.suspend(),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){if(this.scheduler.closed||this.art.isDestroy)return this;const t=++this.scheduler.starts,e=this.scheduler.generation;this.isStop=!1,this.scheduler.recover();return this.scheduler.closed||this.art.isDestroy||this.isStop||this.scheduler.fault||e!==this.scheduler.generation||t!==this.scheduler.starts||(this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start")),this}reset(){return this.scheduler.invalidate(),this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this.scheduler.recover(),this.scheduler.schedule(),this}seek(){this.scheduler.invalidate(),this.scheduler.schedule()}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.scheduler.invalidate(),this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this.scheduler.schedule(),this}destroy(){c(this),this.scheduler.destroy();try{try{this.stop()}finally{this.workerClient?.dispose()}}finally{this.renderer.destroy(),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("video:seeking",this.seek),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}}const x=4294967295;function $(t,e){let i=0,n=t.length;for(;iNumber.isFinite(t)&&t>0))return null;const r={xMin:0,xMax:t,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*e),sampling:Math.max(1,Math.floor(t/100)),smoothing:.2,flattening:.2},o=Object.assign({},r,"object"===typeof a?a:{});for(const g of Object.keys(r))Number.isFinite(o[g])||(o[g]=r[g]);if(o.sampling<=0&&(o.sampling=r.sampling),o.xMin===o.xMax||o.yMin===o.yMax)return null;const l=Array.isArray(s)&&s.length?[...s]:function(t,e,i,n){if(!Number.isFinite(e)||e<=0||!Number.isFinite(i)||i<=0||!Number.isFinite(n)||n<=0||Math.floor(e/n)+1>x)return[];const a=t.map(t=>t.time).filter(t=>"number"===typeof t&&!Number.isNaN(t)).sort((t,e)=>t-e),s=[],r=i/e;if(!Number.isFinite(r))return[];for(let o=0;o<=e;o+=n){if(s.length>=x||o+n===o)return[];const t=o*r,e=(o+n)*r;s.push([o,$(a,e)-$(a,t)])}return s}(n,t,i,o.sampling);if(!l.length||!l.every(t=>Array.isArray(t)&&Number.isFinite(t[0])&&Number.isFinite(t[1])))return null;const d=l[l.length-1];if(d[0]!==t){if(l.length===x)return null;l.push([t,d[1]])}let c=1/0,p=-1/0;for(const g of l)c=Math.min(c,g[1]),p=Math.max(p,g[1]);const h=(c+p)/2;for(const g of l){const t=g[1];g[1]=t*(t>h?1+o.scale:1-o.scale)+o.minHeight}const u=(t,e,i,n)=>{const a=function(t,e){const i=e[0]-t[0],n=e[1]-t[1];return{length:Math.sqrt(i**2+n**2),angle:Math.atan2(n,i)}}(e||t,i||t),s=b(Math.cos(a.angle)*o.flattening,0,1,1,0),r=a.angle*s+(n?Math.PI:0),l=a.length*o.smoothing;return[t[0]+Math.cos(r)*l,t[1]+Math.sin(r)*l]},m=l.map(i=>[b(i[0],o.xMin,o.xMax,0,t),b(i[1],o.yMin,o.yMax,e,0)]);if(!m.every(t=>t.every(Number.isFinite)))return null;const f=m.map((t,i,n)=>{if(0===i)return`M ${n[n.length-1][0]},${e} L ${t[0]},${e} L ${t[0]},${t[1]}`;const a=u(n[i-1],n[i-2],t),s=u(t,n[i-1],n[i+1],!0);return`C ${a[0]},${a[1]} ${s[0]},${s[1]} ${t[0]},${t[1]}${i===n.length-1?" z":""}`}).join(" ");return/NaN|Infinity/u.test(f)?null:{path:f,opacity:o.opacity,width:t,height:e}}let S=0;function z(t,e,i){if(t.isDestroy)return;const{query:n}=t.constructor.utils;let a;const s=[];let r,o,l,d=!1;const c=()=>!d&&!t.isDestroy;function p(e,i){s.push([e,i]),t.on(e,i)}function h(e){if(d)return;let i;d=!0,o=null,l=null;let n=!1;const a=t=>{try{t()}catch(e){n||(i=e),n=!0}};for(const[r,o]of s.splice(0))a(()=>t.off(r,o));if(e&&r&&t.controls.heatmap===r&&a(()=>t.controls.remove("heatmap")),r=void 0,n)throw i}function u(t){c()&&o&&l&&(o.setAttribute("offset",100*t+"%"),l.setAttribute("offset",100*t+"%"))}function m(s){try{!function(s=[]){if(!c())return;if(o=null,l=null,r.innerHTML="",t.option.isLive)return;const d=M({width:r.offsetWidth,height:r.offsetHeight,duration:t.duration,queue:e.queue,option:i,points:s});if(!d||!c())return;const p=r.ownerDocument;for(a||(a=p?"heatmap-solids":"heatmap-solids-"+ ++S);p?.getElementById(a);)a="heatmap-solids-"+ ++S;r.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,o=n("#heatmap-start",r),l=n("#heatmap-stop",r),u(t.played)}(s)}catch(d){try{h(!0)}catch{}throw d}}p("destroy",()=>h(!0));try{t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},beforeUnmount(){h(!1)},mounted(e){if(r=e,d)return t.controls.heatmap===e&&t.controls.remove("heatmap"),void(r=void 0);c()?(p("video:timeupdate",()=>u(t.played)),p("setBar",(t,e)=>{"played"===t&&u(e)}),p("ready",()=>m()),p("resize",()=>m()),p("artplayerPluginDanmuku:loaded",()=>m()),p("artplayerPluginDanmuku:points",t=>m(t))):h(!0)}})}catch(f){try{h(!0)}catch{}throw f}return()=>h(!0)}const E=new WeakMap;class C{constructor(t){this.art=t,this.closed=!1,this.disposers=new Set,this.cancelled=new Promise(t=>this.cancel=t)}get active(){return!this.closed&&!this.art.isDestroy}own(t){this.closed?t():this.disposers.add(t)}write(t,e,i,n=i=>t[e]=i){if(!this.active)return;const a=()=>"function"===typeof t.getPropertyPriority?t.getPropertyPriority(e):void 0;let s=E.get(t);s||(s=new Map,E.set(t,s));let r=s.get(e);r||(r={original:t[e],present:e in t||void 0!==t[e],priority:a(),owners:new Map,apply:n},s.set(e,r)),r.owners.has(this)||this.own(()=>{const i=[...r.owners.keys()],n=i[i.length-1]===this;if(r.owners.delete(this),n&&t[e]===r.last&&a()===r.lastPriority){const i=[...r.owners.values()];i.length?(r.apply(i[i.length-1]),r.last=t[e],r.lastPriority=a()):void 0!==r.priority?t.setProperty(e,r.original,r.priority):r.present?r.apply(r.original):delete t[e]}r.owners.size||(s.delete(e),s.size||E.delete(t))}),r.owners.delete(this),r.owners.set(this,i),r.apply(i),r.last=t[e],r.lastPriority=a()}on(t,e){if(!this.active)return;const i=this;function n(...t){if(i.active)return e.apply(this,t)}const a=()=>this.art.off(t,n);this.own(a);try{this.art.on(t,n),this.active||a()}catch(s){throw this.release(a),s}}proxy(t,e,i){if(!this.active)return;const n=this;function a(...t){if(n.active)return i.apply(this,t)}let s;const r=()=>{if("function"===typeof s){const t=this.art.events;"function"===typeof t?.remove&&t.destroyEvents?.has(s)?t.remove(s):s()}else t.removeEventListener?.(e,a)};this.own(r);try{s=this.art.proxy(t,e,a),this.active||r()}catch(o){throw this.release(r),o}}wait(t){return Promise.race([t,this.cancelled])}release(t){try{t()}catch(e){console.warn("Failed to dispose danmuku setting resource:",e)}}close(){if(!this.closed){this.closed=!0,this.cancel();for(const t of this.disposers)this.release(t);this.disposers.clear()}}}function F({min:t,max:e,container:i,findIndex:n,onChange:a,steps:s=[]}){const{lifecycle:r}=this;if(!r.active)return{reset(){}};const{query:o,clamp:l,setStyle:d}=this.utils;d(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 c=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i){if(!r.active)return;if(void 0===i&&(i=n()),ie)return;const o=(i-t)/(e-t);c.style.left=100*o+"%",0===s.length&&(p.style.width=c.style.left),a(i)}function m(n){const{top:a,height:s,left:r,width:o}=i.getBoundingClientRect();if(this.art.isRotate){const i=l(n.clientY-a,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=l(n.clientX-r,0,o);u(Math.round(i/o*(e-t)+t))}}return r.own(()=>h=!1),r.proxy(i,"click",t=>{m.call(this,t)}),r.proxy(i,"pointerdown",t=>{h=0===t.button}),r.on("document:pointermove",t=>{h&&m.call(this,t)}),r.on("document:pointerup",t=>{h&&(h=!1,m.call(this,t))}),{reset:u}}const D='',T='',P='',A='',L='',H='',O='',V='',N='',q='',I='',j='';function B(){const{option:t}=this;return`\n
\n ${I}${q}\n
\n
\n ${P}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${A}${L}\n
\u6eda\u52a8
\n
\n
\n ${H}${O}\n
\u9876\u90e8
\n
\n
\n ${V}${N}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${T}${D}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${T}${D}\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 ${j}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${L}\n
\u6eda\u52a8
\n
\n
\n ${O}\n
\u9876\u90e8
\n
\n
\n ${N}\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 `}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku",e=Symbol.for("artplayer-plugin-danmuku.pending-style");let i=document.getElementById(t)||document[e];if(!i)if(i=document.createElement("style"),i.id=t,"loading"===document.readyState){document[e]=i;const n=()=>{document.removeEventListener("DOMContentLoaded",n),document[e]===i&&delete document[e];const a=document.getElementById(t);a?a.textContent=i.textContent:(document.head||document.documentElement).appendChild(i)};document.addEventListener("DOMContentLoaded",n,{once:!0})}else(document.head||document.documentElement).appendChild(i);i.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}'}class R{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:n}=t.template;this.template={$controlsCenter:n,$mount:n,$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.lifecycle=new C(t),this.destroy=this.destroy.bind(this),this.lifecycle.own(()=>t.off("destroy",this.destroy));try{if(t.on("destroy",this.destroy),!this.lifecycle.active)return;if(this.lifecycle.write(n.style,"display","flex",t=>i(n,"display",t)),this.createTemplate(),!this.lifecycle.active)return;if(this.createSliders(),!this.lifecycle.active)return;if(this.createEvents(),this.mount(this.option.mount),!this.lifecycle.active)return;this.lifecycle.on("resize",()=>this.resize()),this.lifecycle.on("fullscreen",t=>this.onFullscreen(t)),this.lifecycle.on("fullscreenWeb",t=>this.onFullscreen(t)),this.lifecycle.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),this.lifecycle.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}catch(a){try{this.destroy()}catch(s){console.warn("Failed to roll back danmuku setting:",s)}throw a}}static get icons(){return{$on:I,$off:q,$config:P,$style:j,$mode_0_off:A,$mode_0_on:L,$mode_1_off:H,$mode_1_on:O,$mode_2_off:V,$mode_2_on:N,$check_on:T,$check_off:D}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){return B.call(this)}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){if(!this.lifecycle.active)return;const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){if(!this.lifecycle.active)return;const{$player:i}=this.art.template,{$mount:n}=this.template;this.lifecycle.write(i.dataset,t,e),this.outside&&this.lifecycle.write(n.dataset,t,e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");this.template.$danmuku=i,i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,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:n}=this.template;this.lifecycle.on("artplayerPluginDanmuku:show",()=>{e(n,"\u5173\u95ed\u5f39\u5e55")}),this.lifecycle.on("artplayerPluginDanmuku:hide",()=>{e(n,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:n,$antiOverlap:a,$syncVideo:s,$send:r,$input:o}=this.template;this.lifecycle.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.lifecycle.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.lifecycle.proxy(a,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.lifecycle.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.lifecycle.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.lifecycle.proxy(n,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.lifecycle.proxy(r,"click",()=>this.emit()),this.lifecycle.proxy(o,"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(t){return F.call(this,t)}onFullscreen(t){if(!this.lifecycle.active)return;const{$danmuku:e,$controlsCenter:i,$mount:n}=this.template;this.outside?t?this.append(i,e):this.append(n,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){if(!this.lifecycle.active)return;const{$player:i}=this.art.template,n=t.getBoundingClientRect(),a=e.getBoundingClientRect(),s=i.getBoundingClientRect(),r=a.width/2-n.width/2,o=s.left-(n.left-r),l=n.right+r-s.right;e.style.left=o>0?`${-r+o}px`:l>0?-r-l+"px":-r+"px"}emit(){return async function(t){const{lifecycle:e,template:{$input:i}}=t;if(!e.active)return;const n=i.value.trim();if(!n.length||t.isLock||t.emitting)return;const a={text:n,mode:t.option.mode,color:t.option.color,time:t.art.currentTime},s=t=>{e.active&&console.error("Error emitting danmuku:",t)};try{t.emitting=!0;const n=await e.wait(t.option.beforeEmit(a));if(!e.active)return;if(t.emitting=!1,!0!==n)return;if(a.border=!0,delete a.time,Promise.resolve(t.danmuku.emit(a)).catch(s),!e.active)return;i.value="",t.lock()}catch(r){s(r),e.active&&(t.emitting=!1)}}(this)}lock(){!function(t){if(!t.lifecycle.active)return;const{addClass:e}=t.utils,{$send:i}=t.template;clearTimeout(t.timer),t.isLock=!0;let n=t.option.lockTime;i.textContent=n,e(i,"apd-lock");const a=()=>{t.timer=setTimeout(()=>{t.lifecycle.active&&(0===n?t.unlock():(n-=1,i.textContent=n,a()))},1e3)};a()}(this)}unlock(){!function(t){if(clearTimeout(t.timer),t.timer=null,t.isLock=!1,!t.lifecycle.active)return;const{removeClass:e}=t.utils,{$send:i}=t.template;i.textContent="\u53d1\u9001",e(i,"apd-lock")}(this)}resize(){if(!this.lifecycle.active)return;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){if(!this.lifecycle.active)return;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()}destroy(){if(this.lifecycle.closed)return;this.lifecycle.close(),clearTimeout(this.timer),this.timer=null,this.emitting=!1,this.isLock=!1;const t=this.template.$danmuku;t?.parentElement&&t.parentElement.removeChild(t)}}function Z(t){return e=>{const i=new k(e,t);let n;try{n=e.isDestroy?void 0:new R(e,i),!e.isDestroy&&i.option.heatmap&&z(e,i,i.option.heatmap)}catch(a){try{n?.destroy()}catch{}try{i.destroy()}catch{}throw a}return{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:n?n.mount.bind(n):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return Z.icons=R.icons,Z}); 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 0052ae2a5..206abcd73 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,n=Object.getOwnPropertySymbols,a=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,o=Math.pow,r=(e,i,n)=>i in e?t(e,i,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[i]=n,l=(t,e)=>{for(var i in e||(e={}))a.call(e,i)&&r(t,i,e[i]);if(n)for(var i of n(e))s.call(e,i)&&r(t,i,e[i]);return t},d=(t,n)=>e(t,i(n)),p=(t,e,i)=>new Promise((n,a)=>{var s=t=>{try{r(i.next(t))}catch(e){a(e)}},o=t=>{try{r(i.throw(t))}catch(e){a(e)}},r=t=>t.done?n(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,n=c(e);globalThis.postMessage({danmus:n,id:i})}let f=0;function g(t,{signal:e,onCancel:i}={}){return new Promise((n,a)=>{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 n=o;o=void 0;try{URL.revokeObjectURL(n)}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?a(i):n(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 n=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(n,t.error||t)},s.onmessageerror=t=>v(n,t.error||t),s.postMessage({xml:n,id:d})}catch(a){v(n,a)}})}().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 w(t){const e=k(t),i=e.emitting[e.emitting.length-1];return!e.closed&&!t.art.isDestroy&&(!i||i.active())}function x(t){const e=k(t);e.closed=!0;for(const i of[...e.tasks])i.cancel()}function $(t,e,i){const n=t.states[e]||[];for(let a=0;a{if(this.frame=null,t.timer=null,this.closed||this.fault||t.isStop||t.art.isDestroy)return;let e;const i=new Promise(t=>e=()=>t(b)),n={generation:this.generation,cancel:e,wait:t=>Promise.race([t,i]),ref:null};return this.operation=n,this.run(n).catch(t=>{this.active(n)&&this.fail(t)}).finally(()=>{this.release(n),this.operation===n&&(this.operation=null,this.schedule())})}),t.timer=this.frame)}run(t){return p(this,null,function*(){const e=this.owner,{setStyles:i}=e.utils;if(!e.art.playing||!this.active(t))return;e.filter("emit",t=>{const i=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=i,t.$lastStartTime=Date.now(),t.$restTime<=0&&e.makeWait(t)});const n=e.readys;for(const s of n){if(!this.active(t))return;if(this.failedItems.has(s))continue;let n;try{n=yield t.wait(e.option.beforeVisible(s))}catch(a){if(!this.active(t))return;this.failedItems.add(s),this.report(a);continue}if(!this.active(t))return;if(!n)continue;const{clientWidth:o,clientHeight:r}=e.$player,l=e.$ref;if(t.danmu=s,t.ref=l,s.$ref=l,l.textContent=s.text,e.$danmuku.appendChild(l),l.style.opacity=e.option.opacity,l.style.fontSize=`${e.fontSize}px`,l.style.color=s.color,l.style.border=s.border?`1px solid ${s.color}`:null,l.style.backgroundColor=s.border?"rgb(0 0 0 / 50%)":null,i(l,s.style),!this.active(t))return;s.$lastStartTime=Date.now(),s.$restTime=e.speed;const d=o+l.clientWidth,{result:p}=yield t.wait(e.postMessage({type:"getDanmuTop",target:{mode:s.mode,height:l.clientHeight,speed:d/s.$restTime},visibles:e.visibles,antiOverlap:e.option.antiOverlap,clientWidth:o,clientHeight:r,marginBottom:e.marginBottom,marginTop:e.marginTop}));if(!this.active(t)||s.$ref!==l)return;if(void 0!==p){switch(e.setState(s,"emit"),l.style.top=`${p}px`,l.style.visibility="visible",l.dataset.mode=s.mode,l.dataset.id=s.id||"",s.mode){case 0:l.style.left=`${o}px`,l.style.marginLeft="0px",l.style.transform=`translateX(${-d}px)`,l.style.transition=`transform ${s.$restTime}s linear 0s`;break;case 1:case 2:l.style.left="50%",l.style.marginLeft=`-${l.clientWidth/2}px`}t.ref=null,e.art.emit("artplayerPluginDanmuku:visible",s)}else e.setState(s,"ready"),this.release(t)}})}destroy(){this.closed=!0,this.invalidate()}}let M=0;class z{constructor(t,e){this.pending=new Map,this.closed=!1,this.failed=!1,this.onFailure=e,this.worker=t();try{this.worker.onmessage=t=>{var e;const i=this.pending.get(null==(e=t.data)?void 0:e.id);i&&(this.pending.delete(t.data.id),i.resolve(t.data))},this.worker.onerror=t=>{var e;null==(e=t.preventDefault)||e.call(t),this.fail(t.error||t)},this.worker.onmessageerror=t=>this.fail(t.error||t)}catch(i){try{this.dispose()}catch(n){}throw i}}request(t){t.id=++M;const{id:e}=t;return this.closed||this.failed?Promise.resolve({id:e,result:void 0}):new Promise((i,n)=>{this.pending.set(e,{resolve:i,reject:n});try{this.worker.postMessage(t)}catch(a){this.fail(a)}})}fail(t){if(!this.closed&&!this.failed){this.failed=!0;for(const e of this.pending.values())e.reject(t);this.pending.clear();try{this.dispose()}catch(e){}this.onFailure(t)}}cancel(){for(const[t,e]of this.pending)e.resolve({id:t,result:void 0});this.pending.clear()}dispose(){if(this.closed)return;this.closed=!0,this.cancel();let t,e=!1;const i=i=>{try{i()}catch(n){e||(t=n),e=!0}};for(const n of["onmessage","onerror","onmessageerror"])i(()=>this.worker[n]=null);if(i(()=>this.worker.terminate()),e)throw t}}const C='/*!\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',E="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",C],{type:"text/javascript;charset=utf-8"});function D(t){let e;try{if(e=E&&(self.URL||self.webkitURL).createObjectURL(E),!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(C),{type:"module",name:null==t?void 0:t.name})}}class F{constructor(t,e){const{constructor:i,template:n}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=n.$danmuku,this.$player=n.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.worker=null,this.workerClient=null,this.scheduler=new S(this),this.option=F.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),this.seek=this.seek.bind(this),w(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!w(this))return;this.createWorker()}catch(a){throw t.off("destroy",this.destroy),x(this),this.scheduler.destroy(),a}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),t.on("video:seeking",this.seek),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 F.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 F.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 n=Number.parseFloat(i)/100;return Math.round(t(e*n,12,e))}return F.option.fontSize}get $ref(){const t=this.$refs.pop()||document.createElement("div");return t.style.cssText=F.cssText,t.dataset.mode="",t.dataset.id="",t.className="",t}get readys(){return function(t){const{currentTime:e}=t.art,i=[];return $(t,"ready",t=>i.push(t)),$(t,"wait",t=>{e+.1>=t.time&&t.time>=e-.1&&i.push(t)}),i}(this)}get visibles(){const t=[],{clientWidth:e}=this.$player,i=this.getLeft(this.$player);return this.filter("emit",n=>{const a=n.$ref.offsetTop,s=this.getLeft(n.$ref)-i,o=n.$ref.clientHeight,r=n.$ref.clientWidth,l=s+r,d=e-l,p=l/n.$restTime,h={};h.top=a,h.left=s,h.height=o,h.width=r,h.right=d,h.speed=p,h.distance=l,h.time=n.$restTime,h.mode=n.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 n,a=i.closed||t.art.isDestroy;const s=new Promise(t=>n=t),o=new Set,r="function"===typeof AbortController?new AbortController:void 0,l={replace:e,signal:null==r?void 0:r.signal,active:()=>!a&&!i.closed&&!t.art.isDestroy,onCancel:t=>l.active()?(o.add(t),()=>o.delete(t)):(t(),()=>{}),cancel(){if(!a){a=!0,n(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 a?n(v):i.tasks.add(l),l}(this,void 0===t);try{if(!i.active())return this;const n=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),a=n.asynchronous?yield i.wait(n.value):n.value;if(!i.active())return this;if(e(Array.isArray(a),"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=a[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:n,clamp:a,mount:s}){const o=Object.assign({},i,t,e);return n(o),o.mode=a(o.mode,0,2),o.speed=a(o.speed,1,10),o.opacity=a(o.opacity,0,1),o.lockTime=a(o.lockTime,1,60),o.maxLength=a(o.maxLength,1,1e3),o.mount=o.mount||s,o}(this.option,t,{defaults:F.option,validate:t=>this.validator(t,F.scheme),clamp:i,mount:n});if(!w(this))return this;const r=this.option.beforeVisible!==o.beforeVisible;return this.option=o,r&&(this.scheduler.invalidate(),this.scheduler.schedule()),t.fontSize&&(this.reset(),!w(this))||(this.option.visible?this.show():this.hide(),w(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this}getLeft(t){const e=t.getBoundingClientRect();return this.isRotate?e.top:e.left}postMessage(t={}){return this.workerClient.request(t)}createWorker(){this.workerClient=new z(()=>new D,t=>this.scheduler.fail(t)),this.worker=this.workerClient.worker}filter(t,e){return $(this,t,e)}setState(t,e){!function(t,e,i){t.states[e.$state]=t.states[e.$state].filter(t=>t!==e),e.$state=i,e.$ref&&(e.$ref.dataset.state=i),t.states[i].push(e)}(this,t,e)}makeWait(t){this.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=F.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(){return this.scheduler.running=!0,this.scheduler.schedule(),this}resize(){const{clientWidth:t}=this.$player;this.filter("stop",e=>{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.scheduler.running=!1,this.scheduler.invalidate(),this.suspend(),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){if(this.scheduler.closed||this.art.isDestroy)return this;const t=++this.scheduler.starts,e=this.scheduler.generation;this.isStop=!1,this.scheduler.recover();return this.scheduler.closed||this.art.isDestroy||this.isStop||this.scheduler.fault||e!==this.scheduler.generation||t!==this.scheduler.starts||(this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start")),this}reset(){return this.scheduler.invalidate(),this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this.scheduler.recover(),this.scheduler.schedule(),this}seek(){this.scheduler.invalidate(),this.scheduler.schedule()}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.scheduler.invalidate(),this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this.scheduler.schedule(),this}destroy(){var t;x(this),this.scheduler.destroy();try{try{this.stop()}finally{null==(t=this.workerClient)||t.dispose()}}finally{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("video:seeking",this.seek),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}}const T={map:(t,e,i,n,a)=>(t-e)*(a-n)/(i-e)+n,range(t,e,i){const n=Math.round(t/i)*i;return Array.from({length:Math.floor((e-t)/i)},(t,e)=>e*i+n)}};function A(t,e,i){const{query:n}=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(a){let s=null,r=null;function l(l=[]){if(s=null,r=null,a.innerHTML="",!t.duration||t.option.isLive)return;const d={w:a.offsetWidth,h:a.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 n=e.queue.filter(({time:e})=>e>t*i&&e<=(t+p.sampling)*i).length;h.push([t,n])}}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,n)=>{const a=function(t,e){const i=e[0]-t[0],n=e[1]-t[1];return{length:Math.sqrt(o(i,2)+o(n,2)),angle:Math.atan2(n,i)}}(e||t,i||t),s=T.map(Math.cos(a.angle)*p.flattening,0,1,1,0),r=a.angle*s+(n?Math.PI:0),l=a.length*p.smoothing;return[t[0]+Math.cos(r)*l,t[1]+Math.sin(r)*l]},v=h.map(t=>[T.map(t[0],p.xMin,p.xMax,0,d.w),T.map(t[1],p.yMin,p.yMax,d.h,0)]).reduce((t,e,i,n)=>0===i?`M ${n[n.length-1][0]},${d.h} L ${e[0]},${d.h} L ${e[0]},${e[1]}`:`${t} ${((t,e,i)=>{const n=y(i[e-1],i[e-2],t),a=y(t,i[e-1],i[e+1],!0),s=e===i.length-1?" z":"";return`C ${n[0]},${n[1]} ${a[0]},${a[1]} ${t[0]},${t[1]}${s}`})(e,i,n)}`,"");a.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,s=n("#heatmap-start",a),r=n("#heatmap-stop",a),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 P='',L='',O='',H='',V='',j='',R='',I='',q='',B='',Z='',N='';class W{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:n}=t.template;i(n,"display","flex"),this.template={$controlsCenter:n,$mount:n,$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:Z,$off:B,$config:O,$style:N,$mode_0_off:H,$mode_0_on:V,$mode_1_off:j,$mode_1_on:R,$mode_2_off:I,$mode_2_on:q,$check_on:L,$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 ${Z}${B}\n
\n
\n ${O}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${H}${V}\n
\u6eda\u52a8
\n
\n
\n ${j}${R}\n
\u9876\u90e8
\n
\n
\n ${I}${q}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${L}${P}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${L}${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 ${N}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${V}\n
\u6eda\u52a8
\n
\n
\n ${R}\n
\u9876\u90e8
\n
\n
\n ${q}\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:n}=this.template;i.dataset[t]=e,this.outside&&(n.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:n}=this.template;this.art.on("artplayerPluginDanmuku:show",()=>{e(n,"\u5173\u95ed\u5f39\u5e55")}),this.art.on("artplayerPluginDanmuku:hide",()=>{e(n,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:n,$antiOverlap:a,$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(a,"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(n,"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:n,onChange:a,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=n()){if(ie)return;const o=(i-t)/(e-t);d.style.left=100*o+"%",0===s.length&&(p.style.width=d.style.left),a(i)}function c(n){const{top:a,height:s,left:o,width:l}=i.getBoundingClientRect();if(this.art.isRotate){const i=r(n.clientY-a,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=r(n.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:n}=this.template;this.outside?t?this.append(i,e):this.append(n,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){const{$player:i}=this.art.template,n=t.getBoundingClientRect(),a=e.getBoundingClientRect(),s=i.getBoundingClientRect(),o=a.width/2-n.width/2,r=s.left-(n.left-o),l=n.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(n){console.error("Error emitting danmuku:",n),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 n=()=>{this.timer=setTimeout(()=>{0===i?this.unlock():(i-=1,e.textContent=i,n())},1e3)};n()}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 _(t){return e=>{const i=new F(e,t),n=e.isDestroy?void 0:new W(e,i);return!e.isDestroy&&i.option.heatmap&&A(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:n?n.mount.bind(n):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return _.icons=W.icons,_}); +!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,n=Object.getOwnPropertySymbols,a=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,r=Math.pow,o=(e,i,n)=>i in e?t(e,i,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[i]=n,l=(t,e)=>{for(var i in e||(e={}))a.call(e,i)&&o(t,i,e[i]);if(n)for(var i of n(e))s.call(e,i)&&o(t,i,e[i]);return t},d=(t,n)=>e(t,i(n)),c=(t,e,i)=>new Promise((n,a)=>{var s=t=>{try{o(i.next(t))}catch(e){a(e)}},r=t=>{try{o(i.throw(t))}catch(e){a(e)}},o=t=>t.done?n(t.value):Promise.resolve(t.value).then(s,r);o((i=i.apply(t,e)).next())});function p(t){return t instanceof Promise||"[object Promise]"===Object.prototype.toString.call(t)}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 m({data:t}){const{xml:e,id:i}=t,n=u(e);globalThis.postMessage({danmus:n,id:i})}let f=0;function g(t,{signal:e,onCancel:i}={}){return new Promise((n,a)=>{let s,r,o,l=!1;const d=++f,p=()=>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!==r){const n=r;r=void 0;try{URL.revokeObjectURL(n)}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",p),null==o||o();try{g()}catch(r){s||(i=r),s=!0}s?a(i):n(t)}function v(t,e){if(!l){console.error("Error parsing Bilibili Danmu:",e);try{y(u(t))}catch(i){y(void 0,i)}}}(null==e?void 0:e.aborted)?p():(null==e||e.addEventListener("abort",p),o=null==i?void 0:i(p),l?null==o||o():function(){return c(this,null,function*(){const i=e?yield fetch(t,{signal:e}):yield fetch(t);if(l)return;const n=yield i.text();if(!l)try{const t=`\n ${h.toString()}\n ${u.toString()}\n onmessage = ${m.toString()}\n `;if(r=URL.createObjectURL(new Blob([t],{type:"application/javascript"})),l)return void g();if(s=new Worker(r),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(n,t.error||t)},s.onmessageerror=t=>v(n,t.error||t),s.postMessage({xml:n,id:d})}catch(a){v(n,a)}})}().catch(t=>y(void 0,t)))})}const y=new WeakMap,v=Symbol("cancelled danmuku input");function w(t){return y.has(t)||y.set(t,{closed:!1,tasks:new Set,emitting:[]}),y.get(t)}function k(t){const e=w(t),i=e.emitting[e.emitting.length-1];return!e.closed&&!t.art.isDestroy&&(!i||i.active())}function x(t){const e=w(t);e.closed=!0;for(const i of[...e.tasks])i.cancel()}function b(t,e,i){const n=t.states[e]||[];for(let a=0;a{const a=n.$ref.offsetTop,s=this.owner.getLeft(n.$ref)-i,r=n.$ref.clientHeight,o=n.$ref.clientWidth,l=s+o,d=e-l,c=l/n.$restTime,p={};p.top=a,p.left=s,p.height=r,p.width=o,p.right=d,p.speed=c,p.distance=l,p.time=n.$restTime,p.mode=n.mode,t.push(p)}),t}left(t){const e=t.getBoundingClientRect();return this.owner.isRotate?e.top:e.left}makeWait(t){this.owner.setState(t,"wait"),t.$ref&&(t.$ref.style.cssText=this.owner.constructor.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.owner.$refs.push(t.$ref),t.$ref=null)}resize(){const{clientWidth:t}=this.owner.$player;this.owner.filter("stop",e=>{if(0===e.mode)e.$ref.style.left=`${t}px`}),this.owner.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.owner.$player;return this.owner.filter("stop",e=>{switch(this.owner.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.owner.$player;return this.owner.filter("emit",e=>{switch(this.owner.setState(e,"stop"),e.mode){case 0:{const i=t-(this.owner.getLeft(e.$ref)-this.owner.getLeft(this.owner.$player));e.$ref.style.transform=`translateX(${-i}px)`,e.$ref.style.transition="transform 0s linear 0s";break}}}),this}prepare(t,e){const i=this.owner,n=i.$ref;return e.danmu=t,e.ref=n,t.$ref=n,n.textContent=t.text,i.$danmuku.appendChild(n),n.style.opacity=i.option.opacity,n.style.fontSize=`${i.fontSize}px`,n.style.color=t.color,n.style.border=t.border?`1px solid ${t.color}`:null,n.style.backgroundColor=t.border?"rgb(0 0 0 / 50%)":null,i.utils.setStyles(n,t.style),n}place(t,e,i,n,a){switch(e.style.top=`${i}px`,e.style.visibility="visible",e.dataset.mode=t.mode,e.dataset.id=t.id||"",t.mode){case 0:e.style.left=`${a}px`,e.style.marginLeft="0px",e.style.transform=`translateX(${-n}px)`,e.style.transition=`transform ${t.$restTime}s linear 0s`;break;case 1:case 2:e.style.left="50%",e.style.marginLeft=`-${e.clientWidth/2}px`}}release(t){const{danmu:e,ref:i}=t;i&&e.$ref===i&&(i.style.cssText=this.owner.constructor.cssText,i.style.visibility="hidden",this.owner.$refs.push(i),e.$ref=null),t.ref=null}clear(){this.destroy(),this.owner.$refs=[],this.owner.$danmuku.textContent=""}destroy(){for(const t of this.nodes)t.parentElement&&t.parentElement.removeChild(t);for(const t of this.owner.queue)t.$ref=null;this.owner.$refs.length=0,this.nodes.clear()}}const M=Symbol("cancelled danmuku frame");class S{constructor(t){this.owner=t,this.generation=0,this.starts=0,this.frame=null,this.operation=null,this.closed=!1,this.running=!1,this.fault=!1,this.failedItems=new Set}active(t){const e=this.owner;return!this.closed&&!this.fault&&!e.art.isDestroy&&!e.isStop&&!e.isHide&&t.generation===this.generation}release(t){this.owner.renderer.release(t)}invalidate(){var t;if(this.generation++,null!==this.frame&&window.cancelAnimationFrame(this.frame),this.frame=null,this.owner.timer=null,this.operation){const t=this.operation;this.operation=null,t.cancel(),this.release(t)}null==(t=this.owner.workerClient)||t.cancel(),this.failedItems.clear()}report(t){try{this.owner.art.emit("artplayerPluginDanmuku:error",t)}catch(e){console.warn("Failed to report danmuku scheduling error:",e)}}fail(t){this.closed||this.fault||(this.fault=!0,this.invalidate(),this.report(t))}recover(){var t;if(!this.closed&&!this.owner.art.isDestroy&&(this.failedItems.clear(),this.fault))try{null==(t=this.owner.workerClient)||t.dispose(),this.owner.createWorker(),this.fault=!1}catch(e){this.report(e)}}schedule(){const t=this.owner;!this.running||this.closed||this.fault||t.art.isDestroy||t.isStop||null!==this.frame||this.operation||(this.frame=window.requestAnimationFrame(()=>{if(this.frame=null,t.timer=null,this.closed||this.fault||t.isStop||t.art.isDestroy)return;let e;const i=new Promise(t=>e=()=>t(M)),n={generation:this.generation,cancel:e,wait:t=>Promise.race([t,i]),ref:null};return this.operation=n,this.run(n).catch(t=>{this.active(n)&&this.fail(t)}).finally(()=>{this.release(n),this.operation===n&&(this.operation=null,this.schedule())})}),t.timer=this.frame)}run(t){return c(this,null,function*(){const e=this.owner;if(!e.art.playing||!this.active(t))return;e.filter("emit",t=>{const i=(Date.now()-t.$lastStartTime)/1e3;t.$restTime-=i,t.$lastStartTime=Date.now(),t.$restTime<=0&&e.makeWait(t)});const i=e.readys;for(const a of i){if(!this.active(t))return;if(this.failedItems.has(a))continue;let i;try{i=yield t.wait(e.option.beforeVisible(a))}catch(n){if(!this.active(t))return;this.failedItems.add(a),this.report(n);continue}if(!this.active(t))return;if(!i)continue;const{clientWidth:s,clientHeight:r}=e.$player,o=e.renderer.prepare(a,t);if(!this.active(t))return;a.$lastStartTime=Date.now(),a.$restTime=e.speed;const l=s+o.clientWidth,{result:d}=yield t.wait(e.postMessage({type:"getDanmuTop",target:{mode:a.mode,height:o.clientHeight,speed:l/a.$restTime},visibles:e.visibles,antiOverlap:e.option.antiOverlap,clientWidth:s,clientHeight:r,marginBottom:e.marginBottom,marginTop:e.marginTop}));if(!this.active(t)||a.$ref!==o)return;void 0!==d?(e.setState(a,"emit"),e.renderer.place(a,o,d,l,s),t.ref=null,e.art.emit("artplayerPluginDanmuku:visible",a)):(e.setState(a,"ready"),this.release(t))}})}destroy(){this.closed=!0,this.invalidate()}}let z=0;class E{constructor(t,e){this.pending=new Map,this.closed=!1,this.failed=!1,this.onFailure=e,this.worker=t();try{this.worker.onmessage=t=>{var e;const i=this.pending.get(null==(e=t.data)?void 0:e.id);i&&(this.pending.delete(t.data.id),i.resolve(t.data))},this.worker.onerror=t=>{var e;null==(e=t.preventDefault)||e.call(t),this.fail(t.error||t)},this.worker.onmessageerror=t=>this.fail(t.error||t)}catch(i){try{this.dispose()}catch(n){}throw i}}request(t){t.id=++z;const{id:e}=t;return this.closed||this.failed?Promise.resolve({id:e,result:void 0}):new Promise((i,n)=>{this.pending.set(e,{resolve:i,reject:n});try{this.worker.postMessage(t)}catch(a){this.fail(a)}})}fail(t){if(!this.closed&&!this.failed){this.failed=!0;for(const e of this.pending.values())e.reject(t);this.pending.clear();try{this.dispose()}catch(e){}this.onFailure(t)}}cancel(){for(const[t,e]of this.pending)e.resolve({id:t,result:void 0});this.pending.clear()}dispose(){if(this.closed)return;this.closed=!0,this.cancel();let t,e=!1;const i=i=>{try{i()}catch(n){e||(t=n),e=!0}};for(const n of["onmessage","onerror","onmessageerror"])i(()=>this.worker[n]=null);if(i(()=>this.worker.terminate()),e)throw t}}const C='/*!\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',F="undefined"!==typeof self&&self.Blob&&new Blob(["URL.revokeObjectURL(import.meta.url);",C],{type:"text/javascript;charset=utf-8"});function D(t){let e;try{if(e=F&&(self.URL||self.webkitURL).createObjectURL(F),!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(C),{type:"module",name:null==t?void 0:t.name})}}class P{constructor(t,e){const{constructor:i,template:n}=t;this.utils=i.utils,this.validator=i.validator,this.$danmuku=n.$danmuku,this.$player=n.$player,this.art=t,this.queue=[],this.$refs=[],this.isStop=!1,this.isHide=!1,this.timer=null,this.index=0,this.worker=null,this.workerClient=null,this.renderer=new $(this),this.scheduler=new S(this),this.option=P.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),this.seek=this.seek.bind(this),k(this),t.on("destroy",this.destroy);try{if(this.config(e,!0),!k(this))return;this.createWorker()}catch(a){throw t.off("destroy",this.destroy),x(this),this.scheduler.destroy(),a}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),t.on("video:seeking",this.seek),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 P.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 P.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 n=Number.parseFloat(i)/100;return Math.round(t(e*n,12,e))}return P.option.fontSize}get $ref(){return this.renderer.acquire()}get readys(){return function(t){const{currentTime:e}=t.art,i=[];return b(t,"ready",t=>i.push(t)),b(t,"wait",t=>{e+.1>=t.time&&t.time>=e-.1&&i.push(t)}),i}(this)}get visibles(){return this.renderer.visibles}get speed(){return this.option.synchronousPlayback&&this.art.playbackRate?this.option.speed/Number(this.art.playbackRate):this.option.speed}load(t){return c(this,null,function*(){const{errorHandle:e}=this.utils,i=function(t,e){const i=w(t);if(e)for(const d of[...i.tasks])d.replace&&d.cancel();let n,a=i.closed||t.art.isDestroy;const s=new Promise(t=>n=t),r=new Set,o="function"===typeof AbortController?new AbortController:void 0,l={replace:e,signal:null==o?void 0:o.signal,active:()=>!a&&!i.closed&&!t.art.isDestroy,onCancel:t=>l.active()?(r.add(t),()=>r.delete(t)):(t(),()=>{}),cancel(){if(!a){a=!0,n(v),null==o||o.abort();for(const t of[...r])t();r.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),r.clear()}};return a?n(v):i.tasks.add(l),l}(this,void 0===t);try{if(!i.active())return this;const n=function(t,e){return"function"===typeof t?{asynchronous:!0,value:t()}:p(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),a=n.asynchronous?yield i.wait(n.value):n.value;if(!i.active())return this;if(e(Array.isArray(a),"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.renderer.clear()}for(let t=0;t{const e=a[t],i=s[t];return"function"===typeof e||"function"===typeof i||p(e)||p(i)?e!==i:JSON.stringify(e)!==JSON.stringify(i)}))&&!e)return this;const r=function(t,e,{defaults:i,validate:n,clamp:a,mount:s}){const r=Object.assign({},i,t,e);return n(r),r.mode=a(r.mode,0,2),r.speed=a(r.speed,1,10),r.opacity=a(r.opacity,0,1),r.lockTime=a(r.lockTime,1,60),r.maxLength=a(r.maxLength,1,1e3),r.mount=r.mount||s,r}(this.option,t,{defaults:P.option,validate:t=>this.validator(t,P.scheme),clamp:i,mount:n});if(!k(this))return this;const o=this.option.beforeVisible!==r.beforeVisible;return this.option=r,o&&(this.scheduler.invalidate(),this.scheduler.schedule()),t.fontSize&&(this.reset(),!k(this))||(this.option.visible?this.show():this.hide(),k(this)&&this.art.emit("artplayerPluginDanmuku:config",this.option)),this}getLeft(t){return this.renderer.left(t)}postMessage(t={}){return this.workerClient.request(t)}createWorker(){this.workerClient=new E(()=>new D,t=>this.scheduler.fail(t)),this.worker=this.workerClient.worker}filter(t,e){return b(this,t,e)}setState(t,e){!function(t,e,i){t.states[e.$state]=t.states[e.$state].filter(t=>t!==e),e.$state=i,e.$ref&&(e.$ref.dataset.state=i),t.states[i].push(e)}(this,t,e)}makeWait(t){this.renderer.makeWait(t)}update(){return this.scheduler.running=!0,this.scheduler.schedule(),this}resize(){this.renderer.resize()}continue(){return this.renderer.continue(),this}suspend(){return this.renderer.suspend(),this}stop(){return this.isStop=!0,this.scheduler.running=!1,this.scheduler.invalidate(),this.suspend(),this.art.emit("artplayerPluginDanmuku:stop"),this}start(){if(this.scheduler.closed||this.art.isDestroy)return this;const t=++this.scheduler.starts,e=this.scheduler.generation;this.isStop=!1,this.scheduler.recover();return this.scheduler.closed||this.art.isDestroy||this.isStop||this.scheduler.fault||e!==this.scheduler.generation||t!==this.scheduler.starts||(this.continue(),this.update(),this.art.emit("artplayerPluginDanmuku:start")),this}reset(){return this.scheduler.invalidate(),this.queue.forEach(t=>this.makeWait(t)),this.art.emit("artplayerPluginDanmuku:reset"),this.scheduler.recover(),this.scheduler.schedule(),this}seek(){this.scheduler.invalidate(),this.scheduler.schedule()}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.scheduler.invalidate(),this.$danmuku.style.opacity=0,this.option.visible=!1,this.art.emit("artplayerPluginDanmuku:hide"),this.scheduler.schedule(),this}destroy(){var t;x(this),this.scheduler.destroy();try{try{this.stop()}finally{null==(t=this.workerClient)||t.dispose()}}finally{this.renderer.destroy(),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("video:seeking",this.seek),this.art.off("destroy",this.destroy),this.art.emit("artplayerPluginDanmuku:destroy")}}}const T=4294967295;function A(t,e){let i=0,n=t.length;for(;iNumber.isFinite(t)&&t>0))return null;const o={xMin:0,xMax:t,yMin:0,yMax:128,scale:.25,opacity:.2,minHeight:Math.floor(.05*e),sampling:Math.max(1,Math.floor(t/100)),smoothing:.2,flattening:.2},l=Object.assign({},o,"object"===typeof a?a:{});for(const r of Object.keys(o))Number.isFinite(l[r])||(l[r]=o[r]);if(l.sampling<=0&&(l.sampling=o.sampling),l.xMin===l.xMax||l.yMin===l.yMax)return null;const d=Array.isArray(s)&&s.length?[...s]:function(t,e,i,n){if(!Number.isFinite(e)||e<=0||!Number.isFinite(i)||i<=0||!Number.isFinite(n)||n<=0||Math.floor(e/n)+1>T)return[];const a=t.map(t=>t.time).filter(t=>"number"===typeof t&&!Number.isNaN(t)).sort((t,e)=>t-e),s=[],r=i/e;if(!Number.isFinite(r))return[];for(let o=0;o<=e;o+=n){if(s.length>=T||o+n===o)return[];const t=o*r,e=(o+n)*r;s.push([o,A(a,e)-A(a,t)])}return s}(n,t,i,l.sampling);if(!d.length||!d.every(t=>Array.isArray(t)&&Number.isFinite(t[0])&&Number.isFinite(t[1])))return null;const c=d[d.length-1];if(c[0]!==t){if(d.length===T)return null;d.push([t,c[1]])}let p=1/0,h=-1/0;for(const r of d)p=Math.min(p,r[1]),h=Math.max(h,r[1]);const u=(p+h)/2;for(const r of d){const t=r[1];r[1]=t*(t>u?1+l.scale:1-l.scale)+l.minHeight}const m=(t,e,i,n)=>{const a=function(t,e){const i=e[0]-t[0],n=e[1]-t[1];return{length:Math.sqrt(r(i,2)+r(n,2)),angle:Math.atan2(n,i)}}(e||t,i||t),s=L(Math.cos(a.angle)*l.flattening,0,1,1,0),o=a.angle*s+(n?Math.PI:0),d=a.length*l.smoothing;return[t[0]+Math.cos(o)*d,t[1]+Math.sin(o)*d]},f=d.map(i=>[L(i[0],l.xMin,l.xMax,0,t),L(i[1],l.yMin,l.yMax,e,0)]);if(!f.every(t=>t.every(Number.isFinite)))return null;const g=f.map((t,i,n)=>{if(0===i)return`M ${n[n.length-1][0]},${e} L ${t[0]},${e} L ${t[0]},${t[1]}`;const a=m(n[i-1],n[i-2],t),s=m(t,n[i-1],n[i+1],!0);return`C ${a[0]},${a[1]} ${s[0]},${s[1]} ${t[0]},${t[1]}${i===n.length-1?" z":""}`}).join(" ");return/NaN|Infinity/u.test(g)?null:{path:g,opacity:l.opacity,width:t,height:e}}let H=0;function V(t,e,i){if(t.isDestroy)return;const{query:n}=t.constructor.utils;let a;const s=[];let r,o,l,d=!1;const c=()=>!d&&!t.isDestroy;function p(e,i){s.push([e,i]),t.on(e,i)}function h(e){if(d)return;let i;d=!0,o=null,l=null;let n=!1;const a=t=>{try{t()}catch(e){n||(i=e),n=!0}};for(const[r,o]of s.splice(0))a(()=>t.off(r,o));if(e&&r&&t.controls.heatmap===r&&a(()=>t.controls.remove("heatmap")),r=void 0,n)throw i}function u(t){c()&&o&&l&&(o.setAttribute("offset",100*t+"%"),l.setAttribute("offset",100*t+"%"))}function m(s){try{!function(s=[]){if(!c())return;if(o=null,l=null,r.innerHTML="",t.option.isLive)return;const d=O({width:r.offsetWidth,height:r.offsetHeight,duration:t.duration,queue:e.queue,option:i,points:s});if(!d||!c())return;const p=r.ownerDocument;for(a||(a=p?"heatmap-solids":"heatmap-solids-"+ ++H);null==p?void 0:p.getElementById(a);)a="heatmap-solids-"+ ++H;r.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n \n `,o=n("#heatmap-start",r),l=n("#heatmap-stop",r),u(t.played)}(s)}catch(d){try{h(!0)}catch(p){}throw d}}p("destroy",()=>h(!0));try{t.controls.add({name:"heatmap",position:"top",html:"",style:{position:"absolute",top:"-100px",left:"0px",right:"0px",height:"100px",width:"100%",pointerEvents:"none"},beforeUnmount(){h(!1)},mounted(e){if(r=e,d)return t.controls.heatmap===e&&t.controls.remove("heatmap"),void(r=void 0);c()?(p("video:timeupdate",()=>u(t.played)),p("setBar",(t,e)=>{"played"===t&&u(e)}),p("ready",()=>m()),p("resize",()=>m()),p("artplayerPluginDanmuku:loaded",()=>m()),p("artplayerPluginDanmuku:points",t=>m(t))):h(!0)}})}catch(f){try{h(!0)}catch(g){}throw f}return()=>h(!0)}const j=new WeakMap;class N{constructor(t){this.art=t,this.closed=!1,this.disposers=new Set,this.cancelled=new Promise(t=>this.cancel=t)}get active(){return!this.closed&&!this.art.isDestroy}own(t){this.closed?t():this.disposers.add(t)}write(t,e,i,n=i=>t[e]=i){if(!this.active)return;const a=()=>"function"===typeof t.getPropertyPriority?t.getPropertyPriority(e):void 0;let s=j.get(t);s||(s=new Map,j.set(t,s));let r=s.get(e);r||(r={original:t[e],present:e in t||void 0!==t[e],priority:a(),owners:new Map,apply:n},s.set(e,r)),r.owners.has(this)||this.own(()=>{const i=[...r.owners.keys()],n=i[i.length-1]===this;if(r.owners.delete(this),n&&t[e]===r.last&&a()===r.lastPriority){const i=[...r.owners.values()];i.length?(r.apply(i[i.length-1]),r.last=t[e],r.lastPriority=a()):void 0!==r.priority?t.setProperty(e,r.original,r.priority):r.present?r.apply(r.original):delete t[e]}r.owners.size||(s.delete(e),s.size||j.delete(t))}),r.owners.delete(this),r.owners.set(this,i),r.apply(i),r.last=t[e],r.lastPriority=a()}on(t,e){if(!this.active)return;const i=this;function n(...t){if(i.active)return e.apply(this,t)}const a=()=>this.art.off(t,n);this.own(a);try{this.art.on(t,n),this.active||a()}catch(s){throw this.release(a),s}}proxy(t,e,i){if(!this.active)return;const n=this;function a(...t){if(n.active)return i.apply(this,t)}let s;const r=()=>{var i,n;if("function"===typeof s){const t=this.art.events;"function"===typeof(null==t?void 0:t.remove)&&(null==(i=t.destroyEvents)?void 0:i.has(s))?t.remove(s):s()}else null==(n=t.removeEventListener)||n.call(t,e,a)};this.own(r);try{s=this.art.proxy(t,e,a),this.active||r()}catch(o){throw this.release(r),o}}wait(t){return Promise.race([t,this.cancelled])}release(t){try{t()}catch(e){console.warn("Failed to dispose danmuku setting resource:",e)}}close(){if(!this.closed){this.closed=!0,this.cancel();for(const t of this.disposers)this.release(t);this.disposers.clear()}}}function q({min:t,max:e,container:i,findIndex:n,onChange:a,steps:s=[]}){const{lifecycle:r}=this;if(!r.active)return{reset(){}};const{query:o,clamp:l,setStyle:d}=this.utils;d(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 c=o(".apd-slider-dot",i),p=o(".apd-slider-progress",i);let h=!1;function u(i){if(!r.active)return;if(void 0===i&&(i=n()),ie)return;const o=(i-t)/(e-t);c.style.left=100*o+"%",0===s.length&&(p.style.width=c.style.left),a(i)}function m(n){const{top:a,height:s,left:r,width:o}=i.getBoundingClientRect();if(this.art.isRotate){const i=l(n.clientY-a,0,s);u(Math.round(i/s*(e-t)+t))}else{const i=l(n.clientX-r,0,o);u(Math.round(i/o*(e-t)+t))}}return r.own(()=>h=!1),r.proxy(i,"click",t=>{m.call(this,t)}),r.proxy(i,"pointerdown",t=>{h=0===t.button}),r.on("document:pointermove",t=>{h&&m.call(this,t)}),r.on("document:pointerup",t=>{h&&(h=!1,m.call(this,t))}),{reset:u}}const I='',R='',B='',Z='',W='',_='',U='',G='',X='',Y='',J='',K='';function Q(){const{option:t}=this;return`\n
\n ${J}${Y}\n
\n
\n ${B}\n
\n
\n
\n \u6309\u7c7b\u578b\u5c4f\u853d\n
\n
\n ${Z}${W}\n
\u6eda\u52a8
\n
\n
\n ${_}${U}\n
\u9876\u90e8
\n
\n
\n ${G}${X}\n
\u5e95\u90e8
\n
\n
\n
\n
\n
\n ${R}${I}\n \u9632\u6b62\u5f39\u5e55\u91cd\u53e0\n
\n
\n ${R}${I}\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 ${K}\n
\n
\n
\n \u6a21\u5f0f\n
\n
\n ${W}\n
\u6eda\u52a8
\n
\n
\n ${U}\n
\u9876\u90e8
\n
\n
\n ${X}\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 `}if("undefined"!==typeof document){const t="artplayer-plugin-danmuku",e=Symbol.for("artplayer-plugin-danmuku.pending-style");let i=document.getElementById(t)||document[e];if(!i)if(i=document.createElement("style"),i.id=t,"loading"===document.readyState){document[e]=i;const n=()=>{document.removeEventListener("DOMContentLoaded",n),document[e]===i&&delete document[e];const a=document.getElementById(t);a?a.textContent=i.textContent:(document.head||document.documentElement).appendChild(i)};document.addEventListener("DOMContentLoaded",n,{once:!0})}else(document.head||document.documentElement).appendChild(i);i.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}'}class tt{constructor(t,e){this.art=t,this.danmuku=e,this.utils=t.constructor.utils;const{setStyle:i}=this.utils,{$controlsCenter:n}=t.template;this.template={$controlsCenter:n,$mount:n,$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.lifecycle=new N(t),this.destroy=this.destroy.bind(this),this.lifecycle.own(()=>t.off("destroy",this.destroy));try{if(t.on("destroy",this.destroy),!this.lifecycle.active)return;if(this.lifecycle.write(n.style,"display","flex",t=>i(n,"display",t)),this.createTemplate(),!this.lifecycle.active)return;if(this.createSliders(),!this.lifecycle.active)return;if(this.createEvents(),this.mount(this.option.mount),!this.lifecycle.active)return;this.lifecycle.on("resize",()=>this.resize()),this.lifecycle.on("fullscreen",t=>this.onFullscreen(t)),this.lifecycle.on("fullscreenWeb",t=>this.onFullscreen(t)),this.lifecycle.proxy(this.template.$config,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$config,$panel:this.template.$configPanel})}),this.lifecycle.proxy(this.template.$style,"mouseenter",()=>{this.onMouseEnter({$control:this.template.$style,$panel:this.template.$stylePanel})})}catch(a){try{this.destroy()}catch(s){console.warn("Failed to roll back danmuku setting:",s)}throw a}}static get icons(){return{$on:J,$off:Y,$config:B,$style:K,$mode_0_off:Z,$mode_0_on:W,$mode_1_off:_,$mode_1_on:U,$mode_2_off:G,$mode_2_on:X,$check_on:R,$check_off:I}}get option(){return this.danmuku.option}get outside(){return this.template.$mount!==this.template.$controlsCenter}get TEMPLATE(){return Q.call(this)}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){if(!this.lifecycle.active)return;const{append:i}=this.utils;[...t.children].includes(e)||i(t,e)}setData(t,e){if(!this.lifecycle.active)return;const{$player:i}=this.art.template,{$mount:n}=this.template;this.lifecycle.write(i.dataset,t,e),this.outside&&this.lifecycle.write(n.dataset,t,e)}createTemplate(){const{createElement:t,tooltip:e}=this.utils,i=t("div");this.template.$danmuku=i,i.className="artplayer-plugin-danmuku",i.innerHTML=this.TEMPLATE,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:n}=this.template;this.lifecycle.on("artplayerPluginDanmuku:show",()=>{e(n,"\u5173\u95ed\u5f39\u5e55")}),this.lifecycle.on("artplayerPluginDanmuku:hide",()=>{e(n,"\u6253\u5f00\u5f39\u5e55")})}createEvents(){const{$toggle:t,$configModes:e,$styleModes:i,$colors:n,$antiOverlap:a,$syncVideo:s,$send:r,$input:o}=this.template;this.lifecycle.proxy(t,"click",()=>{this.danmuku.config({visible:!this.option.visible}),this.reset()}),this.lifecycle.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.lifecycle.proxy(a,"click",()=>{this.danmuku.config({antiOverlap:!this.option.antiOverlap}),this.reset()}),this.lifecycle.proxy(s,"click",()=>{this.danmuku.config({synchronousPlayback:!this.option.synchronousPlayback}),this.reset()}),this.lifecycle.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.lifecycle.proxy(n,"click",t=>{const e=t.target.closest(".apd-color");e&&(this.danmuku.config({color:e.dataset.color}),this.reset())}),this.lifecycle.proxy(r,"click",()=>this.emit()),this.lifecycle.proxy(o,"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(t){return q.call(this,t)}onFullscreen(t){if(!this.lifecycle.active)return;const{$danmuku:e,$controlsCenter:i,$mount:n}=this.template;this.outside?t?this.append(i,e):this.append(n,e):this.append(i,e)}onMouseEnter({$control:t,$panel:e}){if(!this.lifecycle.active)return;const{$player:i}=this.art.template,n=t.getBoundingClientRect(),a=e.getBoundingClientRect(),s=i.getBoundingClientRect(),r=a.width/2-n.width/2,o=s.left-(n.left-r),l=n.right+r-s.right;e.style.left=o>0?`${-r+o}px`:l>0?-r-l+"px":-r+"px"}emit(){return function(t){return c(this,null,function*(){const{lifecycle:e,template:{$input:i}}=t;if(!e.active)return;const n=i.value.trim();if(!n.length||t.isLock||t.emitting)return;const a={text:n,mode:t.option.mode,color:t.option.color,time:t.art.currentTime},s=t=>{e.active&&console.error("Error emitting danmuku:",t)};try{t.emitting=!0;const n=yield e.wait(t.option.beforeEmit(a));if(!e.active)return;if(t.emitting=!1,!0!==n)return;if(a.border=!0,delete a.time,Promise.resolve(t.danmuku.emit(a)).catch(s),!e.active)return;i.value="",t.lock()}catch(r){s(r),e.active&&(t.emitting=!1)}})}(this)}lock(){!function(t){if(!t.lifecycle.active)return;const{addClass:e}=t.utils,{$send:i}=t.template;clearTimeout(t.timer),t.isLock=!0;let n=t.option.lockTime;i.textContent=n,e(i,"apd-lock");const a=()=>{t.timer=setTimeout(()=>{t.lifecycle.active&&(0===n?t.unlock():(n-=1,i.textContent=n,a()))},1e3)};a()}(this)}unlock(){!function(t){if(clearTimeout(t.timer),t.timer=null,t.isLock=!1,!t.lifecycle.active)return;const{removeClass:e}=t.utils,{$send:i}=t.template;i.textContent="\u53d1\u9001",e(i,"apd-lock")}(this)}resize(){if(!this.lifecycle.active)return;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){if(!this.lifecycle.active)return;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()}destroy(){if(this.lifecycle.closed)return;this.lifecycle.close(),clearTimeout(this.timer),this.timer=null,this.emitting=!1,this.isLock=!1;const t=this.template.$danmuku;(null==t?void 0:t.parentElement)&&t.parentElement.removeChild(t)}}function et(t){return e=>{const i=new P(e,t);let n;try{n=e.isDestroy?void 0:new tt(e,i),!e.isDestroy&&i.option.heatmap&&V(e,i,i.option.heatmap)}catch(a){try{null==n||n.destroy()}catch(s){}try{i.destroy()}catch(s){}throw a}return{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:n?n.mount.bind(n):()=>{},get option(){return i.option},get isHide(){return i.isHide},get isStop(){return i.isStop}}}}return et.icons=tt.icons,et}); diff --git a/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs b/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs index b6e8613cc..14f67f4da 100644 --- a/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs +++ b/packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs @@ -365,6 +365,179 @@ function setItemState(owner, danmu, state) { danmu.$ref.dataset.state = state; owner.states[state].push(danmu); } +class Renderer { + constructor(owner) { + this.owner = owner; + this.nodes = /* @__PURE__ */ new Set(); + } + acquire() { + const $ref = this.owner.$refs.pop() || document.createElement("div"); + $ref.style.cssText = this.owner.constructor.cssText; + $ref.dataset.mode = ""; + $ref.dataset.id = ""; + $ref.className = ""; + this.nodes.add($ref); + return $ref; + } + get visibles() { + const result = []; + const { clientWidth } = this.owner.$player; + const clientLeft = this.owner.getLeft(this.owner.$player); + this.owner.filter("emit", (danmu) => { + const top = danmu.$ref.offsetTop; + const left = this.owner.getLeft(danmu.$ref) - clientLeft; + const height = danmu.$ref.clientHeight; + const width = danmu.$ref.clientWidth; + const distance = left + width; + const right = clientWidth - distance; + const speed = distance / danmu.$restTime; + const emit = {}; + emit.top = top; + emit.left = left; + emit.height = height; + emit.width = width; + emit.right = right; + emit.speed = speed; + emit.distance = distance; + emit.time = danmu.$restTime; + emit.mode = danmu.mode; + result.push(emit); + }); + return result; + } + left($ref) { + const rect = $ref.getBoundingClientRect(); + return this.owner.isRotate ? rect.top : rect.left; + } + makeWait(danmu) { + this.owner.setState(danmu, "wait"); + if (danmu.$ref) { + danmu.$ref.style.cssText = this.owner.constructor.cssText; + danmu.$ref.style.visibility = "hidden"; + danmu.$ref.style.marginLeft = "0px"; + danmu.$ref.style.transform = "translateX(0px)"; + danmu.$ref.style.transition = "transform 0s linear 0s"; + this.owner.$refs.push(danmu.$ref); + danmu.$ref = null; + } + } + resize() { + const { clientWidth } = this.owner.$player; + this.owner.filter("stop", (danmu) => { + switch (danmu.mode) { + // 滚动的弹幕 + case 0: + danmu.$ref.style.left = `${clientWidth}px`; + break; + } + }); + this.owner.filter("emit", (danmu) => { + danmu.$lastStartTime = Date.now(); + switch (danmu.mode) { + // 滚动的弹幕 + case 0: { + const distance = clientWidth + danmu.$ref.clientWidth; + danmu.$ref.style.left = `${clientWidth}px`; + danmu.$ref.style.transform = `translateX(${-distance}px)`; + danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; + break; + } + } + }); + } + continue() { + const { clientWidth } = this.owner.$player; + this.owner.filter("stop", (danmu) => { + this.owner.setState(danmu, "emit"); + danmu.$lastStartTime = Date.now(); + switch (danmu.mode) { + // 继续滚动的弹幕 + case 0: { + const distance = clientWidth + danmu.$ref.clientWidth; + danmu.$ref.style.transform = `translateX(${-distance}px)`; + danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; + break; + } + } + }); + return this; + } + suspend() { + const { clientWidth } = this.owner.$player; + this.owner.filter("emit", (danmu) => { + this.owner.setState(danmu, "stop"); + switch (danmu.mode) { + // 停止滚动的弹幕 + case 0: { + const translateX = clientWidth - (this.owner.getLeft(danmu.$ref) - this.owner.getLeft(this.owner.$player)); + danmu.$ref.style.transform = `translateX(${-translateX}px)`; + danmu.$ref.style.transition = "transform 0s linear 0s"; + break; + } + } + }); + return this; + } + prepare(danmu, operation) { + const owner = this.owner; + const ref = owner.$ref; + operation.danmu = danmu; + operation.ref = ref; + danmu.$ref = ref; + ref.textContent = danmu.text; + owner.$danmuku.appendChild(ref); + ref.style.opacity = owner.option.opacity; + ref.style.fontSize = `${owner.fontSize}px`; + ref.style.color = danmu.color; + ref.style.border = danmu.border ? `1px solid ${danmu.color}` : null; + ref.style.backgroundColor = danmu.border ? "rgb(0 0 0 / 50%)" : null; + owner.utils.setStyles(ref, danmu.style); + return ref; + } + place(danmu, ref, top, distance, clientWidth) { + ref.style.top = `${top}px`; + ref.style.visibility = "visible"; + ref.dataset.mode = danmu.mode; + ref.dataset.id = danmu.id || ""; + switch (danmu.mode) { + case 0: + ref.style.left = `${clientWidth}px`; + ref.style.marginLeft = "0px"; + ref.style.transform = `translateX(${-distance}px)`; + ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; + break; + case 1: + case 2: + ref.style.left = "50%"; + ref.style.marginLeft = `-${ref.clientWidth / 2}px`; + break; + } + } + release(operation) { + const { danmu, ref } = operation; + if (ref && danmu.$ref === ref) { + ref.style.cssText = this.owner.constructor.cssText; + ref.style.visibility = "hidden"; + this.owner.$refs.push(ref); + danmu.$ref = null; + } + operation.ref = null; + } + clear() { + this.destroy(); + this.owner.$refs = []; + this.owner.$danmuku.textContent = ""; + } + destroy() { + for (const node of this.nodes) { + if (node.parentElement) + node.parentElement.removeChild(node); + } + for (const danmu of this.owner.queue) danmu.$ref = null; + this.owner.$refs.length = 0; + this.nodes.clear(); + } +} const cancelled = /* @__PURE__ */ Symbol("cancelled danmuku frame"); class Scheduler { constructor(owner) { @@ -383,14 +556,7 @@ class Scheduler { return !this.closed && !this.fault && !owner.art.isDestroy && !owner.isStop && !owner.isHide && operation.generation === this.generation; } release(operation) { - const { danmu, ref } = operation; - if (ref && danmu.$ref === ref) { - ref.style.cssText = this.owner.constructor.cssText; - ref.style.visibility = "hidden"; - this.owner.$refs.push(ref); - danmu.$ref = null; - } - operation.ref = null; + this.owner.renderer.release(operation); } invalidate() { this.generation++; @@ -463,7 +629,6 @@ class Scheduler { } async run(operation) { const owner = this.owner; - const { setStyles } = owner.utils; if (!owner.art.playing || !this.active(operation)) return; owner.filter("emit", (danmu) => { @@ -494,18 +659,7 @@ class Scheduler { if (!state) continue; const { clientWidth, clientHeight } = owner.$player; - const ref = owner.$ref; - operation.danmu = danmu; - operation.ref = ref; - danmu.$ref = ref; - ref.textContent = danmu.text; - owner.$danmuku.appendChild(ref); - ref.style.opacity = owner.option.opacity; - ref.style.fontSize = `${owner.fontSize}px`; - ref.style.color = danmu.color; - ref.style.border = danmu.border ? `1px solid ${danmu.color}` : null; - ref.style.backgroundColor = danmu.border ? "rgb(0 0 0 / 50%)" : null; - setStyles(ref, danmu.style); + const ref = owner.renderer.prepare(danmu, operation); if (!this.active(operation)) return; danmu.$lastStartTime = Date.now(); @@ -525,23 +679,7 @@ class Scheduler { return; if (top !== void 0) { owner.setState(danmu, "emit"); - ref.style.top = `${top}px`; - ref.style.visibility = "visible"; - ref.dataset.mode = danmu.mode; - ref.dataset.id = danmu.id || ""; - switch (danmu.mode) { - case 0: - ref.style.left = `${clientWidth}px`; - ref.style.marginLeft = "0px"; - ref.style.transform = `translateX(${-distance}px)`; - ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; - break; - case 1: - case 2: - ref.style.left = "50%"; - ref.style.marginLeft = `-${ref.clientWidth / 2}px`; - break; - } + owner.renderer.place(danmu, ref, top, distance, clientWidth); operation.ref = null; owner.art.emit("artplayerPluginDanmuku:visible", danmu); } else { @@ -679,6 +817,7 @@ class Danmuku { this.index = 0; this.worker = null; this.workerClient = null; + this.renderer = new Renderer(this); this.scheduler = new Scheduler(this); this.option = Danmuku.option; this.states = { wait: [], ready: [], emit: [], stop: [] }; @@ -782,12 +921,7 @@ class Danmuku { } // 获取弹幕DOM节点 get $ref() { - const $ref = this.$refs.pop() || document.createElement("div"); - $ref.style.cssText = Danmuku.cssText; - $ref.dataset.mode = ""; - $ref.dataset.id = ""; - $ref.className = ""; - return $ref; + return this.renderer.acquire(); } // 获取准备好发送的弹幕 get readys() { @@ -795,30 +929,7 @@ class Danmuku { } // 可见的弹幕的数据,用于计算下一个弹幕的top值 get visibles() { - const result = []; - const { clientWidth } = this.$player; - const clientLeft = this.getLeft(this.$player); - this.filter("emit", (danmu) => { - const top = danmu.$ref.offsetTop; - const left = this.getLeft(danmu.$ref) - clientLeft; - const height = danmu.$ref.clientHeight; - const width = danmu.$ref.clientWidth; - const distance = left + width; - const right = clientWidth - distance; - const speed = distance / danmu.$restTime; - const emit = {}; - emit.top = top; - emit.left = left; - emit.height = height; - emit.width = width; - emit.right = right; - emit.speed = speed; - emit.distance = distance; - emit.time = danmu.$restTime; - emit.mode = danmu.mode; - result.push(emit); - }); - return result; + return this.renderer.visibles; } // 计算弹幕速度 get speed() { @@ -843,8 +954,7 @@ class Danmuku { return this; this.queue = []; this.states = { wait: [], ready: [], emit: [], stop: [] }; - this.$refs = []; - this.$danmuku.textContent = ""; + this.renderer.clear(); } for (let index = 0; index < danmus.length; index++) { if (!task.active()) @@ -961,8 +1071,7 @@ class Danmuku { } // 计算DOM的left值,受到旋屏影响 getLeft($ref) { - const rect = $ref.getBoundingClientRect(); - return this.isRotate ? rect.top : rect.left; + return this.renderer.left($ref); } // 复杂运算交给 Web Worker 处理 postMessage(message = {}) { @@ -982,16 +1091,7 @@ class Danmuku { } // 重置弹幕到wait状态,回收弹幕DOM节点 makeWait(danmu) { - this.setState(danmu, "wait"); - if (danmu.$ref) { - danmu.$ref.style.cssText = Danmuku.cssText; - danmu.$ref.style.visibility = "hidden"; - danmu.$ref.style.marginLeft = "0px"; - danmu.$ref.style.transform = "translateX(0px)"; - danmu.$ref.style.transition = "transform 0s linear 0s"; - this.$refs.push(danmu.$ref); - danmu.$ref = null; - } + this.renderer.makeWait(danmu); } // 实时更新弹幕 update() { @@ -1001,62 +1101,16 @@ class Danmuku { } // 重置正在显示的弹幕: stop/emit 状态的弹幕 resize() { - const { clientWidth } = this.$player; - this.filter("stop", (danmu) => { - switch (danmu.mode) { - // 滚动的弹幕 - case 0: - danmu.$ref.style.left = `${clientWidth}px`; - break; - } - }); - this.filter("emit", (danmu) => { - danmu.$lastStartTime = Date.now(); - switch (danmu.mode) { - // 滚动的弹幕 - case 0: { - const distance = clientWidth + danmu.$ref.clientWidth; - danmu.$ref.style.left = `${clientWidth}px`; - danmu.$ref.style.transform = `translateX(${-distance}px)`; - danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; - break; - } - } - }); + this.renderer.resize(); } // 继续弹幕 continue() { - const { clientWidth } = this.$player; - this.filter("stop", (danmu) => { - this.setState(danmu, "emit"); - danmu.$lastStartTime = Date.now(); - switch (danmu.mode) { - // 继续滚动的弹幕 - case 0: { - const distance = clientWidth + danmu.$ref.clientWidth; - danmu.$ref.style.transform = `translateX(${-distance}px)`; - danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; - break; - } - } - }); + this.renderer.continue(); return this; } // 暂停弹幕 suspend() { - const { clientWidth } = this.$player; - this.filter("emit", (danmu) => { - this.setState(danmu, "stop"); - switch (danmu.mode) { - // 停止滚动的弹幕 - case 0: { - const translateX = clientWidth - (this.getLeft(danmu.$ref) - this.getLeft(this.$player)); - danmu.$ref.style.transform = `translateX(${-translateX}px)`; - danmu.$ref.style.transition = "transform 0s linear 0s"; - break; - } - } - }); + this.renderer.suspend(); return this; } stop() { @@ -1120,6 +1174,7 @@ class Danmuku { this.workerClient?.dispose(); } } finally { + this.renderer.destroy(); this.art.off("video:play", this.start); this.art.off("video:playing", this.start); this.art.off("video:pause", this.stop); @@ -1131,164 +1186,529 @@ class Danmuku { } } } -const lib = { - map(value, inMin, inMax, outMin, outMax) { - return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; - }, - range(start, end, tick) { - const s = Math.round(start / tick) * tick; - return Array.from( - { - length: Math.floor((end - start) / tick) - }, - (v, k) => { - return k * tick + s; - } - ); +const MAX_HEATMAP_POINTS = 4294967295; +function upperBound(values, value) { + let start = 0; + let end = values.length; + while (start < end) { + const middle = Math.floor((start + end) / 2); + if (values[middle] <= value) + start = middle + 1; + else end = middle; } -}; + return start; +} +function sampleHeatmap(queue, width, duration, sampling) { + if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(duration) || duration <= 0 || !Number.isFinite(sampling) || sampling <= 0 || Math.floor(width / sampling) + 1 > MAX_HEATMAP_POINTS) { + return []; + } + const times = queue.map((item) => item.time).filter((time) => typeof time === "number" && !Number.isNaN(time)).sort((a, b) => a - b); + const points = []; + const gap = duration / width; + if (!Number.isFinite(gap)) + return []; + for (let x = 0; x <= width; x += sampling) { + if (points.length >= MAX_HEATMAP_POINTS || x + sampling === x) + return []; + const left = x * gap; + const right = (x + sampling) * gap; + points.push([x, upperBound(times, right) - upperBound(times, left)]); + } + return points; +} +function map(value, inMin, inMax, outMin, outMax) { + return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; +} function line(pointA, pointB) { const lengthX = pointB[0] - pointA[0]; const lengthY = pointB[1] - pointA[1]; - return { - length: Math.sqrt(lengthX ** 2 + lengthY ** 2), - angle: Math.atan2(lengthY, lengthX) - }; + return { length: Math.sqrt(lengthX ** 2 + lengthY ** 2), angle: Math.atan2(lengthY, lengthX) }; } +function heatmapGeometry({ width, height, duration, queue, option, points: input = [] }) { + if (![width, height, duration].every((value) => Number.isFinite(value) && value > 0)) + return null; + const defaults = { + xMin: 0, + xMax: width, + yMin: 0, + yMax: 128, + scale: 0.25, + opacity: 0.2, + minHeight: Math.floor(height * 0.05), + sampling: Math.max(1, Math.floor(width / 100)), + smoothing: 0.2, + flattening: 0.2 + }; + const options = Object.assign({}, defaults, typeof option === "object" ? option : {}); + for (const key of Object.keys(defaults)) { + if (!Number.isFinite(options[key])) + options[key] = defaults[key]; + } + if (options.sampling <= 0) + options.sampling = defaults.sampling; + if (options.xMin === options.xMax || options.yMin === options.yMax) + return null; + const points = Array.isArray(input) && input.length ? [...input] : sampleHeatmap(queue, width, duration, options.sampling); + if (!points.length || !points.every((point) => Array.isArray(point) && Number.isFinite(point[0]) && Number.isFinite(point[1]))) { + return null; + } + const lastPoint = points[points.length - 1]; + if (lastPoint[0] !== width) { + if (points.length === MAX_HEATMAP_POINTS) + return null; + points.push([width, lastPoint[1]]); + } + let yMin = Infinity; + let yMax = -Infinity; + for (const point of points) { + yMin = Math.min(yMin, point[1]); + yMax = Math.max(yMax, point[1]); + } + const yMid = (yMin + yMax) / 2; + for (const point of points) { + const y = point[1]; + point[1] = y * (y > yMid ? 1 + options.scale : 1 - options.scale) + options.minHeight; + } + const controlPoint = (current, previous, next, reverse) => { + const geometry = line(previous || current, next || current); + const flat = map(Math.cos(geometry.angle) * options.flattening, 0, 1, 1, 0); + const angle = geometry.angle * flat + (reverse ? Math.PI : 0); + const length = geometry.length * options.smoothing; + return [current[0] + Math.cos(angle) * length, current[1] + Math.sin(angle) * length]; + }; + const positions = points.map((point) => [map(point[0], options.xMin, options.xMax, 0, width), map(point[1], options.yMin, options.yMax, height, 0)]); + if (!positions.every((point) => point.every(Number.isFinite))) + return null; + const path = positions.map((point, index, all) => { + if (index === 0) + return `M ${all[all.length - 1][0]},${height} L ${point[0]},${height} L ${point[0]},${point[1]}`; + const start = controlPoint(all[index - 1], all[index - 2], point); + const end = controlPoint(point, all[index - 1], all[index + 1], true); + return `C ${start[0]},${start[1]} ${end[0]},${end[1]} ${point[0]},${point[1]}${index === all.length - 1 ? " z" : ""}`; + }).join(" "); + if (/NaN|Infinity/u.test(path)) + return null; + return { path, opacity: options.opacity, width, height }; +} +let nextGradient = 0; function heatmap(art, danmuku, option) { + if (art.isDestroy) + return; const { query } = art.constructor.utils; - art.controls.add({ - name: "heatmap", - position: "top", - html: "", - style: { - position: "absolute", - top: "-100px", - left: "0px", - right: "0px", - height: "100px", - width: "100%", - pointerEvents: "none" - }, - mounted($heatmap) { - let $start = null; - let $stop = null; - function update(arg = []) { - $start = null; - $stop = null; - $heatmap.innerHTML = ""; - if (!art.duration || art.option.isLive) + let gradient; + const subscriptions = []; + let element; + let start; + let stop; + let closed = false; + const active = () => !closed && !art.isDestroy; + function listen(name, callback) { + subscriptions.push([name, callback]); + art.on(name, callback); + } + function dispose(removeControl) { + if (closed) + return; + closed = true; + start = null; + stop = null; + let failure; + let failed = false; + const attempt = (callback) => { + try { + callback(); + } catch (error) { + if (!failed) + failure = error; + failed = true; + } + }; + for (const [name, callback] of subscriptions.splice(0)) + attempt(() => art.off(name, callback)); + if (removeControl && element && art.controls.heatmap === element) + attempt(() => art.controls.remove("heatmap")); + element = void 0; + if (failed) + throw failure; + } + function progress(value) { + if (active() && start && stop) { + start.setAttribute("offset", `${value * 100}%`); + stop.setAttribute("offset", `${value * 100}%`); + } + } + function draw(points = []) { + if (!active()) + return; + start = null; + stop = null; + element.innerHTML = ""; + if (art.option.isLive) + return; + const shape = heatmapGeometry({ width: element.offsetWidth, height: element.offsetHeight, duration: art.duration, queue: danmuku.queue, option, points }); + if (!shape || !active()) + return; + const document2 = element.ownerDocument; + if (!gradient) + gradient = document2 ? "heatmap-solids" : `heatmap-solids-${++nextGradient}`; + while (document2?.getElementById(gradient)) + gradient = `heatmap-solids-${++nextGradient}`; + element.innerHTML = ` + + + + + + + + + + + + `; + start = query("#heatmap-start", element); + stop = query("#heatmap-stop", element); + progress(art.played); + } + function update(points) { + try { + draw(points); + } catch (error) { + try { + dispose(true); + } catch { + } + throw error; + } + } + listen("destroy", () => dispose(true)); + try { + art.controls.add({ + name: "heatmap", + position: "top", + html: "", + style: { + position: "absolute", + top: "-100px", + left: "0px", + right: "0px", + height: "100px", + width: "100%", + pointerEvents: "none" + }, + beforeUnmount() { + dispose(false); + }, + mounted(value) { + element = value; + if (closed) { + if (art.controls.heatmap === value) + art.controls.remove("heatmap"); + element = void 0; return; - const svg = { - w: $heatmap.offsetWidth, - h: $heatmap.offsetHeight - }; - const options = { - xMin: 0, - xMax: svg.w, - yMin: 0, - yMax: 128, - scale: 0.25, - opacity: 0.2, - minHeight: Math.floor(svg.h * 0.05), - sampling: Math.floor(svg.w / 100), - smoothing: 0.2, - flattening: 0.2 - }; - if (typeof option === "object") { - Object.assign(options, option); } - let points = []; - if (Array.isArray(arg) && arg.length) { - points = [...arg]; - } else { - const gap = art.duration / svg.w; - for (let x = 0; x <= svg.w; x += options.sampling) { - const y = danmuku.queue.filter( - ({ time }) => time > x * gap && time <= (x + options.sampling) * gap - ).length; - points.push([x, y]); + if (!active()) { + dispose(true); + return; + } + listen("video:timeupdate", () => progress(art.played)); + listen("setBar", (type, percentage) => { + if (type === "played") + progress(percentage); + }); + listen("ready", () => update()); + listen("resize", () => update()); + listen("artplayerPluginDanmuku:loaded", () => update()); + listen("artplayerPluginDanmuku:points", (points) => update(points)); + } + }); + } catch (error) { + try { + dispose(true); + } catch { + } + throw error; + } + return () => dispose(true); +} +const properties = /* @__PURE__ */ new WeakMap(); +class SettingLifecycle { + constructor(art) { + this.art = art; + this.closed = false; + this.disposers = /* @__PURE__ */ new Set(); + this.cancelled = new Promise((resolve) => this.cancel = resolve); + } + get active() { + return !this.closed && !this.art.isDestroy; + } + own(dispose) { + if (this.closed) + dispose(); + else this.disposers.add(dispose); + } + write(target, key, value, apply = (value2) => target[key] = value2) { + if (!this.active) + return; + const priority = () => typeof target.getPropertyPriority === "function" ? target.getPropertyPriority(key) : void 0; + let keys = properties.get(target); + if (!keys) { + keys = /* @__PURE__ */ new Map(); + properties.set(target, keys); + } + let entry = keys.get(key); + if (!entry) { + entry = { + original: target[key], + present: key in target || target[key] !== void 0, + priority: priority(), + owners: /* @__PURE__ */ new Map(), + apply + }; + keys.set(key, entry); + } + if (!entry.owners.has(this)) { + this.own(() => { + const owners = [...entry.owners.keys()]; + const current = owners[owners.length - 1] === this; + entry.owners.delete(this); + if (current && target[key] === entry.last && priority() === entry.lastPriority) { + const values = [...entry.owners.values()]; + if (values.length) { + entry.apply(values[values.length - 1]); + entry.last = target[key]; + entry.lastPriority = priority(); + } else if (entry.priority !== void 0) { + target.setProperty(key, entry.original, entry.priority); + } else if (entry.present) { + entry.apply(entry.original); + } else { + delete target[key]; } } - if (points.length === 0) - return; - const lastPoint = points[points.length - 1]; - const lastX = lastPoint[0]; - const lastY = lastPoint[1]; - if (lastX !== svg.w) { - points.push([svg.w, lastY]); + if (!entry.owners.size) { + keys.delete(key); + if (!keys.size) + properties.delete(target); } - const yPoints = points.map((point) => point[1]); - const yMin = Math.min(...yPoints); - const yMax = Math.max(...yPoints); - const yMid = (yMin + yMax) / 2; - for (let i = 0; i < points.length; i++) { - const point = points[i]; - const y = point[1]; - point[1] = y * (y > yMid ? 1 + options.scale : 1 - options.scale) + options.minHeight; - } - const controlPoint = (current, previous, next, reverse) => { - const p = previous || current; - const n = next || current; - const o = line(p, n); - const flat = lib.map(Math.cos(o.angle) * options.flattening, 0, 1, 1, 0); - const angle = o.angle * flat + (reverse ? Math.PI : 0); - const length = o.length * options.smoothing; - const x = current[0] + Math.cos(angle) * length; - const y = current[1] + Math.sin(angle) * length; - return [x, y]; - }; - const bezierCommand = (point, i, a) => { - const cps = controlPoint(a[i - 1], a[i - 2], point); - const cpe = controlPoint(point, a[i - 1], a[i + 1], true); - const close = i === a.length - 1 ? " z" : ""; - return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}${close}`; - }; - const pointsPositions = points.map((e) => { - const x = lib.map(e[0], options.xMin, options.xMax, 0, svg.w); - const y = lib.map(e[1], options.yMin, options.yMax, svg.h, 0); - return [x, y]; - }); - const pathD = pointsPositions.reduce( - (acc, e, i, a) => i === 0 ? `M ${a[a.length - 1][0]},${svg.h} L ${e[0]},${svg.h} L ${e[0]},${e[1]}` : `${acc} ${bezierCommand(e, i, a)}`, - "" - ); - $heatmap.innerHTML = ` - - - - - - - - - - - - `; - $start = query("#heatmap-start", $heatmap); - $stop = query("#heatmap-stop", $heatmap); - $start.setAttribute("offset", `${art.played * 100}%`); - $stop.setAttribute("offset", `${art.played * 100}%`); + }); + } + entry.owners.delete(this); + entry.owners.set(this, value); + entry.apply(value); + entry.last = target[key]; + entry.lastPriority = priority(); + } + on(name, callback) { + if (!this.active) + return; + const lifecycle = this; + function listener(...args) { + if (lifecycle.active) + return callback.apply(this, args); + } + const dispose = () => this.art.off(name, listener); + this.own(dispose); + try { + this.art.on(name, listener); + if (!this.active) + dispose(); + } catch (error) { + this.release(dispose); + throw error; + } + } + proxy(target, name, callback) { + if (!this.active) + return; + const lifecycle = this; + function listener(...args) { + if (lifecycle.active) + return callback.apply(this, args); + } + let remove; + const dispose = () => { + if (typeof remove === "function") { + const registry = this.art.events; + if (typeof registry?.remove === "function" && registry.destroyEvents?.has(remove)) + registry.remove(remove); + else remove(); + } else { + target.removeEventListener?.(name, listener); } - art.on("video:timeupdate", () => { - if ($start && $stop) { - $start.setAttribute("offset", `${art.played * 100}%`); - $stop.setAttribute("offset", `${art.played * 100}%`); - } - }); - art.on("setBar", (type, percentage) => { - if ($start && $stop && type === "played") { - $start.setAttribute("offset", `${percentage * 100}%`); - $stop.setAttribute("offset", `${percentage * 100}%`); - } - }); - art.on("ready", () => update()); - art.on("resize", () => update()); - art.on("artplayerPluginDanmuku:loaded", () => update()); - art.on("artplayerPluginDanmuku:points", (points) => update(points)); + }; + this.own(dispose); + try { + remove = this.art.proxy(target, name, listener); + if (!this.active) + dispose(); + } catch (error) { + this.release(dispose); + throw error; + } + } + wait(value) { + return Promise.race([value, this.cancelled]); + } + release(dispose) { + try { + dispose(); + } catch (error) { + console.warn("Failed to dispose danmuku setting resource:", error); + } + } + close() { + if (this.closed) + return; + this.closed = true; + this.cancel(); + for (const dispose of this.disposers) { + this.release(dispose); + } + this.disposers.clear(); + } +} +async function emitSetting(setting) { + const { lifecycle, template: { $input } } = setting; + if (!lifecycle.active) + return; + const text = $input.value.trim(); + if (!text.length || setting.isLock || setting.emitting) + return; + const danmu = { + text, + mode: setting.option.mode, + color: setting.option.color, + time: setting.art.currentTime + }; + const report = (error) => { + if (lifecycle.active) + console.error("Error emitting danmuku:", error); + }; + try { + setting.emitting = true; + const state = await lifecycle.wait(setting.option.beforeEmit(danmu)); + if (!lifecycle.active) + return; + setting.emitting = false; + if (state !== true) + return; + danmu.border = true; + delete danmu.time; + Promise.resolve(setting.danmuku.emit(danmu)).catch(report); + if (!lifecycle.active) + return; + $input.value = ""; + setting.lock(); + } catch (error) { + report(error); + if (lifecycle.active) + setting.emitting = false; + } +} +function lockSetting(setting) { + if (!setting.lifecycle.active) + return; + const { addClass } = setting.utils; + const { $send } = setting.template; + clearTimeout(setting.timer); + setting.isLock = true; + let time = setting.option.lockTime; + $send.textContent = time; + addClass($send, "apd-lock"); + const loop = () => { + setting.timer = setTimeout(() => { + if (!setting.lifecycle.active) + return; + if (time === 0) { + setting.unlock(); + } else { + time -= 1; + $send.textContent = time; + loop(); + } + }, 1e3); + }; + loop(); +} +function unlockSetting(setting) { + clearTimeout(setting.timer); + setting.timer = null; + setting.isLock = false; + if (!setting.lifecycle.active) + return; + const { removeClass } = setting.utils; + const { $send } = setting.template; + $send.textContent = "发送"; + removeClass($send, "apd-lock"); +} +function createSlider({ min, max, container, findIndex, onChange, steps = [] }) { + const { lifecycle } = this; + if (!lifecycle.active) + return { reset() { + } }; + const { query, clamp, setStyle } = this.utils; + setStyle(container, "touch-action", "none"); + container.innerHTML = ` +
+
+ ${steps.map(() => `
`).join("")} +
+
+
+
+
+ ${steps.map((step) => step.hide ? "" : `
${step.name}
`).join("")} +
+ `; + const $dot = query(".apd-slider-dot", container); + const $progress = query(".apd-slider-progress", container); + let isDroging = false; + lifecycle.own(() => isDroging = false); + function reset(index) { + if (!lifecycle.active) + return; + if (index === void 0) + index = findIndex(); + if (index < min || index > max) + return; + const percentage = (index - min) / (max - min); + $dot.style.left = `${percentage * 100}%`; + if (steps.length === 0) { + $progress.style.width = $dot.style.left; + } + onChange(index); + } + function updateLeft(event) { + const { top, height, left, width } = container.getBoundingClientRect(); + if (this.art.isRotate) { + const value = clamp(event.clientY - top, 0, height); + const index = Math.round(value / height * (max - min) + min); + reset(index); + } else { + const value = clamp(event.clientX - left, 0, width); + const index = Math.round(value / width * (max - min) + min); + reset(index); + } + } + lifecycle.proxy(container, "click", (event) => { + updateLeft.call(this, event); + }); + lifecycle.proxy(container, "pointerdown", (event) => { + isDroging = event.button === 0; + }); + lifecycle.on("document:pointermove", (event) => { + if (isDroging) { + updateLeft.call(this, event); } }); + lifecycle.on("document:pointerup", (event) => { + if (isDroging) { + isDroging = false; + updateLeft.call(this, event); + } + }); + return { reset }; } const $check_off = ''; const $check_on = ''; @@ -1302,92 +1722,25 @@ const $mode_2_on = ''; const $on = ''; const $style = ''; -const style = ".artplayer-plugin-danmuku {\n display: flex;\n position: relative;\n z-index: 99;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n height: 32px;\n width: 100%;\n color: #fff;\n font-weight: 300;\n flex-shrink: 0;\n gap: 10px;\n}\n.artplayer-plugin-danmuku .apd-icon {\n cursor: pointer;\n opacity: 0.75;\n transition: all 0.2s ease;\n fill: #fff;\n}\n.artplayer-plugin-danmuku .apd-icon:hover {\n opacity: 1;\n}\n.artplayer-plugin-danmuku .apd-config {\n display: flex;\n position: relative;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 320px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-config-mode,\n.artplayer-plugin-danmuku .apd-config-slider,\n.artplayer-plugin-danmuku .apd-config-other,\n.artplayer-plugin-danmuku .apd-style-mode {\n margin-bottom: 15px;\n}\n.artplayer-plugin-danmuku .apd-modes {\n display: flex;\n align-items: center;\n margin-top: 5px;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode {\n cursor: pointer;\n text-align: center;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-config-slider {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.artplayer-plugin-danmuku .apd-config-slider .apd-value {\n width: 32px;\n text-align: right;\n}\n.artplayer-plugin-danmuku .apd-slider {\n position: relative;\n flex: 1;\n display: flex;\n height: 20px;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-line {\n position: relative;\n height: 2px;\n width: 100%;\n overflow: hidden;\n border-radius: 3px;\n background-color: rgba(255, 255, 255, 0.25);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point {\n width: 2px;\n height: 2px;\n border-radius: 50%;\n background-color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-progress {\n width: 0%;\n height: 100%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-dot {\n position: absolute;\n transform: translateX(-6px);\n left: 0%;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps {\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: absolute;\n bottom: -12px;\n width: calc(100% + 32px);\n color: #777;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step {\n flex-shrink: 0;\n width: 36px;\n text-align: center;\n scale: 0.95;\n}\n.artplayer-plugin-danmuku .apd-config-other {\n display: flex;\n align-items: center;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-check-off,\n.artplayer-plugin-danmuku .apd-config-other .apd-check-on {\n width: 16px;\n height: 16px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other {\n display: flex;\n align-items: center;\n cursor: pointer;\n gap: 2px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-emitter {\n display: flex;\n flex: 1;\n align-items: center;\n height: 100%;\n background-color: rgba(255, 255, 255, 0.25);\n border-radius: 5px;\n}\n.artplayer-plugin-danmuku .apd-style {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 200px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-colors {\n display: flex;\n flex-wrap: wrap;\n margin-top: 5px;\n gap: 8px;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color {\n width: 16px;\n height: 16px;\n border-radius: 2px;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active {\n border: 1px solid black;\n box-shadow: 0 0 0 1px #fff;\n}\n.artplayer-plugin-danmuku .apd-input {\n outline: none;\n height: 100%;\n flex: 1;\n min-width: 0;\n width: auto;\n border: none;\n line-height: 1;\n color: #fff;\n background-color: transparent;\n}\n.artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-send {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n width: 60px;\n flex-shrink: 0;\n cursor: pointer;\n text-shadow: none;\n border-top-right-radius: 5px;\n border-bottom-right-radius: 5px;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-send.apd-lock {\n cursor: not-allowed;\n color: #666;\n background-color: #e7e7e7;\n}\n.art-controls-center .apd-emitter {\n width: 260px;\n flex: none;\n}\n.art-fullscreen .artplayer-plugin-danmuku,\n.art-fullscreen-web .artplayer-plugin-danmuku {\n height: 38px;\n gap: 16px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on {\n width: 28px;\n height: 28px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter {\n width: 400px;\n flex: none;\n}\n.art-video-player > .artplayer-plugin-danmuku {\n position: absolute;\n left: 0;\n right: 0;\n bottom: -40px;\n padding: 0 10px;\n}\n.art-video-player:has(> .artplayer-plugin-danmuku) {\n margin-bottom: 40px;\n}\n[data-danmuku-emitter='false'] .apd-emitter {\n display: none !important;\n}\n[data-danmuku-emitter='false'] .art-controls-center .artplayer-plugin-danmuku {\n justify-content: flex-end;\n gap: 18px;\n}\n[data-danmuku-emitter='false'].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,\n[data-danmuku-emitter='false'].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku {\n gap: 24px;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-icon {\n fill: #333;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-emitter {\n background-color: #f1f2f3;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input {\n color: #000;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(0, 0, 0, 0.3);\n}\n[data-danmuku-visible='false'] .apd-toggle-off {\n display: block;\n}\n[data-danmuku-visible='false'] .apd-toggle-on {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-off {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-on {\n display: block;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-on {\n display: none;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-off {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-on {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-off {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-on {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-off {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-on {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-off {\n display: none;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-off {\n display: block;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-on {\n display: none;\n}\n[data-danmuku-mode0='false'] .art-danmuku [data-mode='0'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-off {\n display: none;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-on {\n display: block;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-off {\n display: block;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-on {\n display: none;\n}\n[data-danmuku-mode1='false'] .art-danmuku [data-mode='1'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-off {\n display: none;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-on {\n display: block;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-off {\n display: block;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-on {\n display: none;\n}\n[data-danmuku-mode2='false'] .art-danmuku [data-mode='2'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-off {\n display: none;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-on {\n display: block;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] path {\n fill: #00a1d6;\n}\n"; -class Setting { - constructor(art, danmuku) { - this.art = art; - this.danmuku = danmuku; - this.utils = art.constructor.utils; - const { setStyle } = this.utils; - const { $controlsCenter } = art.template; - setStyle($controlsCenter, "display", "flex"); - this.template = { - $controlsCenter, - $mount: $controlsCenter, - $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 = false; - this.isLock = false; - this.timer = null; - this.createTemplate(); - this.createSliders(); - this.createEvents(); - this.mount(this.option.mount); - art.on("resize", () => this.resize()); - art.on("fullscreen", (state) => this.onFullscreen(state)); - art.on("fullscreenWeb", (state) => this.onFullscreen(state)); - art.proxy(this.template.$config, "mouseenter", () => { - this.onMouseEnter({ - $control: this.template.$config, - $panel: this.template.$configPanel - }); - }); - art.proxy(this.template.$style, "mouseenter", () => { - this.onMouseEnter({ - $control: this.template.$style, - $panel: this.template.$stylePanel - }); - }); - } - static get icons() { - return { - $on, - $off, - $config, - $style, - $mode_0_off, - $mode_0_on, - $mode_1_off, - $mode_1_on, - $mode_2_off, - $mode_2_on, - $check_on, - $check_off - }; - } - get option() { - return this.danmuku.option; - } - get outside() { - return this.template.$mount !== this.template.$controlsCenter; - } - get TEMPLATE() { - const { option } = this; - return ` +function settingIcons() { + return { + $on, + $off, + $config, + $style, + $mode_0_off, + $mode_0_on, + $mode_1_off, + $mode_1_on, + $mode_2_off, + $mode_2_on, + $check_on, + $check_off + }; +} +function settingTemplate() { + const { option } = this; + return `
${$on}${$off}
@@ -1480,6 +1833,126 @@ class Setting {
发送
`; +} +const style = ".artplayer-plugin-danmuku {\n display: flex;\n position: relative;\n z-index: 99;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n height: 32px;\n width: 100%;\n color: #fff;\n font-weight: 300;\n flex-shrink: 0;\n gap: 10px;\n}\n.artplayer-plugin-danmuku .apd-icon {\n cursor: pointer;\n opacity: 0.75;\n transition: all 0.2s ease;\n fill: #fff;\n}\n.artplayer-plugin-danmuku .apd-icon:hover {\n opacity: 1;\n}\n.artplayer-plugin-danmuku .apd-config {\n display: flex;\n position: relative;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 320px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-config-mode,\n.artplayer-plugin-danmuku .apd-config-slider,\n.artplayer-plugin-danmuku .apd-config-other,\n.artplayer-plugin-danmuku .apd-style-mode {\n margin-bottom: 15px;\n}\n.artplayer-plugin-danmuku .apd-modes {\n display: flex;\n align-items: center;\n margin-top: 5px;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode {\n cursor: pointer;\n text-align: center;\n}\n.artplayer-plugin-danmuku .apd-modes .apd-mode:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-config-slider {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.artplayer-plugin-danmuku .apd-config-slider .apd-value {\n width: 32px;\n text-align: right;\n}\n.artplayer-plugin-danmuku .apd-slider {\n position: relative;\n flex: 1;\n display: flex;\n height: 20px;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-line {\n position: relative;\n height: 2px;\n width: 100%;\n overflow: hidden;\n border-radius: 3px;\n background-color: rgba(255, 255, 255, 0.25);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point {\n width: 2px;\n height: 2px;\n border-radius: 50%;\n background-color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-progress {\n width: 0%;\n height: 100%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-dot {\n position: absolute;\n transform: translateX(-6px);\n left: 0%;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps {\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: absolute;\n bottom: -12px;\n width: calc(100% + 32px);\n color: #777;\n}\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step {\n flex-shrink: 0;\n width: 36px;\n text-align: center;\n scale: 0.95;\n}\n.artplayer-plugin-danmuku .apd-config-other {\n display: flex;\n align-items: center;\n gap: 20px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-check-off,\n.artplayer-plugin-danmuku .apd-config-other .apd-check-on {\n width: 16px;\n height: 16px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other {\n display: flex;\n align-items: center;\n cursor: pointer;\n gap: 2px;\n}\n.artplayer-plugin-danmuku .apd-config-other .apd-other:hover {\n color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-emitter {\n display: flex;\n flex: 1;\n align-items: center;\n height: 100%;\n background-color: rgba(255, 255, 255, 0.25);\n border-radius: 5px;\n}\n.artplayer-plugin-danmuku .apd-style {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel {\n position: absolute;\n bottom: 24px;\n left: 0;\n width: 200px;\n padding: 10px;\n opacity: 0;\n pointer-events: none;\n}\n.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner {\n width: 100%;\n border-radius: 3px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 10px;\n}\n.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel {\n opacity: 100;\n pointer-events: all;\n}\n.artplayer-plugin-danmuku .apd-colors {\n display: flex;\n flex-wrap: wrap;\n margin-top: 5px;\n gap: 8px;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color {\n width: 16px;\n height: 16px;\n border-radius: 2px;\n cursor: pointer;\n}\n.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active {\n border: 1px solid black;\n box-shadow: 0 0 0 1px #fff;\n}\n.artplayer-plugin-danmuku .apd-input {\n outline: none;\n height: 100%;\n flex: 1;\n min-width: 0;\n width: auto;\n border: none;\n line-height: 1;\n color: #fff;\n background-color: transparent;\n}\n.artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(255, 255, 255, 0.5);\n}\n.artplayer-plugin-danmuku .apd-send {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n width: 60px;\n flex-shrink: 0;\n cursor: pointer;\n text-shadow: none;\n border-top-right-radius: 5px;\n border-bottom-right-radius: 5px;\n background-color: #00a1d6;\n}\n.artplayer-plugin-danmuku .apd-send.apd-lock {\n cursor: not-allowed;\n color: #666;\n background-color: #e7e7e7;\n}\n.art-controls-center .apd-emitter {\n width: 260px;\n flex: none;\n}\n.art-fullscreen .artplayer-plugin-danmuku,\n.art-fullscreen-web .artplayer-plugin-danmuku {\n height: 38px;\n gap: 16px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off,\n.art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on {\n width: 28px;\n height: 28px;\n}\n.art-fullscreen .artplayer-plugin-danmuku .apd-emitter,\n.art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter {\n width: 400px;\n flex: none;\n}\n.art-video-player > .artplayer-plugin-danmuku {\n position: absolute;\n left: 0;\n right: 0;\n bottom: -40px;\n padding: 0 10px;\n}\n.art-video-player:has(> .artplayer-plugin-danmuku) {\n margin-bottom: 40px;\n}\n[data-danmuku-emitter='false'] .apd-emitter {\n display: none !important;\n}\n[data-danmuku-emitter='false'] .art-controls-center .artplayer-plugin-danmuku {\n justify-content: flex-end;\n gap: 18px;\n}\n[data-danmuku-emitter='false'].art-fullscreen .art-controls-center .artplayer-plugin-danmuku,\n[data-danmuku-emitter='false'].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku {\n gap: 24px;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-icon {\n fill: #333;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-emitter {\n background-color: #f1f2f3;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input {\n color: #000;\n}\n[data-danmuku-theme='light'] > .artplayer-plugin-danmuku .apd-input::placeholder {\n color: rgba(0, 0, 0, 0.3);\n}\n[data-danmuku-visible='false'] .apd-toggle-off {\n display: block;\n}\n[data-danmuku-visible='false'] .apd-toggle-on {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-off {\n display: none;\n}\n[data-danmuku-visible='true'] .apd-toggle-on {\n display: block;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-on {\n display: none;\n}\n[data-danmuku-anti-overlap='false'] .apd-anti-overlap .apd-check-off {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-on {\n display: block;\n}\n[data-danmuku-anti-overlap='true'] .apd-anti-overlap .apd-check-off {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-on {\n display: none;\n}\n[data-danmuku-sync-video='false'] .apd-sync-video .apd-check-off {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-on {\n display: block;\n}\n[data-danmuku-sync-video='true'] .apd-sync-video .apd-check-off {\n display: none;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-off {\n display: block;\n}\n[data-danmuku-mode0='false'] .apd-config-mode .apd-mode-0-on {\n display: none;\n}\n[data-danmuku-mode0='false'] .art-danmuku [data-mode='0'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-off {\n display: none;\n}\n[data-danmuku-mode0='true'] .apd-config-mode .apd-mode-0-on {\n display: block;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='0'] .apd-style-mode [data-mode='0'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-off {\n display: block;\n}\n[data-danmuku-mode1='false'] .apd-config-mode .apd-mode-1-on {\n display: none;\n}\n[data-danmuku-mode1='false'] .art-danmuku [data-mode='1'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-off {\n display: none;\n}\n[data-danmuku-mode1='true'] .apd-config-mode .apd-mode-1-on {\n display: block;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='1'] .apd-style-mode [data-mode='1'] path {\n fill: #00a1d6;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-off {\n display: block;\n}\n[data-danmuku-mode2='false'] .apd-config-mode .apd-mode-2-on {\n display: none;\n}\n[data-danmuku-mode2='false'] .art-danmuku [data-mode='2'] {\n opacity: 0 !important;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-off {\n display: none;\n}\n[data-danmuku-mode2='true'] .apd-config-mode .apd-mode-2-on {\n display: block;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] {\n color: #00a1d6;\n}\n[data-danmuku-mode='2'] .apd-style-mode [data-mode='2'] path {\n fill: #00a1d6;\n}\n"; +if (typeof document !== "undefined") { + const id = "artplayer-plugin-danmuku"; + const pending = /* @__PURE__ */ Symbol.for("artplayer-plugin-danmuku.pending-style"); + let $style2 = document.getElementById(id) || document[pending]; + if (!$style2) { + $style2 = document.createElement("style"); + $style2.id = id; + if (document.readyState === "loading") { + document[pending] = $style2; + const ready = () => { + document.removeEventListener("DOMContentLoaded", ready); + if (document[pending] === $style2) + delete document[pending]; + const existing = document.getElementById(id); + if (existing) + existing.textContent = $style2.textContent; + else + (document.head || document.documentElement).appendChild($style2); + }; + document.addEventListener("DOMContentLoaded", ready, { once: true }); + } else { + (document.head || document.documentElement).appendChild($style2); + } + } + $style2.textContent = style; +} +class Setting { + constructor(art, danmuku) { + this.art = art; + this.danmuku = danmuku; + this.utils = art.constructor.utils; + const { setStyle } = this.utils; + const { $controlsCenter } = art.template; + this.template = { + $controlsCenter, + $mount: $controlsCenter, + $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 = false; + this.isLock = false; + this.timer = null; + this.lifecycle = new SettingLifecycle(art); + this.destroy = this.destroy.bind(this); + this.lifecycle.own(() => art.off("destroy", this.destroy)); + try { + art.on("destroy", this.destroy); + if (!this.lifecycle.active) + return; + this.lifecycle.write($controlsCenter.style, "display", "flex", (value) => setStyle($controlsCenter, "display", value)); + this.createTemplate(); + if (!this.lifecycle.active) + return; + this.createSliders(); + if (!this.lifecycle.active) + return; + this.createEvents(); + this.mount(this.option.mount); + if (!this.lifecycle.active) + return; + this.lifecycle.on("resize", () => this.resize()); + this.lifecycle.on("fullscreen", (state) => this.onFullscreen(state)); + this.lifecycle.on("fullscreenWeb", (state) => this.onFullscreen(state)); + this.lifecycle.proxy(this.template.$config, "mouseenter", () => { + this.onMouseEnter({ + $control: this.template.$config, + $panel: this.template.$configPanel + }); + }); + this.lifecycle.proxy(this.template.$style, "mouseenter", () => { + this.onMouseEnter({ + $control: this.template.$style, + $panel: this.template.$stylePanel + }); + }); + } catch (error) { + try { + this.destroy(); + } catch (cleanupError) { + console.warn("Failed to roll back danmuku setting:", cleanupError); + } + throw error; + } + } + static get icons() { + return settingIcons(); + } + get option() { + return this.danmuku.option; + } + get outside() { + return this.template.$mount !== this.template.$controlsCenter; + } + get TEMPLATE() { + return settingTemplate.call(this); } get OPACITY() { return { @@ -1577,6 +2050,8 @@ class Setting { return query(selector, $danmuku); } append(el, target) { + if (!this.lifecycle.active) + return; const { append } = this.utils; const children = [...el.children]; if (children.includes(target)) @@ -1584,19 +2059,21 @@ class Setting { append(el, target); } setData(key, value) { + if (!this.lifecycle.active) + return; const { $player } = this.art.template; const { $mount } = this.template; - $player.dataset[key] = value; + this.lifecycle.write($player.dataset, key, value); if (this.outside) { - $mount.dataset[key] = value; + this.lifecycle.write($mount.dataset, key, value); } } createTemplate() { const { createElement, tooltip } = this.utils; const $danmuku = createElement("div"); + this.template.$danmuku = $danmuku; $danmuku.className = "artplayer-plugin-danmuku"; $danmuku.innerHTML = this.TEMPLATE; - this.template.$danmuku = $danmuku; this.template.$toggle = this.query(".apd-toggle"); this.template.$config = this.query(".apd-config"); this.template.$configPanel = this.query(".apd-config-panel"); @@ -1618,22 +2095,22 @@ class Setting { this.template.$input = this.query(".apd-input"); this.template.$send = this.query(".apd-send"); const { $toggle } = this.template; - this.art.on("artplayerPluginDanmuku:show", () => { + this.lifecycle.on("artplayerPluginDanmuku:show", () => { tooltip($toggle, "关闭弹幕"); }); - this.art.on("artplayerPluginDanmuku:hide", () => { + this.lifecycle.on("artplayerPluginDanmuku:hide", () => { tooltip($toggle, "打开弹幕"); }); } createEvents() { const { $toggle, $configModes, $styleModes, $colors, $antiOverlap, $syncVideo, $send, $input } = this.template; - this.art.proxy($toggle, "click", () => { + this.lifecycle.proxy($toggle, "click", () => { this.danmuku.config({ visible: !this.option.visible }); this.reset(); }); - this.art.proxy($configModes, "click", (event) => { + this.lifecycle.proxy($configModes, "click", (event) => { const $mode = event.target.closest(".apd-mode"); if (!$mode) return; @@ -1649,19 +2126,19 @@ class Setting { } this.reset(); }); - this.art.proxy($antiOverlap, "click", () => { + this.lifecycle.proxy($antiOverlap, "click", () => { this.danmuku.config({ antiOverlap: !this.option.antiOverlap }); this.reset(); }); - this.art.proxy($syncVideo, "click", () => { + this.lifecycle.proxy($syncVideo, "click", () => { this.danmuku.config({ synchronousPlayback: !this.option.synchronousPlayback }); this.reset(); }); - this.art.proxy($styleModes, "click", (event) => { + this.lifecycle.proxy($styleModes, "click", (event) => { const $mode = event.target.closest(".apd-mode"); if (!$mode) return; @@ -1671,7 +2148,7 @@ class Setting { }); this.reset(); }); - this.art.proxy($colors, "click", (event) => { + this.lifecycle.proxy($colors, "click", (event) => { const $color = event.target.closest(".apd-color"); if (!$color) return; @@ -1680,8 +2157,8 @@ class Setting { }); this.reset(); }); - this.art.proxy($send, "click", () => this.emit()); - this.art.proxy($input, "keypress", (event) => { + this.lifecycle.proxy($send, "click", () => this.emit()); + this.lifecycle.proxy($input, "keypress", (event) => { if (event.key === "Enter") { event.preventDefault(); this.emit(); @@ -1756,66 +2233,12 @@ class Setting { } }); } - createSlider({ min, max, container, findIndex, onChange, steps = [] }) { - const { query, clamp, setStyle } = this.utils; - setStyle(container, "touch-action", "none"); - container.innerHTML = ` -
-
- ${steps.map(() => `
`).join("")} -
-
-
-
-
- ${steps.map((step) => step.hide ? "" : `
${step.name}
`).join("")} -
- `; - const $dot = query(".apd-slider-dot", container); - const $progress = query(".apd-slider-progress", container); - let isDroging = false; - function reset(index = findIndex()) { - if (index < min || index > max) - return; - const percentage = (index - min) / (max - min); - $dot.style.left = `${percentage * 100}%`; - if (steps.length === 0) { - $progress.style.width = $dot.style.left; - } - onChange(index); - } - function updateLeft(event) { - const { top, height, left, width } = container.getBoundingClientRect(); - if (this.art.isRotate) { - const value = clamp(event.clientY - top, 0, height); - const index = Math.round(value / height * (max - min) + min); - reset(index); - } else { - const value = clamp(event.clientX - left, 0, width); - const index = Math.round(value / width * (max - min) + min); - reset(index); - } - } - this.art.proxy(container, "click", (event) => { - updateLeft.call(this, event); - }); - this.art.proxy(container, "pointerdown", (event) => { - isDroging = event.button === 0; - }); - this.art.on("document:pointermove", (event) => { - if (isDroging) { - updateLeft.call(this, event); - } - }); - this.art.on("document:pointerup", (event) => { - if (isDroging) { - isDroging = false; - updateLeft.call(this, event); - } - }); - return { reset }; + createSlider(option) { + return createSlider.call(this, option); } onFullscreen(state) { + if (!this.lifecycle.active) + return; const { $danmuku, $controlsCenter, $mount } = this.template; if (this.outside) { if (state) { @@ -1828,6 +2251,8 @@ class Setting { } } onMouseEnter({ $control, $panel }) { + if (!this.lifecycle.active) + return; const { $player } = this.art.template; const controlRect = $control.getBoundingClientRect(); const panelRect = $panel.getBoundingClientRect(); @@ -1843,66 +2268,18 @@ class Setting { $panel.style.left = `${-half}px`; } } - async emit() { - const { $input } = this.template; - const text = $input.value.trim(); - if (!text.length) - return; - if (this.isLock) - return; - if (this.emitting) - return; - const danmu = { - text, - mode: this.option.mode, - color: this.option.color, - time: this.art.currentTime - }; - try { - this.emitting = true; - const state = await this.option.beforeEmit(danmu); - this.emitting = false; - if (state !== true) - return; - danmu.border = true; - delete danmu.time; - this.danmuku.emit(danmu); - $input.value = ""; - this.lock(); - } catch (error) { - console.error("Error emitting danmuku:", error); - this.emitting = false; - } + emit() { + return emitSetting(this); } lock() { - const { addClass } = this.utils; - const { $send } = this.template; - this.isLock = true; - let time = this.option.lockTime; - $send.textContent = time; - addClass($send, "apd-lock"); - const loop = () => { - this.timer = setTimeout(() => { - if (time === 0) { - this.unlock(); - } else { - time -= 1; - $send.textContent = time; - loop(); - } - }, 1e3); - }; - loop(); + lockSetting(this); } unlock() { - const { removeClass } = this.utils; - const { $send } = this.template; - clearTimeout(this.timer); - this.isLock = false; - $send.textContent = "发送"; - removeClass($send, "apd-lock"); + unlockSetting(this); } resize() { + if (!this.lifecycle.active) + return; if (this.outside) return; if (this.art.fullscreen) @@ -1918,12 +2295,15 @@ class Setting { } } reset() { + if (!this.lifecycle.active) + return; const { inverseClass, tooltip } = this.utils; const { $toggle, $colors } = this.template; - this.slider.opacity.reset(); - this.slider.margin.reset(); - this.slider.fontSize.reset(); - this.slider.speed.reset(); + for (const slider of Object.values(this.slider)) { + slider.reset(); + if (!this.lifecycle.active) + return; + } this.setData("danmukuVisible", this.option.visible); this.setData("danmukuMode", this.option.mode); this.setData("danmukuColor", this.option.color); @@ -1941,6 +2321,8 @@ class Setting { this.resize(); } mount(target) { + if (!this.lifecycle.active) + return; const { errorHandle } = this.utils; const $el = typeof target === "string" ? document.querySelector(target) : target; errorHandle($el, `Can not find the mount point: ${target}`); @@ -1948,29 +2330,38 @@ class Setting { this.template.$mount = $el; this.reset(); } -} -if (typeof document !== "undefined") { - const id = "artplayer-plugin-danmuku"; - let $style2 = document.getElementById(id); - if (!$style2) { - $style2 = document.createElement("style"); - $style2.id = id; - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", () => { - document.head.appendChild($style2); - }); - } else { - (document.head || document.documentElement).appendChild($style2); - } + destroy() { + if (this.lifecycle.closed) + return; + this.lifecycle.close(); + clearTimeout(this.timer); + this.timer = null; + this.emitting = false; + this.isLock = false; + const $danmuku = this.template.$danmuku; + if ($danmuku?.parentElement) + $danmuku.parentElement.removeChild($danmuku); } - $style2.textContent = style; } function artplayerPluginDanmuku(option) { return (art) => { const danmuku = new Danmuku(art, option); - const setting = art.isDestroy ? void 0 : new Setting(art, danmuku); - if (!art.isDestroy && danmuku.option.heatmap) { - heatmap(art, danmuku, danmuku.option.heatmap); + let setting; + try { + setting = art.isDestroy ? void 0 : new Setting(art, danmuku); + if (!art.isDestroy && danmuku.option.heatmap) { + heatmap(art, danmuku, danmuku.option.heatmap); + } + } catch (error) { + try { + setting?.destroy(); + } catch { + } + try { + danmuku.destroy(); + } catch { + } + throw error; } return { name: "artplayerPluginDanmuku", diff --git a/packages/artplayer-plugin-danmuku/src/danmuku.js b/packages/artplayer-plugin-danmuku/src/danmuku.js index 25a721079..32e20afaa 100644 --- a/packages/artplayer-plugin-danmuku/src/danmuku.js +++ b/packages/artplayer-plugin-danmuku/src/danmuku.js @@ -1,6 +1,7 @@ import { defaultOption, normalizeOption, optionChanged, optionScheme } from './config' import { beginInput, cancelInputs, inputActive, readInput } from './input' import { filterState, readyItems, setItemState } from './queue' +import Renderer from './renderer' import Scheduler from './scheduler' import WorkerClient from './worker-client' import DanmuWorker from './worker.js?worker&inline' @@ -23,6 +24,7 @@ export default class Danmuku { this.index = 0 // 弹幕索引 this.worker = null this.workerClient = null + this.renderer = new Renderer(this) this.scheduler = new Scheduler(this) // 格式化后的配置项 @@ -158,12 +160,7 @@ export default class Danmuku { // 获取弹幕DOM节点 get $ref() { - const $ref = this.$refs.pop() || document.createElement('div') - $ref.style.cssText = Danmuku.cssText - $ref.dataset.mode = '' - $ref.dataset.id = '' - $ref.className = '' - return $ref + return this.renderer.acquire() } // 获取准备好发送的弹幕 @@ -173,34 +170,7 @@ export default class Danmuku { // 可见的弹幕的数据,用于计算下一个弹幕的top值 get visibles() { - const result = [] - const { clientWidth } = this.$player - const clientLeft = this.getLeft(this.$player) - - this.filter('emit', (danmu) => { - const top = danmu.$ref.offsetTop - const left = this.getLeft(danmu.$ref) - clientLeft - const height = danmu.$ref.clientHeight - const width = danmu.$ref.clientWidth - const distance = left + width - const right = clientWidth - distance - const speed = distance / danmu.$restTime - - const emit = {} - emit.top = top - emit.left = left - emit.height = height - emit.width = width - emit.right = right - emit.speed = speed - emit.distance = distance - emit.time = danmu.$restTime - emit.mode = danmu.mode - - result.push(emit) - }) - - return result + return this.renderer.visibles } // 计算弹幕速度 @@ -234,8 +204,7 @@ export default class Danmuku { return this this.queue = [] // 清空弹幕队列 this.states = { wait: [], ready: [], emit: [], stop: [] } // 清空弹幕状态池 - this.$refs = [] // 清空弹幕DOM节点池 - this.$danmuku.textContent = '' // 清空弹幕层 + this.renderer.clear() } // 逐个验证原始弹幕并转换到弹幕队列 @@ -391,8 +360,7 @@ export default class Danmuku { // 计算DOM的left值,受到旋屏影响 getLeft($ref) { - const rect = $ref.getBoundingClientRect() - return this.isRotate ? rect.top : rect.left + return this.renderer.left($ref) } // 复杂运算交给 Web Worker 处理 @@ -417,16 +385,7 @@ export default class Danmuku { // 重置弹幕到wait状态,回收弹幕DOM节点 makeWait(danmu) { - this.setState(danmu, 'wait') - if (danmu.$ref) { - danmu.$ref.style.cssText = Danmuku.cssText - danmu.$ref.style.visibility = 'hidden' - danmu.$ref.style.marginLeft = '0px' - danmu.$ref.style.transform = 'translateX(0px)' - danmu.$ref.style.transition = 'transform 0s linear 0s' - this.$refs.push(danmu.$ref) - danmu.$ref = null - } + this.renderer.makeWait(danmu) } // 实时更新弹幕 @@ -438,76 +397,18 @@ export default class Danmuku { // 重置正在显示的弹幕: stop/emit 状态的弹幕 resize() { - const { clientWidth } = this.$player - - this.filter('stop', (danmu) => { - switch (danmu.mode) { - // 滚动的弹幕 - case 0: - danmu.$ref.style.left = `${clientWidth}px` - break - default: - break - } - }) - - this.filter('emit', (danmu) => { - danmu.$lastStartTime = Date.now() - switch (danmu.mode) { - // 滚动的弹幕 - case 0: { - const distance = clientWidth + danmu.$ref.clientWidth - danmu.$ref.style.left = `${clientWidth}px` - danmu.$ref.style.transform = `translateX(${-distance}px)` - danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s` - break - } - default: - break - } - }) + this.renderer.resize() } // 继续弹幕 continue() { - const { clientWidth } = this.$player - this.filter('stop', (danmu) => { - this.setState(danmu, 'emit') // 转换为emit状态 - danmu.$lastStartTime = Date.now() - switch (danmu.mode) { - // 继续滚动的弹幕 - case 0: { - const distance = clientWidth + danmu.$ref.clientWidth - danmu.$ref.style.transform = `translateX(${-distance}px)` - danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s` - break - } - default: - break - } - }) - + this.renderer.continue() return this } // 暂停弹幕 suspend() { - const { clientWidth } = this.$player - this.filter('emit', (danmu) => { - this.setState(danmu, 'stop') // 转换为stop状态 - switch (danmu.mode) { - // 停止滚动的弹幕 - case 0: { - const translateX = clientWidth - (this.getLeft(danmu.$ref) - this.getLeft(this.$player)) - danmu.$ref.style.transform = `translateX(${-translateX}px)` - danmu.$ref.style.transition = 'transform 0s linear 0s' - break - } - default: - break - } - }) - + this.renderer.suspend() return this } @@ -581,6 +482,7 @@ export default class Danmuku { } } finally { + this.renderer.destroy() this.art.off('video:play', this.start) this.art.off('video:playing', this.start) this.art.off('video:pause', this.stop) diff --git a/packages/artplayer-plugin-danmuku/src/heatmap-geometry.js b/packages/artplayer-plugin-danmuku/src/heatmap-geometry.js new file mode 100644 index 000000000..e3dcc842c --- /dev/null +++ b/packages/artplayer-plugin-danmuku/src/heatmap-geometry.js @@ -0,0 +1,78 @@ +import { MAX_HEATMAP_POINTS, sampleHeatmap } from './heatmap-sampling' + +function map(value, inMin, inMax, outMin, outMax) { + return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin +} + +function line(pointA, pointB) { + const lengthX = pointB[0] - pointA[0] + const lengthY = pointB[1] - pointA[1] + return { length: Math.sqrt(lengthX ** 2 + lengthY ** 2), angle: Math.atan2(lengthY, lengthX) } +} + +export function heatmapGeometry({ width, height, duration, queue, option, points: input = [] }) { + if (![width, height, duration].every(value => Number.isFinite(value) && value > 0)) + return null + const defaults = { + xMin: 0, + xMax: width, + yMin: 0, + yMax: 128, + scale: 0.25, + opacity: 0.2, + minHeight: Math.floor(height * 0.05), + sampling: Math.max(1, Math.floor(width / 100)), + smoothing: 0.2, + flattening: 0.2, + } + const options = Object.assign({}, defaults, typeof option === 'object' ? option : {}) + for (const key of Object.keys(defaults)) { + if (!Number.isFinite(options[key])) + options[key] = defaults[key] + } + if (options.sampling <= 0) + options.sampling = defaults.sampling + if (options.xMin === options.xMax || options.yMin === options.yMax) + return null + const points = Array.isArray(input) && input.length ? [...input] : sampleHeatmap(queue, width, duration, options.sampling) + if (!points.length + || !points.every(point => Array.isArray(point) && Number.isFinite(point[0]) && Number.isFinite(point[1]))) { return null } + const lastPoint = points[points.length - 1] + if (lastPoint[0] !== width) { + if (points.length === MAX_HEATMAP_POINTS) + return null + points.push([width, lastPoint[1]]) + } + let yMin = Infinity + let yMax = -Infinity + for (const point of points) { + yMin = Math.min(yMin, point[1]) + yMax = Math.max(yMax, point[1]) + } + const yMid = (yMin + yMax) / 2 + // Historical points events copy only the outer array: callers observe these y writes. + for (const point of points) { + const y = point[1] + point[1] = y * (y > yMid ? 1 + options.scale : 1 - options.scale) + options.minHeight + } + const controlPoint = (current, previous, next, reverse) => { + const geometry = line(previous || current, next || current) + const flat = map(Math.cos(geometry.angle) * options.flattening, 0, 1, 1, 0) + const angle = geometry.angle * flat + (reverse ? Math.PI : 0) + const length = geometry.length * options.smoothing + return [current[0] + Math.cos(angle) * length, current[1] + Math.sin(angle) * length] + } + const positions = points.map(point => [map(point[0], options.xMin, options.xMax, 0, width), map(point[1], options.yMin, options.yMax, height, 0)]) + if (!positions.every(point => point.every(Number.isFinite))) + return null + const path = positions.map((point, index, all) => { + if (index === 0) + return `M ${all[all.length - 1][0]},${height} L ${point[0]},${height} L ${point[0]},${point[1]}` + const start = controlPoint(all[index - 1], all[index - 2], point) + const end = controlPoint(point, all[index - 1], all[index + 1], true) + return `C ${start[0]},${start[1]} ${end[0]},${end[1]} ${point[0]},${point[1]}${index === all.length - 1 ? ' z' : ''}` + }).join(' ') + if (/NaN|Infinity/u.test(path)) + return null + return { path, opacity: options.opacity, width, height } +} diff --git a/packages/artplayer-plugin-danmuku/src/heatmap-sampling.js b/packages/artplayer-plugin-danmuku/src/heatmap-sampling.js new file mode 100644 index 000000000..805c0da66 --- /dev/null +++ b/packages/artplayer-plugin-danmuku/src/heatmap-sampling.js @@ -0,0 +1,32 @@ +export const MAX_HEATMAP_POINTS = 0xFFFFFFFF + +function upperBound(values, value) { + let start = 0 + let end = values.length + while (start < end) { + const middle = Math.floor((start + end) / 2) + if (values[middle] <= value) + start = middle + 1 + else end = middle + } + return start +} + +export function sampleHeatmap(queue, width, duration, sampling) { + if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(duration) || duration <= 0 + || !Number.isFinite(sampling) || sampling <= 0 || Math.floor(width / sampling) + 1 > MAX_HEATMAP_POINTS) { return [] } + const times = queue.map(item => item.time).filter(time => typeof time === 'number' && !Number.isNaN(time)).sort((a, b) => a - b) + const points = [] + const gap = duration / width + if (!Number.isFinite(gap)) + return [] + // Keep the old floating-point x progression and strict-left/inclusive-right bins. + for (let x = 0; x <= width; x += sampling) { + if (points.length >= MAX_HEATMAP_POINTS || x + sampling === x) + return [] + const left = x * gap + const right = (x + sampling) * gap + points.push([x, upperBound(times, right) - upperBound(times, left)]) + } + return points +} diff --git a/packages/artplayer-plugin-danmuku/src/heatmap.js b/packages/artplayer-plugin-danmuku/src/heatmap.js index b326c17c8..3e9f67348 100644 --- a/packages/artplayer-plugin-danmuku/src/heatmap.js +++ b/packages/artplayer-plugin-danmuku/src/heatmap.js @@ -1,186 +1,146 @@ -const lib = { - map(value, inMin, inMax, outMin, outMax) { - return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin - }, - range(start, end, tick) { - const s = Math.round(start / tick) * tick - return Array.from( - { - length: Math.floor((end - start) / tick), - }, - (v, k) => { - return k * tick + s - }, - ) - }, -} +import { heatmapGeometry } from './heatmap-geometry' -function line(pointA, pointB) { - const lengthX = pointB[0] - pointA[0] - const lengthY = pointB[1] - pointA[1] - return { - length: Math.sqrt(lengthX ** 2 + lengthY ** 2), - angle: Math.atan2(lengthY, lengthX), - } -} +let nextGradient = 0 export default function heatmap(art, danmuku, option) { + if (art.isDestroy) + return const { query } = art.constructor.utils + let gradient + const subscriptions = [] + let element + let start + let stop + let closed = false - art.controls.add({ - name: 'heatmap', - position: 'top', - html: '', - style: { - position: 'absolute', - top: '-100px', - left: '0px', - right: '0px', - height: '100px', - width: '100%', - pointerEvents: 'none', - }, - mounted($heatmap) { - let $start = null - let $stop = null - - function update(arg = []) { - $start = null - $stop = null - $heatmap.innerHTML = '' - - if (!art.duration || art.option.isLive) - return - - const svg = { - w: $heatmap.offsetWidth, - h: $heatmap.offsetHeight, - } - - const options = { - xMin: 0, - xMax: svg.w, - yMin: 0, - yMax: 128, - scale: 0.25, - opacity: 0.2, - minHeight: Math.floor(svg.h * 0.05), - sampling: Math.floor(svg.w / 100), - smoothing: 0.2, - flattening: 0.2, - } - - if (typeof option === 'object') { - Object.assign(options, option) - } - - let points = [] - - if (Array.isArray(arg) && arg.length) { - points = [...arg] - } - else { - const gap = art.duration / svg.w - for (let x = 0; x <= svg.w; x += options.sampling) { - const y = danmuku.queue.filter( - ({ time }) => time > x * gap && time <= (x + options.sampling) * gap, - ).length - points.push([x, y]) - } - } - - if (points.length === 0) - return - - const lastPoint = points[points.length - 1] - const lastX = lastPoint[0] - const lastY = lastPoint[1] - if (lastX !== svg.w) { - points.push([svg.w, lastY]) - } - - const yPoints = points.map(point => point[1]) - const yMin = Math.min(...yPoints) - const yMax = Math.max(...yPoints) - const yMid = (yMin + yMax) / 2 - - for (let i = 0; i < points.length; i++) { - const point = points[i] - const y = point[1] - point[1] = y * (y > yMid ? 1 + options.scale : 1 - options.scale) + options.minHeight - } - - const controlPoint = (current, previous, next, reverse) => { - const p = previous || current - const n = next || current - const o = line(p, n) - const flat = lib.map(Math.cos(o.angle) * options.flattening, 0, 1, 1, 0) - const angle = o.angle * flat + (reverse ? Math.PI : 0) - const length = o.length * options.smoothing - const x = current[0] + Math.cos(angle) * length - const y = current[1] + Math.sin(angle) * length - return [x, y] - } - - const bezierCommand = (point, i, a) => { - const cps = controlPoint(a[i - 1], a[i - 2], point) - const cpe = controlPoint(point, a[i - 1], a[i + 1], true) - const close = i === a.length - 1 ? ' z' : '' - return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}${close}` - } - - const pointsPositions = points.map((e) => { - const x = lib.map(e[0], options.xMin, options.xMax, 0, svg.w) - const y = lib.map(e[1], options.yMin, options.yMax, svg.h, 0) - return [x, y] - }) - - const pathD = pointsPositions.reduce( - (acc, e, i, a) => - i === 0 - ? `M ${a[a.length - 1][0]},${svg.h} L ${e[0]},${svg.h} L ${e[0]},${e[1]}` - : `${acc} ${bezierCommand(e, i, a)}`, - '', - ) - - $heatmap.innerHTML = ` - - - - - - - - - - - - ` - - $start = query('#heatmap-start', $heatmap) - $stop = query('#heatmap-stop', $heatmap) - $start.setAttribute('offset', `${art.played * 100}%`) - $stop.setAttribute('offset', `${art.played * 100}%`) + const active = () => !closed && !art.isDestroy + function listen(name, callback) { + subscriptions.push([name, callback]) + art.on(name, callback) + } + function dispose(removeControl) { + if (closed) + return + closed = true + start = null + stop = null + let failure + let failed = false + const attempt = (callback) => { + try { + callback() } - - art.on('video:timeupdate', () => { - if ($start && $stop) { - $start.setAttribute('offset', `${art.played * 100}%`) - $stop.setAttribute('offset', `${art.played * 100}%`) + catch (error) { + if (!failed) + failure = error + failed = true + } + } + for (const [name, callback] of subscriptions.splice(0)) + attempt(() => art.off(name, callback)) + if (removeControl && element && art.controls.heatmap === element) + attempt(() => art.controls.remove('heatmap')) + element = undefined + if (failed) + throw failure + } + function progress(value) { + if (active() && start && stop) { + start.setAttribute('offset', `${value * 100}%`) + stop.setAttribute('offset', `${value * 100}%`) + } + } + function draw(points = []) { + if (!active()) + return + start = null + stop = null + element.innerHTML = '' + if (art.option.isLive) + return + const shape = heatmapGeometry({ width: element.offsetWidth, height: element.offsetHeight, duration: art.duration, queue: danmuku.queue, option, points }) + if (!shape || !active()) + return + const document = element.ownerDocument + if (!gradient) + gradient = document ? 'heatmap-solids' : `heatmap-solids-${++nextGradient}` + while (document?.getElementById(gradient)) + gradient = `heatmap-solids-${++nextGradient}` + element.innerHTML = ` + + + + + + + + + + + + ` + start = query('#heatmap-start', element) + stop = query('#heatmap-stop', element) + progress(art.played) + } + function update(points) { + try { + draw(points) + } + catch (error) { + try { + dispose(true) + } + catch {} + throw error + } + } + listen('destroy', () => dispose(true)) + try { + art.controls.add({ + name: 'heatmap', + position: 'top', + html: '', + style: { + position: 'absolute', + top: '-100px', + left: '0px', + right: '0px', + height: '100px', + width: '100%', + pointerEvents: 'none', + }, + beforeUnmount() { dispose(false) }, + mounted(value) { + element = value + if (closed) { + if (art.controls.heatmap === value) + art.controls.remove('heatmap') + element = undefined + return } - }) - - art.on('setBar', (type, percentage) => { - if ($start && $stop && type === 'played') { - $start.setAttribute('offset', `${percentage * 100}%`) - $stop.setAttribute('offset', `${percentage * 100}%`) + if (!active()) { + dispose(true) + return } - }) - - art.on('ready', () => update()) - art.on('resize', () => update()) - art.on('artplayerPluginDanmuku:loaded', () => update()) - art.on('artplayerPluginDanmuku:points', points => update(points)) - }, - }) + listen('video:timeupdate', () => progress(art.played)) + listen('setBar', (type, percentage) => { + if (type === 'played') + progress(percentage) + }) + listen('ready', () => update()) + listen('resize', () => update()) + listen('artplayerPluginDanmuku:loaded', () => update()) + listen('artplayerPluginDanmuku:points', points => update(points)) + }, + }) + } + catch (error) { + try { + dispose(true) + } + catch {} + throw error + } + return () => dispose(true) } diff --git a/packages/artplayer-plugin-danmuku/src/index.js b/packages/artplayer-plugin-danmuku/src/index.js index 1b6d8326c..f4e58131b 100644 --- a/packages/artplayer-plugin-danmuku/src/index.js +++ b/packages/artplayer-plugin-danmuku/src/index.js @@ -5,10 +5,23 @@ import Setting from './setting' export default function artplayerPluginDanmuku(option) { return (art) => { const danmuku = new Danmuku(art, option) - const setting = art.isDestroy ? undefined : new Setting(art, danmuku) - - if (!art.isDestroy && danmuku.option.heatmap) { - heatmap(art, danmuku, danmuku.option.heatmap) + let setting + try { + setting = art.isDestroy ? undefined : new Setting(art, danmuku) + if (!art.isDestroy && danmuku.option.heatmap) { + heatmap(art, danmuku, danmuku.option.heatmap) + } + } + catch (error) { + try { + setting?.destroy() + } + catch {} + try { + danmuku.destroy() + } + catch {} + throw error } return { diff --git a/packages/artplayer-plugin-danmuku/src/renderer.js b/packages/artplayer-plugin-danmuku/src/renderer.js new file mode 100644 index 000000000..d11d3876f --- /dev/null +++ b/packages/artplayer-plugin-danmuku/src/renderer.js @@ -0,0 +1,203 @@ +export default class Renderer { + constructor(owner) { + this.owner = owner + this.nodes = new Set() + } + + acquire() { + const $ref = this.owner.$refs.pop() || document.createElement('div') + $ref.style.cssText = this.owner.constructor.cssText + $ref.dataset.mode = '' + $ref.dataset.id = '' + $ref.className = '' + this.nodes.add($ref) + return $ref + } + + get visibles() { + const result = [] + const { clientWidth } = this.owner.$player + const clientLeft = this.owner.getLeft(this.owner.$player) + + this.owner.filter('emit', (danmu) => { + const top = danmu.$ref.offsetTop + const left = this.owner.getLeft(danmu.$ref) - clientLeft + const height = danmu.$ref.clientHeight + const width = danmu.$ref.clientWidth + const distance = left + width + const right = clientWidth - distance + const speed = distance / danmu.$restTime + + const emit = {} + emit.top = top + emit.left = left + emit.height = height + emit.width = width + emit.right = right + emit.speed = speed + emit.distance = distance + emit.time = danmu.$restTime + emit.mode = danmu.mode + + result.push(emit) + }) + + return result + } + + left($ref) { + const rect = $ref.getBoundingClientRect() + return this.owner.isRotate ? rect.top : rect.left + } + + makeWait(danmu) { + this.owner.setState(danmu, 'wait') + if (danmu.$ref) { + danmu.$ref.style.cssText = this.owner.constructor.cssText + danmu.$ref.style.visibility = 'hidden' + danmu.$ref.style.marginLeft = '0px' + danmu.$ref.style.transform = 'translateX(0px)' + danmu.$ref.style.transition = 'transform 0s linear 0s' + this.owner.$refs.push(danmu.$ref) + danmu.$ref = null + } + } + + resize() { + const { clientWidth } = this.owner.$player + + this.owner.filter('stop', (danmu) => { + switch (danmu.mode) { + // 滚动的弹幕 + case 0: + danmu.$ref.style.left = `${clientWidth}px` + break + default: + break + } + }) + + this.owner.filter('emit', (danmu) => { + danmu.$lastStartTime = Date.now() + switch (danmu.mode) { + // 滚动的弹幕 + case 0: { + const distance = clientWidth + danmu.$ref.clientWidth + danmu.$ref.style.left = `${clientWidth}px` + danmu.$ref.style.transform = `translateX(${-distance}px)` + danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s` + break + } + default: + break + } + }) + } + + continue() { + const { clientWidth } = this.owner.$player + this.owner.filter('stop', (danmu) => { + this.owner.setState(danmu, 'emit') // 转换为emit状态 + danmu.$lastStartTime = Date.now() + switch (danmu.mode) { + // 继续滚动的弹幕 + case 0: { + const distance = clientWidth + danmu.$ref.clientWidth + danmu.$ref.style.transform = `translateX(${-distance}px)` + danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s` + break + } + default: + break + } + }) + + return this + } + + suspend() { + const { clientWidth } = this.owner.$player + this.owner.filter('emit', (danmu) => { + this.owner.setState(danmu, 'stop') // 转换为stop状态 + switch (danmu.mode) { + // 停止滚动的弹幕 + case 0: { + const translateX = clientWidth - (this.owner.getLeft(danmu.$ref) - this.owner.getLeft(this.owner.$player)) + danmu.$ref.style.transform = `translateX(${-translateX}px)` + danmu.$ref.style.transition = 'transform 0s linear 0s' + break + } + default: + break + } + }) + + return this + } + + prepare(danmu, operation) { + const owner = this.owner + const ref = owner.$ref + operation.danmu = danmu + operation.ref = ref + danmu.$ref = ref + ref.textContent = danmu.text + owner.$danmuku.appendChild(ref) + ref.style.opacity = owner.option.opacity + ref.style.fontSize = `${owner.fontSize}px` + ref.style.color = danmu.color + ref.style.border = danmu.border ? `1px solid ${danmu.color}` : null + ref.style.backgroundColor = danmu.border ? 'rgb(0 0 0 / 50%)' : null + owner.utils.setStyles(ref, danmu.style) + return ref + } + + place(danmu, ref, top, distance, clientWidth) { + ref.style.top = `${top}px` + ref.style.visibility = 'visible' + ref.dataset.mode = danmu.mode + ref.dataset.id = danmu.id || '' + switch (danmu.mode) { + case 0: + ref.style.left = `${clientWidth}px` + ref.style.marginLeft = '0px' + ref.style.transform = `translateX(${-distance}px)` + ref.style.transition = `transform ${danmu.$restTime}s linear 0s` + break + case 1: + case 2: + ref.style.left = '50%' + ref.style.marginLeft = `-${ref.clientWidth / 2}px` + break + default: + break + } + } + + release(operation) { + const { danmu, ref } = operation + if (ref && danmu.$ref === ref) { + ref.style.cssText = this.owner.constructor.cssText + ref.style.visibility = 'hidden' + this.owner.$refs.push(ref) + danmu.$ref = null + } + operation.ref = null + } + + clear() { + this.destroy() + this.owner.$refs = [] + this.owner.$danmuku.textContent = '' + } + + destroy() { + for (const node of this.nodes) { + if (node.parentElement) + node.parentElement.removeChild(node) + } + for (const danmu of this.owner.queue) danmu.$ref = null + this.owner.$refs.length = 0 + this.nodes.clear() + } +} diff --git a/packages/artplayer-plugin-danmuku/src/scheduler.js b/packages/artplayer-plugin-danmuku/src/scheduler.js index c15412656..8425504c7 100644 --- a/packages/artplayer-plugin-danmuku/src/scheduler.js +++ b/packages/artplayer-plugin-danmuku/src/scheduler.js @@ -20,14 +20,7 @@ export default class Scheduler { } release(operation) { - const { danmu, ref } = operation - if (ref && danmu.$ref === ref) { - ref.style.cssText = this.owner.constructor.cssText - ref.style.visibility = 'hidden' - this.owner.$refs.push(ref) - danmu.$ref = null - } - operation.ref = null + this.owner.renderer.release(operation) } invalidate() { @@ -108,7 +101,6 @@ export default class Scheduler { async run(operation) { const owner = this.owner - const { setStyles } = owner.utils if (!owner.art.playing || !this.active(operation)) return owner.filter('emit', (danmu) => { @@ -141,18 +133,7 @@ export default class Scheduler { continue const { clientWidth, clientHeight } = owner.$player - const ref = owner.$ref - operation.danmu = danmu - operation.ref = ref - danmu.$ref = ref - ref.textContent = danmu.text - owner.$danmuku.appendChild(ref) - ref.style.opacity = owner.option.opacity - ref.style.fontSize = `${owner.fontSize}px` - ref.style.color = danmu.color - ref.style.border = danmu.border ? `1px solid ${danmu.color}` : null - ref.style.backgroundColor = danmu.border ? 'rgb(0 0 0 / 50%)' : null - setStyles(ref, danmu.style) + const ref = owner.renderer.prepare(danmu, operation) if (!this.active(operation)) return danmu.$lastStartTime = Date.now() @@ -172,25 +153,7 @@ export default class Scheduler { return if (top !== undefined) { owner.setState(danmu, 'emit') - ref.style.top = `${top}px` - ref.style.visibility = 'visible' - ref.dataset.mode = danmu.mode - ref.dataset.id = danmu.id || '' - switch (danmu.mode) { - case 0: - ref.style.left = `${clientWidth}px` - ref.style.marginLeft = '0px' - ref.style.transform = `translateX(${-distance}px)` - ref.style.transition = `transform ${danmu.$restTime}s linear 0s` - break - case 1: - case 2: - ref.style.left = '50%' - ref.style.marginLeft = `-${ref.clientWidth / 2}px` - break - default: - break - } + owner.renderer.place(danmu, ref, top, distance, clientWidth) operation.ref = null owner.art.emit('artplayerPluginDanmuku:visible', danmu) } diff --git a/packages/artplayer-plugin-danmuku/src/setting-lifecycle.js b/packages/artplayer-plugin-danmuku/src/setting-lifecycle.js new file mode 100644 index 000000000..83e86356f --- /dev/null +++ b/packages/artplayer-plugin-danmuku/src/setting-lifecycle.js @@ -0,0 +1,154 @@ +const properties = new WeakMap() + +export default class SettingLifecycle { + constructor(art) { + this.art = art + this.closed = false + this.disposers = new Set() + this.cancelled = new Promise(resolve => this.cancel = resolve) + } + + get active() { + return !this.closed && !this.art.isDestroy + } + + own(dispose) { + if (this.closed) + dispose() + else this.disposers.add(dispose) + } + + write(target, key, value, apply = value => target[key] = value) { + if (!this.active) + return + const priority = () => typeof target.getPropertyPriority === 'function' ? target.getPropertyPriority(key) : undefined + let keys = properties.get(target) + if (!keys) { + keys = new Map() + properties.set(target, keys) + } + let entry = keys.get(key) + if (!entry) { + entry = { + original: target[key], + present: key in target || target[key] !== undefined, + priority: priority(), + owners: new Map(), + apply, + } + keys.set(key, entry) + } + if (!entry.owners.has(this)) { + this.own(() => { + const owners = [...entry.owners.keys()] + const current = owners[owners.length - 1] === this + entry.owners.delete(this) + // A later user write belongs to the host, not this settings instance. + if (current && target[key] === entry.last && priority() === entry.lastPriority) { + const values = [...entry.owners.values()] + if (values.length) { + entry.apply(values[values.length - 1]) + entry.last = target[key] + entry.lastPriority = priority() + } + else if (entry.priority !== undefined) { + target.setProperty(key, entry.original, entry.priority) + } + else if (entry.present) { + entry.apply(entry.original) + } + else { + delete target[key] + } + } + if (!entry.owners.size) { + keys.delete(key) + if (!keys.size) + properties.delete(target) + } + }) + } + entry.owners.delete(this) + entry.owners.set(this, value) + entry.apply(value) + entry.last = target[key] + entry.lastPriority = priority() + } + + on(name, callback) { + if (!this.active) + return + const lifecycle = this + function listener(...args) { + if (lifecycle.active) + return callback.apply(this, args) + } + const dispose = () => this.art.off(name, listener) + this.own(dispose) + try { + this.art.on(name, listener) + if (!this.active) + dispose() + } + catch (error) { + this.release(dispose) + throw error + } + } + + proxy(target, name, callback) { + if (!this.active) + return + const lifecycle = this + function listener(...args) { + if (lifecycle.active) + return callback.apply(this, args) + } + let remove + const dispose = () => { + if (typeof remove === 'function') { + const registry = this.art.events + if (typeof registry?.remove === 'function' && registry.destroyEvents?.has(remove)) + registry.remove(remove) + else remove() + } + else { + target.removeEventListener?.(name, listener) + } + } + this.own(dispose) + try { + remove = this.art.proxy(target, name, listener) + if (!this.active) + dispose() + } + catch (error) { + this.release(dispose) + throw error + } + } + + wait(value) { + return Promise.race([value, this.cancelled]) + } + + release(dispose) { + try { + dispose() + } + catch (error) { + console.warn('Failed to dispose danmuku setting resource:', error) + } + } + + close() { + if (this.closed) + return + this.closed = true + this.cancel() + for (const dispose of this.disposers) { + this.release(dispose) + } + this.disposers.clear() + } +} diff --git a/packages/artplayer-plugin-danmuku/src/setting-send.js b/packages/artplayer-plugin-danmuku/src/setting-send.js new file mode 100644 index 000000000..62ed5f876 --- /dev/null +++ b/packages/artplayer-plugin-danmuku/src/setting-send.js @@ -0,0 +1,79 @@ +export async function emitSetting(setting) { + const { lifecycle, template: { $input } } = setting + if (!lifecycle.active) + return + const text = $input.value.trim() + if (!text.length || setting.isLock || setting.emitting) + return + const danmu = { + text, + mode: setting.option.mode, + color: setting.option.color, + time: setting.art.currentTime, + } + const report = (error) => { + if (lifecycle.active) + console.error('Error emitting danmuku:', error) + } + try { + setting.emitting = true + const state = await lifecycle.wait(setting.option.beforeEmit(danmu)) + if (!lifecycle.active) + return + setting.emitting = false + if (state !== true) + return + danmu.border = true + delete danmu.time + // emit inserts synchronously; observe its Promise without delaying the UI. + Promise.resolve(setting.danmuku.emit(danmu)).catch(report) + if (!lifecycle.active) + return + $input.value = '' + setting.lock() + } + catch (error) { + report(error) + if (lifecycle.active) + setting.emitting = false + } +} + +export function lockSetting(setting) { + if (!setting.lifecycle.active) + return + const { addClass } = setting.utils + const { $send } = setting.template + clearTimeout(setting.timer) + setting.isLock = true + let time = setting.option.lockTime + $send.textContent = time + addClass($send, 'apd-lock') + const loop = () => { + setting.timer = setTimeout(() => { + if (!setting.lifecycle.active) + return + if (time === 0) { + setting.unlock() + } + else { + time -= 1 + $send.textContent = time + loop() + } + }, 1000) + } + loop() +} + +export function unlockSetting(setting) { + clearTimeout(setting.timer) + setting.timer = null + setting.isLock = false + if (!setting.lifecycle.active) + return + const { removeClass } = setting.utils + const { $send } = setting.template + $send.textContent = '发送' + removeClass($send, 'apd-lock') +} diff --git a/packages/artplayer-plugin-danmuku/src/setting-slider.js b/packages/artplayer-plugin-danmuku/src/setting-slider.js new file mode 100644 index 000000000..65dab5573 --- /dev/null +++ b/packages/artplayer-plugin-danmuku/src/setting-slider.js @@ -0,0 +1,80 @@ +export default function createSlider({ min, max, container, findIndex, onChange, steps = [] }) { + const { lifecycle } = this + if (!lifecycle.active) + return { reset() {} } + const { query, clamp, setStyle } = this.utils + + setStyle(container, 'touch-action', 'none') + + container.innerHTML = ` +
+
+ ${steps.map(() => `
`).join('')} +
+
+
+
+
+ ${steps.map(step => (step.hide ? '' : `
${step.name}
`)).join('')} +
+ ` + + const $dot = query('.apd-slider-dot', container) + const $progress = query('.apd-slider-progress', container) + + let isDroging = false + + lifecycle.own(() => isDroging = false) + + function reset(index) { + if (!lifecycle.active) + return + if (index === undefined) + index = findIndex() + if (index < min || index > max) + return + const percentage = (index - min) / (max - min) + $dot.style.left = `${percentage * 100}%` + if (steps.length === 0) { + $progress.style.width = $dot.style.left + } + onChange(index) + } + + function updateLeft(event) { + const { top, height, left, width } = container.getBoundingClientRect() + if (this.art.isRotate) { + const value = clamp(event.clientY - top, 0, height) + const index = Math.round((value / height) * (max - min) + min) + reset(index) + } + else { + const value = clamp(event.clientX - left, 0, width) + const index = Math.round((value / width) * (max - min) + min) + reset(index) + } + } + + lifecycle.proxy(container, 'click', (event) => { + updateLeft.call(this, event) + }) + + lifecycle.proxy(container, 'pointerdown', (event) => { + isDroging = event.button === 0 + }) + + lifecycle.on('document:pointermove', (event) => { + if (isDroging) { + updateLeft.call(this, event) + } + }) + + lifecycle.on('document:pointerup', (event) => { + if (isDroging) { + isDroging = false + updateLeft.call(this, event) + } + }) + + return { reset } +} diff --git a/packages/artplayer-plugin-danmuku/src/setting-style.js b/packages/artplayer-plugin-danmuku/src/setting-style.js new file mode 100644 index 000000000..2d1aa68ae --- /dev/null +++ b/packages/artplayer-plugin-danmuku/src/setting-style.js @@ -0,0 +1,29 @@ +import style from './style.less?inline' + +if (typeof document !== 'undefined') { + const id = 'artplayer-plugin-danmuku' + const pending = Symbol.for('artplayer-plugin-danmuku.pending-style') + let $style = document.getElementById(id) || document[pending] + if (!$style) { + $style = document.createElement('style') + $style.id = id + if (document.readyState === 'loading') { + document[pending] = $style + const ready = () => { + document.removeEventListener('DOMContentLoaded', ready) + if (document[pending] === $style) + delete document[pending] + const existing = document.getElementById(id) + if (existing) + existing.textContent = $style.textContent + else + (document.head || document.documentElement).appendChild($style) + } + document.addEventListener('DOMContentLoaded', ready, { once: true }) + } + else { + (document.head || document.documentElement).appendChild($style) + } + } + $style.textContent = style +} diff --git a/packages/artplayer-plugin-danmuku/src/setting-template.js b/packages/artplayer-plugin-danmuku/src/setting-template.js new file mode 100644 index 000000000..b8597a03f --- /dev/null +++ b/packages/artplayer-plugin-danmuku/src/setting-template.js @@ -0,0 +1,126 @@ +import $check_off from './img/check_off.svg?raw' +import $check_on from './img/check_on.svg?raw' +import $config from './img/config.svg?raw' +import $mode_0_off from './img/mode_0_off.svg?raw' +import $mode_0_on from './img/mode_0_on.svg?raw' +import $mode_1_off from './img/mode_1_off.svg?raw' +import $mode_1_on from './img/mode_1_on.svg?raw' +import $mode_2_off from './img/mode_2_off.svg?raw' +import $mode_2_on from './img/mode_2_on.svg?raw' +import $off from './img/off.svg?raw' +import $on from './img/on.svg?raw' +import $style from './img/style.svg?raw' + +export function settingIcons() { + return { + $on, + $off, + $config, + $style, + $mode_0_off, + $mode_0_on, + $mode_1_off, + $mode_1_on, + $mode_2_off, + $mode_2_on, + $check_on, + $check_off, + } +} + +export function settingTemplate() { + const { option } = this + return ` +
+ ${$on}${$off} +
+
+ ${$config} +
+
+
+ 按类型屏蔽 +
+
+ ${$mode_0_off}${$mode_0_on} +
滚动
+
+
+ ${$mode_1_off}${$mode_1_on} +
顶部
+
+
+ ${$mode_2_off}${$mode_2_on} +
底部
+
+
+
+
+
+ ${$check_on}${$check_off} + 防止弹幕重叠 +
+
+ ${$check_on}${$check_off} + 同步视频速度 +
+
+
+ 不透明度 +
+
未知
+
+
+ 显示区域 +
+
未知
+
+
+ 弹幕字号 +
+
未知
+
+
+ 弹幕速度 +
+
未知
+
+
+
+
+
+
+ ${$style} +
+
+
+ 模式 +
+
+ ${$mode_0_on} +
滚动
+
+
+ ${$mode_1_on} +
顶部
+
+
+ ${$mode_2_on} +
底部
+
+
+
+
+ 颜色 +
+ ${this.COLOR.map(color => `
`).join('')} +
+
+
+
+
+ +
发送
+
+ ` +} diff --git a/packages/artplayer-plugin-danmuku/src/setting.js b/packages/artplayer-plugin-danmuku/src/setting.js index 6a18fee09..858ec8ee5 100644 --- a/packages/artplayer-plugin-danmuku/src/setting.js +++ b/packages/artplayer-plugin-danmuku/src/setting.js @@ -1,16 +1,8 @@ -import $check_off from './img/check_off.svg?raw' -import $check_on from './img/check_on.svg?raw' -import $config from './img/config.svg?raw' -import $mode_0_off from './img/mode_0_off.svg?raw' -import $mode_0_on from './img/mode_0_on.svg?raw' -import $mode_1_off from './img/mode_1_off.svg?raw' -import $mode_1_on from './img/mode_1_on.svg?raw' -import $mode_2_off from './img/mode_2_off.svg?raw' -import $mode_2_on from './img/mode_2_on.svg?raw' -import $off from './img/off.svg?raw' -import $on from './img/on.svg?raw' -import $style from './img/style.svg?raw' -import style from './style.less?inline' +import SettingLifecycle from './setting-lifecycle' +import { emitSetting, lockSetting, unlockSetting } from './setting-send' +import createSlider from './setting-slider' +import { settingIcons, settingTemplate } from './setting-template' +import './setting-style' export default class Setting { constructor(art, danmuku) { @@ -20,7 +12,6 @@ export default class Setting { const { setStyle } = this.utils const { $controlsCenter } = art.template - setStyle($controlsCenter, 'display', 'flex') this.template = { $controlsCenter, @@ -57,46 +48,59 @@ export default class Setting { this.isLock = false this.timer = null - this.createTemplate() - this.createSliders() - this.createEvents() + this.lifecycle = new SettingLifecycle(art) + this.destroy = this.destroy.bind(this) + this.lifecycle.own(() => art.off('destroy', this.destroy)) - this.mount(this.option.mount) + try { + art.on('destroy', this.destroy) + if (!this.lifecycle.active) + return + this.lifecycle.write($controlsCenter.style, 'display', 'flex', value => setStyle($controlsCenter, 'display', value)) - art.on('resize', () => this.resize()) - art.on('fullscreen', state => this.onFullscreen(state)) - art.on('fullscreenWeb', state => this.onFullscreen(state)) + this.createTemplate() + if (!this.lifecycle.active) + return + this.createSliders() + if (!this.lifecycle.active) + return + this.createEvents() - art.proxy(this.template.$config, 'mouseenter', () => { - this.onMouseEnter({ - $control: this.template.$config, - $panel: this.template.$configPanel, + this.mount(this.option.mount) + if (!this.lifecycle.active) + return + + this.lifecycle.on('resize', () => this.resize()) + this.lifecycle.on('fullscreen', state => this.onFullscreen(state)) + this.lifecycle.on('fullscreenWeb', state => this.onFullscreen(state)) + + this.lifecycle.proxy(this.template.$config, 'mouseenter', () => { + this.onMouseEnter({ + $control: this.template.$config, + $panel: this.template.$configPanel, + }) }) - }) - art.proxy(this.template.$style, 'mouseenter', () => { - this.onMouseEnter({ - $control: this.template.$style, - $panel: this.template.$stylePanel, + this.lifecycle.proxy(this.template.$style, 'mouseenter', () => { + this.onMouseEnter({ + $control: this.template.$style, + $panel: this.template.$stylePanel, + }) }) - }) + } + catch (error) { + try { + this.destroy() + } + catch (cleanupError) { + console.warn('Failed to roll back danmuku setting:', cleanupError) + } + throw error + } } static get icons() { - return { - $on, - $off, - $config, - $style, - $mode_0_off, - $mode_0_on, - $mode_1_off, - $mode_1_on, - $mode_2_off, - $mode_2_on, - $check_on, - $check_off, - } + return settingIcons() } get option() { @@ -108,100 +112,7 @@ export default class Setting { } get TEMPLATE() { - const { option } = this - return ` -
- ${$on}${$off} -
-
- ${$config} -
-
-
- 按类型屏蔽 -
-
- ${$mode_0_off}${$mode_0_on} -
滚动
-
-
- ${$mode_1_off}${$mode_1_on} -
顶部
-
-
- ${$mode_2_off}${$mode_2_on} -
底部
-
-
-
-
-
- ${$check_on}${$check_off} - 防止弹幕重叠 -
-
- ${$check_on}${$check_off} - 同步视频速度 -
-
-
- 不透明度 -
-
未知
-
-
- 显示区域 -
-
未知
-
-
- 弹幕字号 -
-
未知
-
-
- 弹幕速度 -
-
未知
-
-
-
-
-
-
- ${$style} -
-
-
- 模式 -
-
- ${$mode_0_on} -
滚动
-
-
- ${$mode_1_on} -
顶部
-
-
- ${$mode_2_on} -
底部
-
-
-
-
- 颜色 -
- ${this.COLOR.map(color => `
`).join('')} -
-
-
-
-
- -
发送
-
- ` + return settingTemplate.call(this) } get OPACITY() { @@ -308,6 +219,8 @@ export default class Setting { } append(el, target) { + if (!this.lifecycle.active) + return const { append } = this.utils const children = [...el.children] if (children.includes(target)) @@ -316,11 +229,13 @@ export default class Setting { } setData(key, value) { + if (!this.lifecycle.active) + return const { $player } = this.art.template const { $mount } = this.template - $player.dataset[key] = value + this.lifecycle.write($player.dataset, key, value) if (this.outside) { - $mount.dataset[key] = value + this.lifecycle.write($mount.dataset, key, value) } } @@ -328,10 +243,10 @@ export default class Setting { const { createElement, tooltip } = this.utils const $danmuku = createElement('div') + this.template.$danmuku = $danmuku $danmuku.className = 'artplayer-plugin-danmuku' $danmuku.innerHTML = this.TEMPLATE - this.template.$danmuku = $danmuku this.template.$toggle = this.query('.apd-toggle') this.template.$config = this.query('.apd-config') this.template.$configPanel = this.query('.apd-config-panel') @@ -355,11 +270,11 @@ export default class Setting { const { $toggle } = this.template - this.art.on('artplayerPluginDanmuku:show', () => { + this.lifecycle.on('artplayerPluginDanmuku:show', () => { tooltip($toggle, '关闭弹幕') }) - this.art.on('artplayerPluginDanmuku:hide', () => { + this.lifecycle.on('artplayerPluginDanmuku:hide', () => { tooltip($toggle, '打开弹幕') }) } @@ -367,14 +282,14 @@ export default class Setting { createEvents() { const { $toggle, $configModes, $styleModes, $colors, $antiOverlap, $syncVideo, $send, $input } = this.template - this.art.proxy($toggle, 'click', () => { + this.lifecycle.proxy($toggle, 'click', () => { this.danmuku.config({ visible: !this.option.visible, }) this.reset() }) - this.art.proxy($configModes, 'click', (event) => { + this.lifecycle.proxy($configModes, 'click', (event) => { const $mode = event.target.closest('.apd-mode') if (!$mode) return @@ -392,21 +307,21 @@ export default class Setting { this.reset() }) - this.art.proxy($antiOverlap, 'click', () => { + this.lifecycle.proxy($antiOverlap, 'click', () => { this.danmuku.config({ antiOverlap: !this.option.antiOverlap, }) this.reset() }) - this.art.proxy($syncVideo, 'click', () => { + this.lifecycle.proxy($syncVideo, 'click', () => { this.danmuku.config({ synchronousPlayback: !this.option.synchronousPlayback, }) this.reset() }) - this.art.proxy($styleModes, 'click', (event) => { + this.lifecycle.proxy($styleModes, 'click', (event) => { const $mode = event.target.closest('.apd-mode') if (!$mode) return @@ -417,7 +332,7 @@ export default class Setting { this.reset() }) - this.art.proxy($colors, 'click', (event) => { + this.lifecycle.proxy($colors, 'click', (event) => { const $color = event.target.closest('.apd-color') if (!$color) return @@ -427,9 +342,9 @@ export default class Setting { this.reset() }) - this.art.proxy($send, 'click', () => this.emit()) + this.lifecycle.proxy($send, 'click', () => this.emit()) - this.art.proxy($input, 'keypress', (event) => { + this.lifecycle.proxy($input, 'keypress', (event) => { if (event.key === 'Enter') { event.preventDefault() this.emit() @@ -509,79 +424,13 @@ export default class Setting { }) } - createSlider({ min, max, container, findIndex, onChange, steps = [] }) { - const { query, clamp, setStyle } = this.utils - - setStyle(container, 'touch-action', 'none') - - container.innerHTML = ` -
-
- ${steps.map(() => `
`).join('')} -
-
-
-
-
- ${steps.map(step => (step.hide ? '' : `
${step.name}
`)).join('')} -
- ` - - const $dot = query('.apd-slider-dot', container) - const $progress = query('.apd-slider-progress', container) - - let isDroging = false - - function reset(index = findIndex()) { - if (index < min || index > max) - return - const percentage = (index - min) / (max - min) - $dot.style.left = `${percentage * 100}%` - if (steps.length === 0) { - $progress.style.width = $dot.style.left - } - onChange(index) - } - - function updateLeft(event) { - const { top, height, left, width } = container.getBoundingClientRect() - if (this.art.isRotate) { - const value = clamp(event.clientY - top, 0, height) - const index = Math.round((value / height) * (max - min) + min) - reset(index) - } - else { - const value = clamp(event.clientX - left, 0, width) - const index = Math.round((value / width) * (max - min) + min) - reset(index) - } - } - - this.art.proxy(container, 'click', (event) => { - updateLeft.call(this, event) - }) - - this.art.proxy(container, 'pointerdown', (event) => { - isDroging = event.button === 0 - }) - - this.art.on('document:pointermove', (event) => { - if (isDroging) { - updateLeft.call(this, event) - } - }) - - this.art.on('document:pointerup', (event) => { - if (isDroging) { - isDroging = false - updateLeft.call(this, event) - } - }) - - return { reset } + createSlider(option) { + return createSlider.call(this, option) } onFullscreen(state) { + if (!this.lifecycle.active) + return const { $danmuku, $controlsCenter, $mount } = this.template if (this.outside) { if (state) { @@ -597,6 +446,8 @@ export default class Setting { } onMouseEnter({ $control, $panel }) { + if (!this.lifecycle.active) + return const { $player } = this.art.template const controlRect = $control.getBoundingClientRect() const panelRect = $panel.getBoundingClientRect() @@ -617,80 +468,21 @@ export default class Setting { } } - async emit() { - const { $input } = this.template - - const text = $input.value.trim() - if (!text.length) - return - if (this.isLock) - return - if (this.emitting) - return - - const danmu = { - text, - mode: this.option.mode, - color: this.option.color, - time: this.art.currentTime, - } - - try { - this.emitting = true - const state = await this.option.beforeEmit(danmu) - this.emitting = false - - if (state !== true) - return - - danmu.border = true - delete danmu.time - this.danmuku.emit(danmu) - $input.value = '' - - this.lock() - } - catch (error) { - console.error('Error emitting danmuku:', error) - this.emitting = false - } + emit() { + return emitSetting(this) } lock() { - const { addClass } = this.utils - const { $send } = this.template - - this.isLock = true - let time = this.option.lockTime - $send.textContent = time - addClass($send, 'apd-lock') - - const loop = () => { - this.timer = setTimeout(() => { - if (time === 0) { - this.unlock() - } - else { - time -= 1 - $send.textContent = time - loop() - } - }, 1000) - } - - loop() + lockSetting(this) } unlock() { - const { removeClass } = this.utils - const { $send } = this.template - clearTimeout(this.timer) - this.isLock = false - $send.textContent = '发送' - removeClass($send, 'apd-lock') + unlockSetting(this) } resize() { + if (!this.lifecycle.active) + return if (this.outside) return if (this.art.fullscreen) @@ -708,13 +500,16 @@ export default class Setting { } reset() { + if (!this.lifecycle.active) + return const { inverseClass, tooltip } = this.utils const { $toggle, $colors } = this.template - this.slider.opacity.reset() - this.slider.margin.reset() - this.slider.fontSize.reset() - this.slider.speed.reset() + for (const slider of Object.values(this.slider)) { + slider.reset() + if (!this.lifecycle.active) + return + } this.setData('danmukuVisible', this.option.visible) this.setData('danmukuMode', this.option.mode) @@ -737,6 +532,8 @@ export default class Setting { } mount(target) { + if (!this.lifecycle.active) + return const { errorHandle } = this.utils const $el = typeof target === 'string' ? document.querySelector(target) : target errorHandle($el, `Can not find the mount point: ${target}`) @@ -744,22 +541,17 @@ export default class Setting { this.template.$mount = $el this.reset() } -} -if (typeof document !== 'undefined') { - const id = 'artplayer-plugin-danmuku' - let $style = document.getElementById(id) - if (!$style) { - $style = document.createElement('style') - $style.id = id - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - document.head.appendChild($style) - }) - } - else { - (document.head || document.documentElement).appendChild($style) - } + destroy() { + if (this.lifecycle.closed) + return + this.lifecycle.close() + clearTimeout(this.timer) + this.timer = null + this.emitting = false + this.isLock = false + const $danmuku = this.template.$danmuku + if ($danmuku?.parentElement) + $danmuku.parentElement.removeChild($danmuku) } - $style.textContent = style } diff --git a/refactor/baselines/danmuku-resources-validation.json b/refactor/baselines/danmuku-resources-validation.json new file mode 100644 index 000000000..dc22f77a3 --- /dev/null +++ b/refactor/baselines/danmuku-resources-validation.json @@ -0,0 +1,59510 @@ +{ + "schemaVersion": 1, + "task": "PKG-DANMUKU-05", + "status": "done", + "capturedAt": "2026-09-13T12:43:51.757Z", + "baselineCommit": "12332df221597bacdeafd222fa70716031586e4e", + "units": [ + { + "format": "source", + "tests": 159, + "implementationDependent": 101, + "common": { + "internalWorker": 18, + "numericHistory": 3, + "settingHistory": 18, + "directHeatmapWithHistoryComparisons": 14, + "directRenderer": 5 + }, + "file": "refactor/.cache/danmuku05-candidate-source-final.log", + "sha256": "310fe7266bc1330d793f798b89c17209988dcf47f1a75d662a38f6dbb5650c75" + }, + { + "format": "main", + "tests": 159, + "implementationDependent": 101, + "common": { + "internalWorker": 18, + "numericHistory": 3, + "settingHistory": 18, + "directHeatmapWithHistoryComparisons": 14, + "directRenderer": 5 + }, + "file": "refactor/.cache/danmuku05-candidate-main-final.log", + "sha256": "0e14b6c5c1ed7986f9798ea7b5fff615d204f7a71b62ab5eac2a9a9d6e9a42cb" + }, + { + "format": "legacy", + "tests": 159, + "implementationDependent": 101, + "common": { + "internalWorker": 18, + "numericHistory": 3, + "settingHistory": 18, + "directHeatmapWithHistoryComparisons": 14, + "directRenderer": 5 + }, + "file": "refactor/.cache/danmuku05-candidate-legacy-final.log", + "sha256": "b28abc1227d02bf91db3254e3fcf0f47e96666dc940908a6c772e602b4422b8a" + } + ], + "completeSuite": { + "tests": 245, + "file": "refactor/.cache/danmuku05-suite-final.log", + "sha256": "57e26ae87c9f3cb4171dd181d11f7a8a754b05cfe95fc36ff17efcd686492a92" + }, + "browsers": [ + { + "format": "source", + "tests": 138, + "workers": 2, + "file": "refactor/.cache/danmuku05-browser-source-resource-final/report.json", + "sha256": "b389088a846451291a921ea094b0d22d16e6a74362febaf24107b26e8f62fb62", + "run": { + "file": "refactor/.cache/danmuku05-browser-source-resource-final/run.log", + "sha256": "9252f9a11a81835b068fb24d21a001202391386f1991f41e29c17237cd473df4" + }, + "specs": [ + { + "file": "refactor/.cache/danmuku05-browser-source-resource-final/danmuku-input.spec.js", + "sha256": "37a96982ea7bf2fb99df5e4d829ce1c3f512145092a440586c2d5ad7d0a2d6f3" + }, + { + "file": "refactor/.cache/danmuku05-browser-source-resource-final/danmuku-scheduler.spec.js", + "sha256": "ee67bc0f204f1f9090859ed9d1d0cfecc545129fff6278d2f17140b009390519" + }, + { + "file": "refactor/.cache/danmuku05-browser-source-resource-final/danmuku-resources.spec.js", + "sha256": "f0d09e962cb422a66bd644c52268aff8f288606ae9c93676fa979539743f9c6b" + } + ], + "archiveResultsRoot": "refactor/.cache/danmuku05-browser-source-resource-final/results", + "originalResultsRoot": "refactor/.cache/browser/results", + "versions": [ + "chromium: 153.0.8010.12", + "firefox: 155.0", + "webkit: 26.6" + ], + "cases": [ + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "chromium", + "durationMs": 445, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/ac8b8c16-1427-495a-ba03-0ce7b60d0e0c", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/7015dc70-160c-44ea-842b-a976969f2628", + "blob:http://127.0.0.1:8084/ac8b8c16-1427-495a-ba03-0ce7b60d0e0c" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/ac8b8c16-1427-495a-ba03-0ce7b60d0e0c", + "blob:http://127.0.0.1:8084/7015dc70-160c-44ea-842b-a976969f2628" + ], + "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", + "durationMs": 271, + "observations": [ + { + "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/3281f15b-4fd0-4b40-958d-e76d60af9056", + "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/713d0063-371e-4fa7-8eaf-a7fece601d35", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/b3653858-14d5-48ee-847d-488e9dd43a66", + "blob:http://127.0.0.1:8084/3281f15b-4fd0-4b40-958d-e76d60af9056", + "blob:http://127.0.0.1:8084/713d0063-371e-4fa7-8eaf-a7fece601d35" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/3281f15b-4fd0-4b40-958d-e76d60af9056", + "blob:http://127.0.0.1:8084/713d0063-371e-4fa7-8eaf-a7fece601d35" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/b3653858-14d5-48ee-847d-488e9dd43a66", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/3281f15b-4fd0-4b40-958d-e76d60af9056", + "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/713d0063-371e-4fa7-8eaf-a7fece601d35", + "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", + "durationMs": 310, + "observations": [ + { + "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", + "durationMs": 252, + "observations": [ + { + "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", + "durationMs": 366, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/80a66902-2bd7-4d54-9638-9b9e44edf34b", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/f1141647-a0ee-4dd9-a678-f577a8d5f0a7", + "blob:http://127.0.0.1:8084/80a66902-2bd7-4d54-9638-9b9e44edf34b" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/80a66902-2bd7-4d54-9638-9b9e44edf34b", + "blob:http://127.0.0.1:8084/f1141647-a0ee-4dd9-a678-f577a8d5f0a7" + ], + "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", + "durationMs": 322, + "observations": [ + { + "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/3f969185-5cd7-475c-be1c-8a2e34ac6ce5", + "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/00df37e2-23dc-4ca0-bde2-88800cf7316c", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/139220c6-868a-45d2-a0e6-c507b2dce7d1", + "blob:http://127.0.0.1:8084/3f969185-5cd7-475c-be1c-8a2e34ac6ce5", + "blob:http://127.0.0.1:8084/00df37e2-23dc-4ca0-bde2-88800cf7316c" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/3f969185-5cd7-475c-be1c-8a2e34ac6ce5", + "blob:http://127.0.0.1:8084/00df37e2-23dc-4ca0-bde2-88800cf7316c" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/139220c6-868a-45d2-a0e6-c507b2dce7d1", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/3f969185-5cd7-475c-be1c-8a2e34ac6ce5", + "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/00df37e2-23dc-4ca0-bde2-88800cf7316c", + "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", + "durationMs": 295, + "observations": [ + { + "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", + "durationMs": 293, + "observations": [ + { + "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", + "durationMs": 1949, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/f9d27a39-5884-4483-9487-2c5870c43e4f", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/81d97485-577f-4efb-add7-6e18f796cadb", + "blob:http://127.0.0.1:8084/f9d27a39-5884-4483-9487-2c5870c43e4f" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/f9d27a39-5884-4483-9487-2c5870c43e4f", + "blob:http://127.0.0.1:8084/81d97485-577f-4efb-add7-6e18f796cadb" + ], + "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", + "durationMs": 669, + "observations": [ + { + "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/8f0977cf-4317-4938-aa01-c405a49d09de", + "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/b7e21127-5e68-4b2c-92cd-30abbd5fd125", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/7a6b574b-d7fa-4470-bdd4-bd6c5d44f889", + "blob:http://127.0.0.1:8084/8f0977cf-4317-4938-aa01-c405a49d09de", + "blob:http://127.0.0.1:8084/b7e21127-5e68-4b2c-92cd-30abbd5fd125" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/8f0977cf-4317-4938-aa01-c405a49d09de", + "blob:http://127.0.0.1:8084/b7e21127-5e68-4b2c-92cd-30abbd5fd125" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/7a6b574b-d7fa-4470-bdd4-bd6c5d44f889", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/8f0977cf-4317-4938-aa01-c405a49d09de", + "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/b7e21127-5e68-4b2c-92cd-30abbd5fd125", + "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", + "durationMs": 645, + "observations": [ + { + "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", + "durationMs": 484, + "observations": [ + { + "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", + "durationMs": 2217, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/49e4de27-a694-486e-a755-5c3af2736f28", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/69270c58-7982-45d8-b6e7-911aedf10ede", + "blob:http://127.0.0.1:8084/49e4de27-a694-486e-a755-5c3af2736f28" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/49e4de27-a694-486e-a755-5c3af2736f28", + "blob:http://127.0.0.1:8084/69270c58-7982-45d8-b6e7-911aedf10ede" + ], + "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", + "durationMs": 538, + "observations": [ + { + "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/5b2e5d2a-e5cc-4386-96a4-08a39f5d6959", + "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/9d3addc7-1764-4e31-8f2f-20d8919888dd", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/ef6f4d03-2679-45ff-82f9-b8e43ee5068b", + "blob:http://127.0.0.1:8084/5b2e5d2a-e5cc-4386-96a4-08a39f5d6959", + "blob:http://127.0.0.1:8084/9d3addc7-1764-4e31-8f2f-20d8919888dd" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/5b2e5d2a-e5cc-4386-96a4-08a39f5d6959", + "blob:http://127.0.0.1:8084/9d3addc7-1764-4e31-8f2f-20d8919888dd" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/ef6f4d03-2679-45ff-82f9-b8e43ee5068b", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/5b2e5d2a-e5cc-4386-96a4-08a39f5d6959", + "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/9d3addc7-1764-4e31-8f2f-20d8919888dd", + "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", + "durationMs": 455, + "observations": [ + { + "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", + "durationMs": 436, + "observations": [ + { + "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", + "durationMs": 1471, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/dc22ab6c-d50f-4b39-939f-38b907489f5f", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/18d77cb7-4759-4d7f-b5a3-81a2695c01f3", + "blob:http://127.0.0.1:8084/dc22ab6c-d50f-4b39-939f-38b907489f5f" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/dc22ab6c-d50f-4b39-939f-38b907489f5f", + "blob:http://127.0.0.1:8084/18d77cb7-4759-4d7f-b5a3-81a2695c01f3" + ], + "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", + "durationMs": 1536, + "observations": [ + { + "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/b3023e13-bd88-4f98-bbf9-e0eab9a228f7", + "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/e978d3e9-6e7e-4eba-8445-82947af0b787", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/f1cae22f-6deb-4523-bf4d-39e25fd95fcf", + "blob:http://127.0.0.1:8084/b3023e13-bd88-4f98-bbf9-e0eab9a228f7", + "blob:http://127.0.0.1:8084/e978d3e9-6e7e-4eba-8445-82947af0b787" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/b3023e13-bd88-4f98-bbf9-e0eab9a228f7", + "blob:http://127.0.0.1:8084/e978d3e9-6e7e-4eba-8445-82947af0b787" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/f1cae22f-6deb-4523-bf4d-39e25fd95fcf", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/b3023e13-bd88-4f98-bbf9-e0eab9a228f7", + "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/e978d3e9-6e7e-4eba-8445-82947af0b787", + "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", + "durationMs": 1446, + "observations": [ + { + "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", + "durationMs": 1150, + "observations": [ + { + "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", + "durationMs": 1521, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/8147e44f-5701-4553-9add-3f1cb73b7215", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/a6b273ae-bf74-45a6-8c36-47d43ec724c4", + "blob:http://127.0.0.1:8084/8147e44f-5701-4553-9add-3f1cb73b7215" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/8147e44f-5701-4553-9add-3f1cb73b7215", + "blob:http://127.0.0.1:8084/a6b273ae-bf74-45a6-8c36-47d43ec724c4" + ], + "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", + "durationMs": 1521, + "observations": [ + { + "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/43120c61-d17f-4223-bc37-7be96aadc835", + "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/8a5c59ed-eb85-4128-8fcd-379d7c449782", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/4b2866a6-ef93-4090-8c12-a8a5b28643d5", + "blob:http://127.0.0.1:8084/43120c61-d17f-4223-bc37-7be96aadc835", + "blob:http://127.0.0.1:8084/8a5c59ed-eb85-4128-8fcd-379d7c449782" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/43120c61-d17f-4223-bc37-7be96aadc835", + "blob:http://127.0.0.1:8084/8a5c59ed-eb85-4128-8fcd-379d7c449782" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/4b2866a6-ef93-4090-8c12-a8a5b28643d5", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/43120c61-d17f-4223-bc37-7be96aadc835", + "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/8a5c59ed-eb85-4128-8fcd-379d7c449782", + "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", + "durationMs": 1345, + "observations": [ + { + "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", + "durationMs": 1251, + "observations": [ + { + "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": "published: shared setting mount preserves the surviving instance and later user changes", + "browser": "chromium", + "durationMs": 298, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "chromium", + "durationMs": 327, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "chromium", + "durationMs": 372, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "pending", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy clears active send timer and leaves another mounted instance usable", + "browser": "chromium", + "durationMs": 444, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "chromium", + "durationMs": 393, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "reject", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "chromium", + "durationMs": 477, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "chromium", + "durationMs": 458, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "chromium", + "durationMs": 544, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "heatmap", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: shared setting mount preserves the surviving instance and later user changes", + "browser": "chromium", + "durationMs": 306, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "chromium", + "durationMs": 340, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "chromium", + "durationMs": 389, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "pending", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy clears active send timer and leaves another mounted instance usable", + "browser": "chromium", + "durationMs": 463, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "chromium", + "durationMs": 424, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "reject", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "chromium", + "durationMs": 472, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "chromium", + "durationMs": 511, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "chromium", + "durationMs": 541, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "heatmap", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: shared setting mount preserves the surviving instance and later user changes", + "browser": "firefox", + "durationMs": 490, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "firefox", + "durationMs": 604, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "firefox", + "durationMs": 775, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "pending", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy clears active send timer and leaves another mounted instance usable", + "browser": "firefox", + "durationMs": 955, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "firefox", + "durationMs": 598, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "reject", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "firefox", + "durationMs": 1373, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "firefox", + "durationMs": 543, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "firefox", + "durationMs": 596, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "heatmap", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: shared setting mount preserves the surviving instance and later user changes", + "browser": "firefox", + "durationMs": 508, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "firefox", + "durationMs": 520, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "firefox", + "durationMs": 639, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "pending", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy clears active send timer and leaves another mounted instance usable", + "browser": "firefox", + "durationMs": 857, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "firefox", + "durationMs": 679, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "reject", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "firefox", + "durationMs": 1227, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "firefox", + "durationMs": 727, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "firefox", + "durationMs": 605, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "heatmap", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: shared setting mount preserves the surviving instance and later user changes", + "browser": "webkit", + "durationMs": 1624, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "webkit", + "durationMs": 1373, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "webkit", + "durationMs": 2066, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "pending", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy clears active send timer and leaves another mounted instance usable", + "browser": "webkit", + "durationMs": 2559, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "webkit", + "durationMs": 2315, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "reject", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "webkit", + "durationMs": 2033, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "webkit", + "durationMs": 1968, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "webkit", + "durationMs": 2130, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "heatmap", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: shared setting mount preserves the surviving instance and later user changes", + "browser": "webkit", + "durationMs": 1415, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "webkit", + "durationMs": 1439, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "webkit", + "durationMs": 1857, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "pending", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy clears active send timer and leaves another mounted instance usable", + "browser": "webkit", + "durationMs": 2678, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "webkit", + "durationMs": 2245, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "reject", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "webkit", + "durationMs": 2033, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "webkit", + "durationMs": 1642, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "webkit", + "durationMs": 2118, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "heatmap", + "artifact": "source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "chromium", + "durationMs": 2347, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "chromium", + "durationMs": 449, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.124351 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/a4da3f5a-a870-4131-96a6-ba330d932d79", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "chromium", + "durationMs": 476, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.124243 + }, + { + "index": 0, + "event": "stop", + "time": 0.124243 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/45bb7a7b-f2a2-43f9-afba-1e9819c0d04a", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2641, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2502, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2406, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2310, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "chromium", + "durationMs": 1359, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "chromium", + "durationMs": 1443, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "chromium", + "durationMs": 2353, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 602.791015625, + "next": 602.791015625, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 223.9000000357628, + "before": 0.021692, + "after": 0.021692, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 241.69999998807907, + "before": 0.039631, + "after": 0.039631, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 257.4000000357628, + "before": 0.055246, + "after": 0.055246, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 274.30000001192093, + "before": 0.072143, + "after": 0.072143, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 290.69999998807907, + "before": 0.088697, + "after": 0.088697, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 306.9000000357628, + "before": 0.104823, + "after": 0.104823, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 324.10000002384186, + "before": 0.122029, + "after": 0.122029, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 339.9000000357628, + "before": 0.137853, + "after": 0.137853, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 357.19999998807907, + "before": 0.15508, + "after": 0.15508, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 373.9000000357628, + "before": 0.171815, + "after": 0.171815, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 390.80000001192093, + "before": 0.188798, + "after": 0.188798, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 408.30000001192093, + "before": 0.206217, + "after": 0.206217, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 423.5, + "before": 0.221392, + "after": 0.221392, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 440.10000002384186, + "before": 0.237978, + "after": 0.237978, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 457.19999998807907, + "before": 0.255039, + "after": 0.255039, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 473.9000000357628, + "before": 0.271822, + "after": 0.271822, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 490.60000002384186, + "before": 0.288558, + "after": 0.288558, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 507.5, + "before": 0.305459, + "after": 0.305459, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 524.1000000238419, + "before": 0.322004, + "after": 0.322004, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 541.1000000238419, + "before": 0.338978, + "after": 0.338978, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 558.1999999880791, + "before": 0.356166, + "after": 0.356166, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 573.9000000357628, + "before": 0.371822, + "after": 0.371822, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 591.4000000357628, + "before": 0.38929, + "after": 0.38929, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 606.4000000357628, + "before": 0.404382, + "after": 0.404382, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 624.4000000357628, + "before": 0.422287, + "after": 0.422287, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 640.8000000119209, + "before": 0.438739, + "after": 0.438739, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 657.4000000357628, + "before": 0.455382, + "after": 0.455382, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 673.6000000238419, + "before": 0.471561, + "after": 0.471561, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 689.9000000357628, + "before": 0.487841, + "after": 0.487841, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 706.6999999880791, + "before": 0.504642, + "after": 0.504642, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 724.1000000238419, + "before": 0.522021, + "after": 0.522021, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 740.5, + "before": 0.538502, + "after": 0.538502, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 756.6000000238419, + "before": 0.554531, + "after": 0.554531, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 773.9000000357628, + "before": 0.571821, + "after": 0.571821, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 790.6000000238419, + "before": 0.588509, + "after": 0.588509, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 806.5, + "before": 0.60442, + "after": 0.60442, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 824.4000000357628, + "before": 0.622369, + "after": 0.622369, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 841.4000000357628, + "before": 0.639378, + "after": 0.639378, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 856.8000000119209, + "before": 0.654673, + "after": 0.654673, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 874.5, + "before": 0.672475, + "after": 0.672475, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 890.1000000238419, + "before": 0.688001, + "after": 0.688001, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 907.6999999880791, + "before": 0.705563, + "after": 0.705563, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 924.6000000238419, + "before": 0.722561, + "after": 0.722561, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 940.6000000238419, + "before": 0.738548, + "after": 0.738548, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 957.4000000357628, + "before": 0.755273, + "after": 0.755273, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 973.5, + "before": 0.771382, + "after": 0.771382, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 990, + "before": 0.787952, + "after": 0.787952, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1006.9000000357628, + "before": 0.804894, + "after": 0.804894, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1024.4000000357628, + "before": 0.822383, + "after": 0.822383, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1040.4000000357628, + "before": 0.838344, + "after": 0.838344, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1057, + "before": 0.854933, + "after": 0.854933, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1074.1000000238419, + "before": 0.872016, + "after": 0.872016, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1091.300000011921, + "before": 0.889314, + "after": 0.889314, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1106.5, + "before": 0.904391, + "after": 0.904391, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1123.5, + "before": 0.921456, + "after": 0.921456, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1357.6000000238419, + "before": 2.05937, + "after": 2.05937, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1373.699999988079, + "before": 2.091658, + "after": 2.091658, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1390.4000000357628, + "before": 2.12497, + "after": 2.12497, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1407.6000000238419, + "before": 2.159264, + "after": 2.159264, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1424.199999988079, + "before": 2.19274, + "after": 2.19274, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1440.800000011921, + "before": 2.22576, + "after": 2.22576, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1457, + "before": 2.25811, + "after": 2.25811, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1474.300000011921, + "before": 2.292782, + "after": 2.292782, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1490.800000011921, + "before": 2.325772, + "after": 2.325772, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1508.4000000357628, + "before": 2.361002, + "after": 2.361002, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1524.5, + "before": 2.393174, + "after": 2.393174, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1539.9000000357628, + "before": 2.424078, + "after": 2.424078, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1558, + "before": 2.460196, + "after": 2.460196, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1573.9000000357628, + "before": 2.491894, + "after": 2.491894, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1590, + "before": 2.524142, + "after": 2.524142, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1607.5, + "before": 2.55923, + "after": 2.55923, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1624.5, + "before": 2.593212, + "after": 2.593212, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1640.5, + "before": 2.62521, + "after": 2.62521, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1657.199999988079, + "before": 2.658474, + "after": 2.658474, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1673.1000000238419, + "before": 2.690416, + "after": 2.690416, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1691.300000011921, + "before": 2.726734, + "after": 2.726734, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1708.300000011921, + "before": 2.760734, + "after": 2.760734, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1724.5, + "before": 2.793064, + "after": 2.793064, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1740.699999988079, + "before": 2.825582, + "after": 2.825582, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1757.4000000357628, + "before": 2.858936, + "after": 2.858936, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1773.199999988079, + "before": 2.890664, + "after": 2.890664, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1791.1000000238419, + "before": 2.926292, + "after": 2.926292, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1807.1000000238419, + "before": 2.9583, + "after": 2.9583, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1823.6000000238419, + "before": 2.991432, + "after": 2.991432, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1841.199999988079, + "before": 3.026556, + "after": 3.026556, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1857.5, + "before": 3.059104, + "after": 3.059104, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1873.6000000238419, + "before": 3.091374, + "after": 3.091374, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1890.9000000357628, + "before": 3.125938, + "after": 3.125938, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1907.699999988079, + "before": 3.159566, + "after": 3.159566, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1923.6000000238419, + "before": 3.191372, + "after": 3.191372, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "wait" + } + ] + }, + { + "wall": 1940.9000000357628, + "before": 3.225928, + "after": 3.225928, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + }, + { + "wall": 1957.4000000357628, + "before": 3.258872, + "after": 3.258872, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + }, + { + "wall": 1973.6000000238419, + "before": 3.29145, + "after": 3.29145, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + }, + { + "wall": 1990.5, + "before": 3.325072, + "after": 3.325072, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + }, + { + "wall": 2007.1000000238419, + "before": 3.358516, + "after": 3.358516, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + }, + { + "wall": 2023.4000000357628, + "before": 3.390952, + "after": 3.390952, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + }, + { + "wall": 2041.300000011921, + "before": 3.42659, + "after": 3.42659, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + }, + { + "wall": 2056.800000011921, + "before": 3.457752, + "after": 3.457752, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + }, + { + "wall": 2073.400000035763, + "before": 3.49095, + "after": 3.49095, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + }, + { + "wall": 2090.400000035763, + "before": 3.525042, + "after": 3.525042, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + }, + { + "wall": 2107.199999988079, + "before": 3.558702, + "after": 3.558702, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + }, + { + "wall": 2123.5, + "before": 3.591142, + "after": 3.591142, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.26718, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "chromium", + "durationMs": 4044, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "chromium", + "durationMs": 2297, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "chromium", + "durationMs": 444, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.120575 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/8c851538-f223-49ce-9a1d-0c3f6f6e4981", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "chromium", + "durationMs": 510, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.120076 + }, + { + "index": 0, + "event": "stop", + "time": 0.120076 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/cf03d613-9f4c-40bc-8276-2005aef0e622", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2626, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2521, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2474, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2450, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "chromium", + "durationMs": 1377, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "chromium", + "durationMs": 1495, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "chromium", + "durationMs": 2279, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 602.7923583984375, + "next": 602.7923583984375, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 218.89999997615814, + "before": 0.02796, + "after": 0.02796, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 235.30000001192093, + "before": 0.044316, + "after": 0.044316, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 251.69999998807907, + "before": 0.060805, + "after": 0.060805, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 267.89999997615814, + "before": 0.077026, + "after": 0.077026, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 283.80000001192093, + "before": 0.092935, + "after": 0.092935, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 301.30000001192093, + "before": 0.110359, + "after": 0.110359, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 317.30000001192093, + "before": 0.126403, + "after": 0.126403, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 334.5, + "before": 0.143688, + "after": 0.143688, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 351.30000001192093, + "before": 0.160394, + "after": 0.160394, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 367.19999998807907, + "before": 0.176336, + "after": 0.176336, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 385.10000002384186, + "before": 0.194238, + "after": 0.194238, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 402.10000002384186, + "before": 0.211243, + "after": 0.211243, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 417.5, + "before": 0.226532, + "after": 0.226532, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 434.19999998807907, + "before": 0.243344, + "after": 0.243344, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 450.60000002384186, + "before": 0.259779, + "after": 0.259779, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 467.5, + "before": 0.276565, + "after": 0.276565, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 485.30000001192093, + "before": 0.294439, + "after": 0.294439, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 501.39999997615814, + "before": 0.310427, + "after": 0.310427, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 517.8999999761581, + "before": 0.327069, + "after": 0.327069, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 534.1000000238419, + "before": 0.343218, + "after": 0.343218, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 550.6000000238419, + "before": 0.359672, + "after": 0.359672, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 567.6000000238419, + "before": 0.376694, + "after": 0.376694, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 585.1000000238419, + "before": 0.394205, + "after": 0.394205, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 601.1000000238419, + "before": 0.410157, + "after": 0.410157, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 618, + "before": 0.426972, + "after": 0.426972, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 634.8000000119209, + "before": 0.443898, + "after": 0.443898, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 652.1000000238419, + "before": 0.461179, + "after": 0.461179, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 668.1000000238419, + "before": 0.477222, + "after": 0.477222, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 685.1000000238419, + "before": 0.49423, + "after": 0.49423, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 701.3999999761581, + "before": 0.510512, + "after": 0.510512, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 717.6000000238419, + "before": 0.526692, + "after": 0.526692, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 734.8000000119209, + "before": 0.543922, + "after": 0.543922, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 751.8000000119209, + "before": 0.560885, + "after": 0.560885, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 768.8999999761581, + "before": 0.578016, + "after": 0.578016, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 785.3999999761581, + "before": 0.594522, + "after": 0.594522, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 800.6000000238419, + "before": 0.609774, + "after": 0.609774, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 818.1999999880791, + "before": 0.627307, + "after": 0.627307, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 834.3000000119209, + "before": 0.643383, + "after": 0.643383, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 851.3999999761581, + "before": 0.660502, + "after": 0.660502, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 867.5, + "before": 0.676582, + "after": 0.676582, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 883.8000000119209, + "before": 0.692936, + "after": 0.692936, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 901.3999999761581, + "before": 0.710458, + "after": 0.710458, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 918.1000000238419, + "before": 0.727212, + "after": 0.727212, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 934.3999999761581, + "before": 0.743553, + "after": 0.743553, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 952.1999999880791, + "before": 0.761246, + "after": 0.761246, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 968.1999999880791, + "before": 0.777346, + "after": 0.777346, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 985, + "before": 0.794089, + "after": 0.794089, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1001.6000000238419, + "before": 0.810683, + "after": 0.810683, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1017.6999999880791, + "before": 0.826807, + "after": 0.826807, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1035.1000000238419, + "before": 0.844138, + "after": 0.844138, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1051.5, + "before": 0.860605, + "after": 0.860605, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1069.1000000238419, + "before": 0.87822, + "after": 0.87822, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1085.699999988079, + "before": 0.894761, + "after": 0.894761, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1100.699999988079, + "before": 0.909753, + "after": 0.909753, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1117.699999988079, + "before": 0.926784, + "after": 0.926784, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1351.8999999761581, + "before": 2.059322, + "after": 2.059322, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1367.8999999761581, + "before": 2.091146, + "after": 2.091146, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1384.300000011921, + "before": 2.124118, + "after": 2.124118, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1401.8999999761581, + "before": 2.159244, + "after": 2.159244, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1418.1000000238419, + "before": 2.191774, + "after": 2.191774, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1434.300000011921, + "before": 2.22397, + "after": 2.22397, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1450.800000011921, + "before": 2.257036, + "after": 2.257036, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1468.3999999761581, + "before": 2.292188, + "after": 2.292188, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1484.300000011921, + "before": 2.324058, + "after": 2.324058, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1501.3999999761581, + "before": 2.358304, + "after": 2.358304, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1518.1000000238419, + "before": 2.391556, + "after": 2.391556, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1534.3999999761581, + "before": 2.4242, + "after": 2.4242, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1551.300000011921, + "before": 2.458112, + "after": 2.458112, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1567.8999999761581, + "before": 2.491332, + "after": 2.491332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1584.199999988079, + "before": 2.523668, + "after": 2.523668, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1601.8999999761581, + "before": 2.559308, + "after": 2.559308, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1617.5, + "before": 2.590462, + "after": 2.590462, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1634.1000000238419, + "before": 2.623636, + "after": 2.623636, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1650.800000011921, + "before": 2.65718, + "after": 2.65718, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1668, + "before": 2.691388, + "after": 2.691388, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1684.1000000238419, + "before": 2.723658, + "after": 2.723658, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1702.199999988079, + "before": 2.759746, + "after": 2.759746, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1717.199999988079, + "before": 2.789852, + "after": 2.789852, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1734.699999988079, + "before": 2.82483, + "after": 2.82483, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1752, + "before": 2.859386, + "after": 2.859386, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1768, + "before": 2.891578, + "after": 2.891578, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1785.199999988079, + "before": 2.92586, + "after": 2.92586, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1800.8999999761581, + "before": 2.957106, + "after": 2.957106, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1817.6000000238419, + "before": 2.99058, + "after": 2.99058, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1834.8999999761581, + "before": 3.02507, + "after": 3.02507, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1851.699999988079, + "before": 3.058874, + "after": 3.058874, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1867.800000011921, + "before": 3.091132, + "after": 3.091132, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1885, + "before": 3.125406, + "after": 3.125406, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1900.800000011921, + "before": 3.157032, + "after": 3.157032, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1918.5, + "before": 3.192336, + "after": 3.192336, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "wait" + } + ] + }, + { + "wall": 1934.300000011921, + "before": 3.224056, + "after": 3.224056, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "emit" + } + ] + }, + { + "wall": 1951.300000011921, + "before": 3.258092, + "after": 3.258092, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "emit" + } + ] + }, + { + "wall": 1967.699999988079, + "before": 3.290726, + "after": 3.290726, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "emit" + } + ] + }, + { + "wall": 1985.300000011921, + "before": 3.325902, + "after": 3.325902, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "emit" + } + ] + }, + { + "wall": 2001.300000011921, + "before": 3.358142, + "after": 3.358142, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "emit" + } + ] + }, + { + "wall": 2017, + "before": 3.389598, + "after": 3.389598, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "emit" + } + ] + }, + { + "wall": 2034.6000000238419, + "before": 3.424698, + "after": 3.424698, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "emit" + } + ] + }, + { + "wall": 2051.199999988079, + "before": 3.457782, + "after": 3.457782, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "emit" + } + ] + }, + { + "wall": 2067.399999976158, + "before": 3.490304, + "after": 3.490304, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "emit" + } + ] + }, + { + "wall": 2084.199999988079, + "before": 3.523726, + "after": 3.523726, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "emit" + } + ] + }, + { + "wall": 2100.399999976158, + "before": 3.556328, + "after": 3.556328, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.268076, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "chromium", + "durationMs": 4069, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "firefox", + "durationMs": 2472, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "firefox", + "durationMs": 722, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.129164 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/6169f19a-659c-4dd0-ae42-f2047a0be120", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "firefox", + "durationMs": 544, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.00569 + }, + { + "index": 0, + "event": "stop", + "time": 0.00569 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/fc085cfb-251a-4992-9451-d3aba1fa71f1", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2893, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2687, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2679, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2724, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "firefox", + "durationMs": 1974, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "firefox", + "durationMs": 1567, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "firefox", + "durationMs": 2498, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 601.3333129882812, + "next": 601.3333129882812, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 252, + "before": 0.005665, + "after": 0.005665, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 269, + "before": 0.005665, + "after": 0.005665, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 285, + "before": 0.046412, + "after": 0.046412, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 302, + "before": 0.046412, + "after": 0.046412, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 318, + "before": 0.087036, + "after": 0.087036, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 335, + "before": 0.087036, + "after": 0.087036, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 351, + "before": 0.087036, + "after": 0.087036, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 369, + "before": 0.127394, + "after": 0.127394, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 385, + "before": 0.127394, + "after": 0.127394, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 402, + "before": 0.167948, + "after": 0.167948, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 418, + "before": 0.167948, + "after": 0.167948, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 435, + "before": 0.167948, + "after": 0.167948, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 451, + "before": 0.208332, + "after": 0.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 468, + "before": 0.208332, + "after": 0.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 485, + "before": 0.249999, + "after": 0.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 501, + "before": 0.249999, + "after": 0.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 518, + "before": 0.249999, + "after": 0.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 535, + "before": 0.291666, + "after": 0.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 552, + "before": 0.291666, + "after": 0.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 568, + "before": 0.333332, + "after": 0.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 585, + "before": 0.333332, + "after": 0.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 602, + "before": 0.333332, + "after": 0.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 618, + "before": 0.374071, + "after": 0.374071, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 635, + "before": 0.374071, + "after": 0.374071, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 652, + "before": 0.416666, + "after": 0.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 669, + "before": 0.416666, + "after": 0.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 686, + "before": 0.416666, + "after": 0.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 702, + "before": 0.458332, + "after": 0.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 719, + "before": 0.458332, + "after": 0.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 736, + "before": 0.499999, + "after": 0.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 752, + "before": 0.499999, + "after": 0.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 769, + "before": 0.499999, + "after": 0.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 786, + "before": 0.541666, + "after": 0.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 802, + "before": 0.541666, + "after": 0.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 818, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 835, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 852, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 869, + "before": 0.624999, + "after": 0.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 885, + "before": 0.624999, + "after": 0.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 902, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 918, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 935, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 952, + "before": 0.708332, + "after": 0.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 968, + "before": 0.708332, + "after": 0.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 985, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1002, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1018, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1035, + "before": 0.791666, + "after": 0.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1052, + "before": 0.791666, + "after": 0.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1069, + "before": 0.833332, + "after": 0.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1085, + "before": 0.833332, + "after": 0.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1102, + "before": 0.833332, + "after": 0.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1119, + "before": 0.874999, + "after": 0.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1140, + "before": 0.874999, + "after": 0.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1153, + "before": 0.916147, + "after": 0.916147, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1168, + "before": 0.916147, + "after": 0.916147, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1186, + "before": 0.916147, + "after": 0.916147, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1204, + "before": 0.958332, + "after": 0.958332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1369, + "before": 2.00005, + "after": 2.00005, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1385, + "before": 2.041666, + "after": 2.041666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1402, + "before": 2.083332, + "after": 2.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1419, + "before": 2.124999, + "after": 2.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1436, + "before": 2.124999, + "after": 2.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1452, + "before": 2.166666, + "after": 2.166666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1469, + "before": 2.208332, + "after": 2.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1485, + "before": 2.249999, + "after": 2.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1502, + "before": 2.249999, + "after": 2.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1519, + "before": 2.291666, + "after": 2.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1537, + "before": 2.333332, + "after": 2.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1552, + "before": 2.374999, + "after": 2.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1570, + "before": 2.416666, + "after": 2.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1586, + "before": 2.416666, + "after": 2.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1602, + "before": 2.458332, + "after": 2.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1619, + "before": 2.499999, + "after": 2.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1637, + "before": 2.541666, + "after": 2.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1653, + "before": 2.583332, + "after": 2.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1670, + "before": 2.583332, + "after": 2.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1686, + "before": 2.624999, + "after": 2.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1702, + "before": 2.666666, + "after": 2.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1720, + "before": 2.708332, + "after": 2.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1736, + "before": 2.749999, + "after": 2.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1754, + "before": 2.749999, + "after": 2.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1770, + "before": 2.791666, + "after": 2.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1786, + "before": 2.833332, + "after": 2.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1803, + "before": 2.874999, + "after": 2.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1819, + "before": 2.916666, + "after": 2.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1836, + "before": 2.958332, + "after": 2.958332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1853, + "before": 2.958332, + "after": 2.958332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1869, + "before": 2.999999, + "after": 2.999999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1886, + "before": 3.041666, + "after": 3.041666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1903, + "before": 3.083332, + "after": 3.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1919, + "before": 3.124999, + "after": 3.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1936, + "before": 3.124999, + "after": 3.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1953, + "before": 3.166666, + "after": 3.166666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1969, + "before": 3.208332, + "after": 3.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1986, + "before": 3.249999, + "after": 3.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2003, + "before": 3.291666, + "after": 3.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2019, + "before": 3.291666, + "after": 3.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2037, + "before": 3.333332, + "after": 3.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2053, + "before": 3.374999, + "after": 3.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2070, + "before": 3.416666, + "after": 3.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2087, + "before": 3.458332, + "after": 3.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2103, + "before": 3.458332, + "after": 3.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2120, + "before": 3.49938, + "after": 3.49938, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2137, + "before": 3.540511, + "after": 3.540511, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2153, + "before": 3.581643, + "after": 3.581643, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2169, + "before": 3.622206, + "after": 3.622206, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2186, + "before": 3.622206, + "after": 3.622206, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2204, + "before": 3.666666, + "after": 3.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "firefox", + "durationMs": 4678, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "firefox", + "durationMs": 2460, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "firefox", + "durationMs": 640, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.127766 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/789193e1-52e9-487c-93b3-c90cb23f8e73", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "firefox", + "durationMs": 872, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.088187 + }, + { + "index": 0, + "event": "stop", + "time": 0.088187 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/e18cc4e8-13a4-4cf4-9ad8-3131112db946", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2790, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2653, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2821, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2534, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "firefox", + "durationMs": 2043, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "firefox", + "durationMs": 1559, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "firefox", + "durationMs": 2512, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 604.25, + "next": 604.25, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 276, + "before": 0.005669, + "after": 0.005669, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 293, + "before": 0.005669, + "after": 0.005669, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 309, + "before": 0.005669, + "after": 0.005669, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 327, + "before": 0.04641, + "after": 0.04641, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 344, + "before": 0.04641, + "after": 0.04641, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 360, + "before": 0.086998, + "after": 0.086998, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 377, + "before": 0.086998, + "after": 0.086998, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 393, + "before": 0.127201, + "after": 0.127201, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 409, + "before": 0.127201, + "after": 0.127201, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 426, + "before": 0.127201, + "after": 0.127201, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 443, + "before": 0.167549, + "after": 0.167549, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 459, + "before": 0.167549, + "after": 0.167549, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 476, + "before": 0.208332, + "after": 0.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 494, + "before": 0.208332, + "after": 0.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 509, + "before": 0.208332, + "after": 0.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 526, + "before": 0.249999, + "after": 0.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 543, + "before": 0.249999, + "after": 0.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 560, + "before": 0.291666, + "after": 0.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 576, + "before": 0.291666, + "after": 0.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 593, + "before": 0.291666, + "after": 0.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 610, + "before": 0.333332, + "after": 0.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 626, + "before": 0.333332, + "after": 0.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 644, + "before": 0.374999, + "after": 0.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 660, + "before": 0.374999, + "after": 0.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 676, + "before": 0.374999, + "after": 0.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 693, + "before": 0.416666, + "after": 0.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 710, + "before": 0.416666, + "after": 0.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 726, + "before": 0.458332, + "after": 0.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 744, + "before": 0.458332, + "after": 0.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 759, + "before": 0.458332, + "after": 0.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 776, + "before": 0.499999, + "after": 0.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 793, + "before": 0.499999, + "after": 0.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 810, + "before": 0.541666, + "after": 0.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 827, + "before": 0.541666, + "after": 0.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 844, + "before": 0.541666, + "after": 0.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 860, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 876, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 894, + "before": 0.624999, + "after": 0.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 910, + "before": 0.624999, + "after": 0.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 927, + "before": 0.624999, + "after": 0.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 944, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 960, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 976, + "before": 0.708332, + "after": 0.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 994, + "before": 0.708332, + "after": 0.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1011, + "before": 0.708332, + "after": 0.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1027, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1043, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1060, + "before": 0.791666, + "after": 0.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1077, + "before": 0.791666, + "after": 0.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1094, + "before": 0.791666, + "after": 0.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1111, + "before": 0.833332, + "after": 0.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1127, + "before": 0.833332, + "after": 0.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1144, + "before": 0.874999, + "after": 0.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1164, + "before": 0.874999, + "after": 0.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1177, + "before": 0.874999, + "after": 0.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1194, + "before": 0.916666, + "after": 0.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1212, + "before": 0.916666, + "after": 0.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1377, + "before": 2.000044, + "after": 2.000044, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1394, + "before": 2.041666, + "after": 2.041666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1410, + "before": 2.083332, + "after": 2.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1427, + "before": 2.083332, + "after": 2.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1444, + "before": 2.124999, + "after": 2.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1460, + "before": 2.166666, + "after": 2.166666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1477, + "before": 2.208332, + "after": 2.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1493, + "before": 2.249999, + "after": 2.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1511, + "before": 2.249999, + "after": 2.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1527, + "before": 2.291666, + "after": 2.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1543, + "before": 2.333332, + "after": 2.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1561, + "before": 2.374999, + "after": 2.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1577, + "before": 2.416666, + "after": 2.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1594, + "before": 2.416666, + "after": 2.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1611, + "before": 2.458332, + "after": 2.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1627, + "before": 2.499999, + "after": 2.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1644, + "before": 2.541666, + "after": 2.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1661, + "before": 2.583332, + "after": 2.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1677, + "before": 2.583332, + "after": 2.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1694, + "before": 2.624999, + "after": 2.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1711, + "before": 2.666666, + "after": 2.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1728, + "before": 2.708332, + "after": 2.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1745, + "before": 2.749999, + "after": 2.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1760, + "before": 2.749999, + "after": 2.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1778, + "before": 2.791666, + "after": 2.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1794, + "before": 2.833332, + "after": 2.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1810, + "before": 2.874999, + "after": 2.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1827, + "before": 2.916666, + "after": 2.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1844, + "before": 2.916666, + "after": 2.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1861, + "before": 2.958332, + "after": 2.958332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1877, + "before": 2.999999, + "after": 2.999999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1894, + "before": 3.041666, + "after": 3.041666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1911, + "before": 3.083332, + "after": 3.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1927, + "before": 3.083332, + "after": 3.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1944, + "before": 3.124999, + "after": 3.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1961, + "before": 3.166666, + "after": 3.166666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1978, + "before": 3.208332, + "after": 3.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 1995, + "before": 3.249999, + "after": 3.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2010, + "before": 3.249999, + "after": 3.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2027, + "before": 3.291666, + "after": 3.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2045, + "before": 3.333332, + "after": 3.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2061, + "before": 3.374333, + "after": 3.374333, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2078, + "before": 3.414857, + "after": 3.414857, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2094, + "before": 3.456154, + "after": 3.456154, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2111, + "before": 3.456154, + "after": 3.456154, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2127, + "before": 3.497422, + "after": 3.497422, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2144, + "before": 3.538161, + "after": 3.538161, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2163, + "before": 3.578425, + "after": 3.578425, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2178, + "before": 3.619302, + "after": 3.619302, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2195, + "before": 3.619302, + "after": 3.619302, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "firefox", + "durationMs": 4506, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "webkit", + "durationMs": 3855, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "webkit", + "durationMs": 1875, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.1509683 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/8b6b2da3-6b1d-4ae6-80d3-bc034d0d3379", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "webkit", + "durationMs": 1819, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.0621958 + }, + { + "index": 0, + "event": "stop", + "time": 0.0622303 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/c282b5e8-6625-4822-aeae-e08bf0f62ebf", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4682, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4090, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4288, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 3588, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "webkit", + "durationMs": 3048, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "webkit", + "durationMs": 3111, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "webkit", + "durationMs": 4123, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 620.6124877929688, + "next": 620.6124877929688, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 1022, + "before": 0.037198, + "after": 0.0372876, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1067, + "before": 0.0825133, + "after": 0.082529, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1128, + "before": 0.1440193, + "after": 0.1440325, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1159, + "before": 0.1747188, + "after": 0.1747298, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1203, + "before": 0.2189016, + "after": 0.2189157, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1248, + "before": 0.2639751, + "after": 0.2639833, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1280, + "before": 0.2959372, + "after": 0.2959794, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1311, + "before": 0.3264783, + "after": 0.3265161, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1357, + "before": 0.3726786, + "after": 0.3726881, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1403, + "before": 0.4184727, + "after": 0.4184974, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1436, + "before": 0.4516336, + "after": 0.4516421, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1481, + "before": 0.4964813, + "after": 0.496491, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1528, + "before": 0.54333, + "after": 0.5433423, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1589, + "before": 0.6045261, + "after": 0.6045355, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1620, + "before": 0.6359789, + "after": 0.6359897, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1665, + "before": 0.6812487, + "after": 0.6812594, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1696, + "before": 0.7122088, + "after": 0.7122233, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1733, + "before": 0.7485995, + "after": 0.7486089, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1734, + "before": 0.7502078, + "after": 0.7502144, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2251, + "before": 2.0761874, + "after": 2.0762162, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "wait" + } + ] + }, + { + "wall": 2297, + "before": 2.167832, + "after": 2.167855, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "wait" + } + ] + }, + { + "wall": 2328, + "before": 2.2296416, + "after": 2.2296696, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "wait" + } + ] + }, + { + "wall": 2359, + "before": 2.2920008, + "after": 2.2920232, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "wait" + } + ] + }, + { + "wall": 2404, + "before": 2.3823982, + "after": 2.3824226, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "wait" + } + ] + }, + { + "wall": 2436, + "before": 2.4459554, + "after": 2.445995, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "wait" + } + ] + }, + { + "wall": 2468, + "before": 2.5096784, + "after": 2.5096964, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + } + ] + }, + { + "wall": 2514, + "before": 2.602687, + "after": 2.6027166, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + } + ] + }, + { + "wall": 2544, + "before": 2.662037, + "after": 2.6620588, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + } + ] + }, + { + "wall": 2590, + "before": 2.753648, + "after": 2.7536698, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + } + ] + }, + { + "wall": 2636, + "before": 2.8456046, + "after": 2.845671, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + } + ] + }, + { + "wall": 2682, + "before": 2.939068, + "after": 2.9391632, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "wait" + } + ] + }, + { + "wall": 2745, + "before": 3.064804, + "after": 3.0648442, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "wait" + } + ] + }, + { + "wall": 2776, + "before": 3.1262278, + "after": 3.1262506, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "wait" + } + ] + }, + { + "wall": 2823, + "before": 3.219277, + "after": 3.219358, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "wait" + } + ] + }, + { + "wall": 2868, + "before": 3.311081, + "after": 3.3111262, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "wait" + } + ] + }, + { + "wall": 2916, + "before": 3.4052488, + "after": 3.4053134, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "wait" + } + ] + }, + { + "wall": 2992, + "before": 3.5587164, + "after": 3.5587392, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "emit" + } + ] + }, + { + "wall": 3052, + "before": 3.6789606, + "after": 3.678987, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "emit" + } + ] + }, + { + "wall": 3097, + "before": 3.7689188, + "after": 3.7689394, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "emit" + } + ] + }, + { + "wall": 3144, + "before": 3.8615482, + "after": 3.8615668, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "emit" + } + ] + }, + { + "wall": 3205, + "before": 3.9844942, + "after": 3.9845156, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "emit" + } + ] + }, + { + "wall": 3251, + "before": 4.0762118, + "after": 4.076237, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "emit" + } + ] + }, + { + "wall": 3299, + "before": 4.1713572, + "after": 4.1713852, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "emit" + } + ] + }, + { + "wall": 3360, + "before": 4.2941152, + "after": 4.294139, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "emit" + } + ] + }, + { + "wall": 3407, + "before": 4.3875544, + "after": 4.3875894, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "emit" + } + ] + }, + { + "wall": 3430, + "before": 4.4341858, + "after": 4.4342146, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "emit" + } + ] + }, + { + "wall": 3432, + "before": 4.43815, + "after": 4.4381792, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5013626, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4219202, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "webkit", + "durationMs": 6358, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "webkit", + "durationMs": 3674, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "webkit", + "durationMs": 1686, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.2025518 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/230a7331-f14b-4eb9-b646-032aee02ef78", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "webkit", + "durationMs": 2046, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.2032197 + }, + { + "index": 0, + "event": "stop", + "time": 0.203256 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/ade0a364-4dbb-46c3-bc35-91426c354ca8", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4556, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4083, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4278, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 3793, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "webkit", + "durationMs": 2406, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "webkit", + "durationMs": 3129, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "webkit", + "durationMs": 4048, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 626.2999877929688, + "next": 626.2999877929688, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 1062, + "before": 0.0335717, + "after": 0.0336291, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1106, + "before": 0.0776323, + "after": 0.0776485, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1136, + "before": 0.1077465, + "after": 0.1077615, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1182, + "before": 0.1542014, + "after": 0.1542094, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1227, + "before": 0.1993149, + "after": 0.1993281, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1273, + "before": 0.2449331, + "after": 0.2449434, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1336, + "before": 0.3076963, + "after": 0.3077176, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1366, + "before": 0.3380133, + "after": 0.3380252, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1412, + "before": 0.3838462, + "after": 0.3838585, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1443, + "before": 0.4145676, + "after": 0.414595, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1489, + "before": 0.4609453, + "after": 0.4609576, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1520, + "before": 0.4915473, + "after": 0.4915565, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1565, + "before": 0.5373316, + "after": 0.5373403, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1609, + "before": 0.5811755, + "after": 0.5811855, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1640, + "before": 0.6114036, + "after": 0.6114114, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1671, + "before": 0.6429715, + "after": 0.6429851, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1718, + "before": 0.6896802, + "after": 0.6896908, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1720, + "before": 0.6923133, + "after": 0.6923329, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1722, + "before": 0.6941142, + "after": 0.6941233, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2226, + "before": 2.0994524, + "after": 2.0994748, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "wait" + } + ] + }, + { + "wall": 2271, + "before": 2.1890682, + "after": 2.1890924, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "wait" + } + ] + }, + { + "wall": 2301, + "before": 2.249769, + "after": 2.2498968, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "wait" + } + ] + }, + { + "wall": 2347, + "before": 2.3411312, + "after": 2.3411476, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "wait" + } + ] + }, + { + "wall": 2379, + "before": 2.4058632, + "after": 2.4058964, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "wait" + } + ] + }, + { + "wall": 2426, + "before": 2.4984908, + "after": 2.4985164, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + } + ] + }, + { + "wall": 2472, + "before": 2.5918974, + "after": 2.5919246, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + } + ] + }, + { + "wall": 2549, + "before": 2.7451686, + "after": 2.7451906, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + } + ] + }, + { + "wall": 2580, + "before": 2.807848, + "after": 2.8078686, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + } + ] + }, + { + "wall": 2627, + "before": 2.9007358, + "after": 2.9007578, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + } + ] + }, + { + "wall": 2658, + "before": 2.9621974, + "after": 2.9622744, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "wait" + } + ] + }, + { + "wall": 2705, + "before": 3.0565498, + "after": 3.0566472, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "wait" + } + ] + }, + { + "wall": 2750, + "before": 3.1477914, + "after": 3.1478124, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "wait" + } + ] + }, + { + "wall": 2795, + "before": 3.2380178, + "after": 3.238039, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "wait" + } + ] + }, + { + "wall": 2859, + "before": 3.3645508, + "after": 3.3645826, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "wait" + } + ] + }, + { + "wall": 2891, + "before": 3.4289314, + "after": 3.4289584, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 2935, + "before": 3.5176272, + "after": 3.517699, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 2982, + "before": 3.610506, + "after": 3.6105288, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 3027, + "before": 3.7011814, + "after": 3.701204, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 3089, + "before": 3.825769, + "after": 3.8257924, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 3151, + "before": 3.9497526, + "after": 3.94977, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 3183, + "before": 4.0131808, + "after": 4.0132086, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 3229, + "before": 4.104923, + "after": 4.1049492, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 3276, + "before": 4.1987698, + "after": 4.198796, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 3308, + "before": 4.2637608, + "after": 4.2637916, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 3353, + "before": 4.3536944, + "after": 4.3537232, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 3390, + "before": 4.426387, + "after": 4.426411, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + }, + { + "wall": 3392, + "before": 4.4312084, + "after": 4.4312262, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5004447, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4356514, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "webkit", + "durationMs": 6621, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "in-memory source UMD", + "sha256": "1423f8ac9de24b15d908761df2e0720123a88a2b05a45628f720adfd01680663", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + } + ] + }, + { + "format": "main", + "tests": 138, + "workers": 2, + "file": "refactor/.cache/danmuku05-browser-main-resource-final/report.json", + "sha256": "8ecfa06baca57584ab2accff9e98c5d79c71bb8a5b519afdddbc6c91ba9e4501", + "run": { + "file": "refactor/.cache/danmuku05-browser-main-resource-final/run.log", + "sha256": "cafa2144d09e14682f9f5dcbc98faecb7dea4339edf080fef8a724aa621736a0" + }, + "specs": [ + { + "file": "refactor/.cache/danmuku05-browser-main-resource-final/danmuku-input.spec.js", + "sha256": "37a96982ea7bf2fb99df5e4d829ce1c3f512145092a440586c2d5ad7d0a2d6f3" + }, + { + "file": "refactor/.cache/danmuku05-browser-main-resource-final/danmuku-scheduler.spec.js", + "sha256": "ee67bc0f204f1f9090859ed9d1d0cfecc545129fff6278d2f17140b009390519" + }, + { + "file": "refactor/.cache/danmuku05-browser-main-resource-final/danmuku-resources.spec.js", + "sha256": "f0d09e962cb422a66bd644c52268aff8f288606ae9c93676fa979539743f9c6b" + } + ], + "archiveResultsRoot": "refactor/.cache/danmuku05-browser-main-resource-final/results", + "originalResultsRoot": "refactor/.cache/browser/results", + "versions": [ + "chromium: 153.0.8010.12", + "firefox: 155.0", + "webkit: 26.6" + ], + "cases": [ + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "chromium", + "durationMs": 640, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/1b1a32b0-69f0-4384-a9cd-50126ef64ce3", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/f9aeb74a-f6c2-48d5-8e77-6e130aeb7e8e", + "blob:http://127.0.0.1:8084/1b1a32b0-69f0-4384-a9cd-50126ef64ce3" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/1b1a32b0-69f0-4384-a9cd-50126ef64ce3", + "blob:http://127.0.0.1:8084/f9aeb74a-f6c2-48d5-8e77-6e130aeb7e8e" + ], + "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", + "durationMs": 293, + "observations": [ + { + "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/88f22f1c-1786-48ca-9bc8-52270c3c2d18", + "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/e58d1bed-578a-4045-aa19-71cec466fa21", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/9584ab6c-30f0-4681-bd58-d4f70c327bd5", + "blob:http://127.0.0.1:8084/88f22f1c-1786-48ca-9bc8-52270c3c2d18", + "blob:http://127.0.0.1:8084/e58d1bed-578a-4045-aa19-71cec466fa21" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/88f22f1c-1786-48ca-9bc8-52270c3c2d18", + "blob:http://127.0.0.1:8084/e58d1bed-578a-4045-aa19-71cec466fa21" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/9584ab6c-30f0-4681-bd58-d4f70c327bd5", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/88f22f1c-1786-48ca-9bc8-52270c3c2d18", + "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/e58d1bed-578a-4045-aa19-71cec466fa21", + "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", + "durationMs": 242, + "observations": [ + { + "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", + "durationMs": 245, + "observations": [ + { + "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", + "durationMs": 622, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/5805c35d-b4a4-42b6-a42d-c1cb9d18bc26", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/af3a7b6e-f9ff-49c7-875f-6f14d3869ca9", + "blob:http://127.0.0.1:8084/5805c35d-b4a4-42b6-a42d-c1cb9d18bc26" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/5805c35d-b4a4-42b6-a42d-c1cb9d18bc26", + "blob:http://127.0.0.1:8084/af3a7b6e-f9ff-49c7-875f-6f14d3869ca9" + ], + "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", + "durationMs": 305, + "observations": [ + { + "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/ff5b5bea-0021-4514-9601-8a8da527fb6c", + "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/c5f795af-631b-43fa-8d6d-eca24f336772", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/5d7044c7-4608-43a2-a79c-f7bc621d2f45", + "blob:http://127.0.0.1:8084/ff5b5bea-0021-4514-9601-8a8da527fb6c", + "blob:http://127.0.0.1:8084/c5f795af-631b-43fa-8d6d-eca24f336772" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/ff5b5bea-0021-4514-9601-8a8da527fb6c", + "blob:http://127.0.0.1:8084/c5f795af-631b-43fa-8d6d-eca24f336772" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/5d7044c7-4608-43a2-a79c-f7bc621d2f45", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/ff5b5bea-0021-4514-9601-8a8da527fb6c", + "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/c5f795af-631b-43fa-8d6d-eca24f336772", + "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", + "durationMs": 259, + "observations": [ + { + "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", + "durationMs": 275, + "observations": [ + { + "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", + "durationMs": 1683, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/768f3b8e-1b6d-4daa-b1ab-05fdad9791fa", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/399b4a12-dc5a-44b6-b182-7916b85b8f78", + "blob:http://127.0.0.1:8084/768f3b8e-1b6d-4daa-b1ab-05fdad9791fa" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/768f3b8e-1b6d-4daa-b1ab-05fdad9791fa", + "blob:http://127.0.0.1:8084/399b4a12-dc5a-44b6-b182-7916b85b8f78" + ], + "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", + "durationMs": 805, + "observations": [ + { + "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/0e1a4b70-2e0c-49f7-9add-520bbd682207", + "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/25bf387c-656c-4888-a9f6-7a610cae8ed3", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/5cb46999-5e14-490f-b25e-c04c10e5d875", + "blob:http://127.0.0.1:8084/0e1a4b70-2e0c-49f7-9add-520bbd682207", + "blob:http://127.0.0.1:8084/25bf387c-656c-4888-a9f6-7a610cae8ed3" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/0e1a4b70-2e0c-49f7-9add-520bbd682207", + "blob:http://127.0.0.1:8084/25bf387c-656c-4888-a9f6-7a610cae8ed3" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/5cb46999-5e14-490f-b25e-c04c10e5d875", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/0e1a4b70-2e0c-49f7-9add-520bbd682207", + "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/25bf387c-656c-4888-a9f6-7a610cae8ed3", + "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", + "durationMs": 552, + "observations": [ + { + "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", + "durationMs": 408, + "observations": [ + { + "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", + "durationMs": 1752, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/d00430db-d3b1-4bcb-a041-7d0554b76ce8", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/65175f63-b7fc-415c-9f72-ba55338aac70", + "blob:http://127.0.0.1:8084/d00430db-d3b1-4bcb-a041-7d0554b76ce8" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/d00430db-d3b1-4bcb-a041-7d0554b76ce8", + "blob:http://127.0.0.1:8084/65175f63-b7fc-415c-9f72-ba55338aac70" + ], + "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", + "durationMs": 658, + "observations": [ + { + "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/b1995d9f-a22e-4e68-b273-dbb5b84651d6", + "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/d0d3cf5c-6819-4a33-9b21-0b3b41487e3e", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/5a8d289d-d317-4d3a-b997-d80366783341", + "blob:http://127.0.0.1:8084/b1995d9f-a22e-4e68-b273-dbb5b84651d6", + "blob:http://127.0.0.1:8084/d0d3cf5c-6819-4a33-9b21-0b3b41487e3e" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/b1995d9f-a22e-4e68-b273-dbb5b84651d6", + "blob:http://127.0.0.1:8084/d0d3cf5c-6819-4a33-9b21-0b3b41487e3e" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/5a8d289d-d317-4d3a-b997-d80366783341", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/b1995d9f-a22e-4e68-b273-dbb5b84651d6", + "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/d0d3cf5c-6819-4a33-9b21-0b3b41487e3e", + "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", + "durationMs": 467, + "observations": [ + { + "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", + "durationMs": 535, + "observations": [ + { + "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", + "durationMs": 1449, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/0f056ea9-2387-4279-a336-e15b4ced0da4", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/ed39801d-c547-40f8-8a4b-e22768b0cc01", + "blob:http://127.0.0.1:8084/0f056ea9-2387-4279-a336-e15b4ced0da4" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/0f056ea9-2387-4279-a336-e15b4ced0da4", + "blob:http://127.0.0.1:8084/ed39801d-c547-40f8-8a4b-e22768b0cc01" + ], + "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", + "durationMs": 1297, + "observations": [ + { + "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/43bdb6e6-5475-4ed7-acb3-ce64492b4a74", + "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/2f3f8431-76cf-4c0f-a21e-b0217c8031ba", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/f1fef377-c61b-4afc-bf78-bd842a8960c7", + "blob:http://127.0.0.1:8084/43bdb6e6-5475-4ed7-acb3-ce64492b4a74", + "blob:http://127.0.0.1:8084/2f3f8431-76cf-4c0f-a21e-b0217c8031ba" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/43bdb6e6-5475-4ed7-acb3-ce64492b4a74", + "blob:http://127.0.0.1:8084/2f3f8431-76cf-4c0f-a21e-b0217c8031ba" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/f1fef377-c61b-4afc-bf78-bd842a8960c7", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/43bdb6e6-5475-4ed7-acb3-ce64492b4a74", + "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/2f3f8431-76cf-4c0f-a21e-b0217c8031ba", + "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", + "durationMs": 1170, + "observations": [ + { + "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", + "durationMs": 1113, + "observations": [ + { + "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", + "durationMs": 1419, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/c17e1bfa-09c5-45c8-be20-2d5cdbfc381d", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/ae9b358f-40fc-431c-a805-2520205c1c1e", + "blob:http://127.0.0.1:8084/c17e1bfa-09c5-45c8-be20-2d5cdbfc381d" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/c17e1bfa-09c5-45c8-be20-2d5cdbfc381d", + "blob:http://127.0.0.1:8084/ae9b358f-40fc-431c-a805-2520205c1c1e" + ], + "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", + "durationMs": 1418, + "observations": [ + { + "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/0bc3ccd1-3947-467e-9ced-a1f6c9254638", + "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/d42f399a-e487-4471-914c-cca106eca47c", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/b27d493a-4642-4f5d-9f11-7ad48a48b218", + "blob:http://127.0.0.1:8084/0bc3ccd1-3947-467e-9ced-a1f6c9254638", + "blob:http://127.0.0.1:8084/d42f399a-e487-4471-914c-cca106eca47c" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/0bc3ccd1-3947-467e-9ced-a1f6c9254638", + "blob:http://127.0.0.1:8084/d42f399a-e487-4471-914c-cca106eca47c" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/b27d493a-4642-4f5d-9f11-7ad48a48b218", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/0bc3ccd1-3947-467e-9ced-a1f6c9254638", + "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/d42f399a-e487-4471-914c-cca106eca47c", + "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", + "durationMs": 1084, + "observations": [ + { + "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", + "durationMs": 1102, + "observations": [ + { + "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": "published: shared setting mount preserves the surviving instance and later user changes", + "browser": "chromium", + "durationMs": 291, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "chromium", + "durationMs": 335, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "chromium", + "durationMs": 493, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy clears active send timer and leaves another mounted instance usable", + "browser": "chromium", + "durationMs": 495, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "chromium", + "durationMs": 424, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "chromium", + "durationMs": 443, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "chromium", + "durationMs": 442, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "chromium", + "durationMs": 742, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: shared setting mount preserves the surviving instance and later user changes", + "browser": "chromium", + "durationMs": 296, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "chromium", + "durationMs": 335, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "chromium", + "durationMs": 392, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy clears active send timer and leaves another mounted instance usable", + "browser": "chromium", + "durationMs": 596, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "chromium", + "durationMs": 402, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "chromium", + "durationMs": 459, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "chromium", + "durationMs": 462, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "chromium", + "durationMs": 470, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: shared setting mount preserves the surviving instance and later user changes", + "browser": "firefox", + "durationMs": 435, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "firefox", + "durationMs": 606, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "firefox", + "durationMs": 615, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy clears active send timer and leaves another mounted instance usable", + "browser": "firefox", + "durationMs": 934, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "firefox", + "durationMs": 652, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "firefox", + "durationMs": 1226, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "firefox", + "durationMs": 641, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "firefox", + "durationMs": 689, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: shared setting mount preserves the surviving instance and later user changes", + "browser": "firefox", + "durationMs": 453, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "firefox", + "durationMs": 517, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "firefox", + "durationMs": 644, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy clears active send timer and leaves another mounted instance usable", + "browser": "firefox", + "durationMs": 834, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "firefox", + "durationMs": 542, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "firefox", + "durationMs": 1247, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "firefox", + "durationMs": 601, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "firefox", + "durationMs": 612, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: shared setting mount preserves the surviving instance and later user changes", + "browser": "webkit", + "durationMs": 1478, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "webkit", + "durationMs": 1873, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "webkit", + "durationMs": 1860, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy clears active send timer and leaves another mounted instance usable", + "browser": "webkit", + "durationMs": 2784, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "webkit", + "durationMs": 1865, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "webkit", + "durationMs": 1764, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "webkit", + "durationMs": 1797, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "webkit", + "durationMs": 1867, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: shared setting mount preserves the surviving instance and later user changes", + "browser": "webkit", + "durationMs": 1507, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "webkit", + "durationMs": 1586, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "webkit", + "durationMs": 2069, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy clears active send timer and leaves another mounted instance usable", + "browser": "webkit", + "durationMs": 2572, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "webkit", + "durationMs": 1799, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "webkit", + "durationMs": 1742, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "webkit", + "durationMs": 1748, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "webkit", + "durationMs": 1956, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "chromium", + "durationMs": 2517, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "chromium", + "durationMs": 441, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.118284 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/e14dbf56-4dbe-45b3-b3e6-27854614d867", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "chromium", + "durationMs": 461, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.120462 + }, + { + "index": 0, + "event": "stop", + "time": 0.120462 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/c44cbe14-b220-4937-9b49-99118e7d1ae6", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2607, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2425, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2426, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2291, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "chromium", + "durationMs": 1295, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "chromium", + "durationMs": 1507, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "chromium", + "durationMs": 2312, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 602.7962036132812, + "next": 602.7962036132812, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 203.70000004768372, + "before": 0.028186, + "after": 0.028186, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 219.80000001192093, + "before": 0.044422, + "after": 0.044422, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 235.9000000357628, + "before": 0.060497, + "after": 0.060497, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 252.9000000357628, + "before": 0.077532, + "after": 0.077532, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 270.4000000357628, + "before": 0.094935, + "after": 0.094935, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 286.30000001192093, + "before": 0.110966, + "after": 0.110966, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 302.30000001192093, + "before": 0.126972, + "after": 0.126972, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 319.2000000476837, + "before": 0.143854, + "after": 0.143854, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 336.60000002384186, + "before": 0.161221, + "after": 0.161221, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 352.9000000357628, + "before": 0.177506, + "after": 0.177506, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 369.30000001192093, + "before": 0.193909, + "after": 0.193909, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 385.9000000357628, + "before": 0.210524, + "after": 0.210524, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 402.5, + "before": 0.227205, + "after": 0.227205, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 419.7000000476837, + "before": 0.244326, + "after": 0.244326, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 436.80000001192093, + "before": 0.261439, + "after": 0.261439, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 453.5, + "before": 0.278064, + "after": 0.278064, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 469.10000002384186, + "before": 0.293735, + "after": 0.293735, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 487.4000000357628, + "before": 0.312015, + "after": 0.312015, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 502.9000000357628, + "before": 0.327499, + "after": 0.327499, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 519.2000000476837, + "before": 0.343779, + "after": 0.343779, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 535.8000000119209, + "before": 0.360446, + "after": 0.360446, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 552.5, + "before": 0.377149, + "after": 0.377149, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 569.1000000238419, + "before": 0.393737, + "after": 0.393737, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 585.7000000476837, + "before": 0.410325, + "after": 0.410325, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 603.8000000119209, + "before": 0.428428, + "after": 0.428428, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 619.2000000476837, + "before": 0.443871, + "after": 0.443871, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 636.2000000476837, + "before": 0.460682, + "after": 0.460682, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 652.7000000476837, + "before": 0.477322, + "after": 0.477322, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 669.6000000238419, + "before": 0.494284, + "after": 0.494284, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 686, + "before": 0.510662, + "after": 0.510662, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 702.9000000357628, + "before": 0.527608, + "after": 0.527608, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 719.7000000476837, + "before": 0.544327, + "after": 0.544327, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 736.2000000476837, + "before": 0.560827, + "after": 0.560827, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 752.6000000238419, + "before": 0.577186, + "after": 0.577186, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 768.9000000357628, + "before": 0.593517, + "after": 0.593517, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 785.9000000357628, + "before": 0.61053, + "after": 0.61053, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 803, + "before": 0.627671, + "after": 0.627671, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 819.1000000238419, + "before": 0.643754, + "after": 0.643754, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 837.3000000119209, + "before": 0.661949, + "after": 0.661949, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 853.1000000238419, + "before": 0.677737, + "after": 0.677737, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 869.9000000357628, + "before": 0.694547, + "after": 0.694547, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 885.8000000119209, + "before": 0.710434, + "after": 0.710434, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 902.9000000357628, + "before": 0.727583, + "after": 0.727583, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 919.8000000119209, + "before": 0.744406, + "after": 0.744406, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 937.3000000119209, + "before": 0.761899, + "after": 0.761899, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 952.6000000238419, + "before": 0.777291, + "after": 0.777291, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 969, + "before": 0.793578, + "after": 0.793578, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 987, + "before": 0.81165, + "after": 0.81165, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1002.9000000357628, + "before": 0.827557, + "after": 0.827557, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1019.7000000476837, + "before": 0.844343, + "after": 0.844343, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1036.6000000238419, + "before": 0.861208, + "after": 0.861208, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1052.6000000238419, + "before": 0.877284, + "after": 0.877284, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1069.5, + "before": 0.894034, + "after": 0.894034, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1086, + "before": 0.910621, + "after": 0.910621, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1103.1000000238419, + "before": 0.927793, + "after": 0.927793, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1336.9000000357628, + "before": 2.059276, + "after": 2.059276, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1352.800000011921, + "before": 2.091068, + "after": 2.091068, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1369.2000000476837, + "before": 2.123972, + "after": 2.123972, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1386.5, + "before": 2.158532, + "after": 2.158532, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1403.4000000357628, + "before": 2.19226, + "after": 2.19226, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1419.4000000357628, + "before": 2.224356, + "after": 2.224356, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1435.6000000238419, + "before": 2.2568, + "after": 2.2568, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1452.800000011921, + "before": 2.291198, + "after": 2.291198, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1469.2000000476837, + "before": 2.324082, + "after": 2.324082, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1486.2000000476837, + "before": 2.35805, + "after": 2.35805, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1502.2000000476837, + "before": 2.390046, + "after": 2.390046, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1519.800000011921, + "before": 2.425246, + "after": 2.425246, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1536.6000000238419, + "before": 2.45879, + "after": 2.45879, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1552.6000000238419, + "before": 2.490734, + "after": 2.490734, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1569, + "before": 2.523588, + "after": 2.523588, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1586.300000011921, + "before": 2.558188, + "after": 2.558188, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1602.4000000357628, + "before": 2.590386, + "after": 2.590386, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1618.800000011921, + "before": 2.62332, + "after": 2.62332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1636.1000000238419, + "before": 2.657878, + "after": 2.657878, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1653, + "before": 2.6914, + "after": 2.6914, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1669.6000000238419, + "before": 2.724776, + "after": 2.724776, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1685.9000000357628, + "before": 2.757178, + "after": 2.757178, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1702.2000000476837, + "before": 2.789946, + "after": 2.789946, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1719.9000000357628, + "before": 2.825314, + "after": 2.825314, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1736.2000000476837, + "before": 2.857972, + "after": 2.857972, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1753.4000000357628, + "before": 2.892254, + "after": 2.892254, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1768.9000000357628, + "before": 2.92338, + "after": 2.92338, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1785.800000011921, + "before": 2.957332, + "after": 2.957332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1802.9000000357628, + "before": 2.99132, + "after": 2.99132, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1819.1000000238419, + "before": 3.023614, + "after": 3.023614, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1835.6000000238419, + "before": 3.056734, + "after": 3.056734, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1852.4000000357628, + "before": 3.090308, + "after": 3.090308, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1869.4000000357628, + "before": 3.12439, + "after": 3.12439, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1885.800000011921, + "before": 3.15711, + "after": 3.15711, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1903.5, + "before": 3.192506, + "after": 3.192506, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "wait" + } + ] + }, + { + "wall": 1920.300000011921, + "before": 3.226266, + "after": 3.226266, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + }, + { + "wall": 1936.5, + "before": 3.258534, + "after": 3.258534, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + }, + { + "wall": 1953, + "before": 3.291564, + "after": 3.291564, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + }, + { + "wall": 1969.300000011921, + "before": 3.32412, + "after": 3.32412, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + }, + { + "wall": 1985.9000000357628, + "before": 3.357342, + "after": 3.357342, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + }, + { + "wall": 2003.1000000238419, + "before": 3.3918, + "after": 3.3918, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + }, + { + "wall": 2019.1000000238419, + "before": 3.423832, + "after": 3.423832, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + }, + { + "wall": 2036.1000000238419, + "before": 3.45775, + "after": 3.45775, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + }, + { + "wall": 2052.300000011921, + "before": 3.49008, + "after": 3.49008, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + }, + { + "wall": 2069.2000000476837, + "before": 3.524058, + "after": 3.524058, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + }, + { + "wall": 2086.800000011921, + "before": 3.55918, + "after": 3.55918, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + }, + { + "wall": 2102.5, + "before": 3.590568, + "after": 3.590568, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.2678, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "chromium", + "durationMs": 3994, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "chromium", + "durationMs": 2372, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "chromium", + "durationMs": 463, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.126499 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/40d92210-3cea-4227-92b0-60af4baf0538", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "chromium", + "durationMs": 591, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.123063 + }, + { + "index": 0, + "event": "stop", + "time": 0.123063 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/f60c55f3-c907-466e-880b-bc0f8891356c", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2638, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2343, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2342, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2298, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "chromium", + "durationMs": 1371, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "chromium", + "durationMs": 1476, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "chromium", + "durationMs": 2357, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 601.334228515625, + "next": 601.334228515625, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 235.5, + "before": 0.028103, + "after": 0.028103, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 250.80000001192093, + "before": 0.043475, + "after": 0.043475, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 267.89999997615814, + "before": 0.060577, + "after": 0.060577, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 284.5, + "before": 0.077118, + "after": 0.077118, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 301, + "before": 0.093638, + "after": 0.093638, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 317.80000001192093, + "before": 0.110352, + "after": 0.110352, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 334.89999997615814, + "before": 0.127479, + "after": 0.127479, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 350.89999997615814, + "before": 0.143558, + "after": 0.143558, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 367.69999998807907, + "before": 0.160251, + "after": 0.160251, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 385.19999998807907, + "before": 0.177867, + "after": 0.177867, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 401.39999997615814, + "before": 0.194079, + "after": 0.194079, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 418.0999999642372, + "before": 0.210668, + "after": 0.210668, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 434.30000001192093, + "before": 0.22695, + "after": 0.22695, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 451, + "before": 0.243562, + "after": 0.243562, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 467.5999999642372, + "before": 0.260261, + "after": 0.260261, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 484.39999997615814, + "before": 0.276963, + "after": 0.276963, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 500.89999997615814, + "before": 0.293509, + "after": 0.293509, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 517.3999999761581, + "before": 0.310009, + "after": 0.310009, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 534.3000000119209, + "before": 0.326905, + "after": 0.326905, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 551.8999999761581, + "before": 0.344547, + "after": 0.344547, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 567.8000000119209, + "before": 0.360407, + "after": 0.360407, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 584.6999999880791, + "before": 0.377333, + "after": 0.377333, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 601.0999999642372, + "before": 0.393728, + "after": 0.393728, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 618, + "before": 0.410661, + "after": 0.410661, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 635, + "before": 0.427514, + "after": 0.427514, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 651.3999999761581, + "before": 0.444013, + "after": 0.444013, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 667.6999999880791, + "before": 0.460278, + "after": 0.460278, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 684.0999999642372, + "before": 0.476706, + "after": 0.476706, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 701, + "before": 0.493649, + "after": 0.493649, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 718.0999999642372, + "before": 0.510741, + "after": 0.510741, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 734.1999999880791, + "before": 0.526869, + "after": 0.526869, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 752.5, + "before": 0.545114, + "after": 0.545114, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 768.3000000119209, + "before": 0.560893, + "after": 0.560893, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 785, + "before": 0.577642, + "after": 0.577642, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 801.8999999761581, + "before": 0.594541, + "after": 0.594541, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 818.0999999642372, + "before": 0.610699, + "after": 0.610699, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 835.0999999642372, + "before": 0.627692, + "after": 0.627692, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 852.3999999761581, + "before": 0.645015, + "after": 0.645015, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 868.6999999880791, + "before": 0.661362, + "after": 0.661362, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 884, + "before": 0.676605, + "after": 0.676605, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 900.8999999761581, + "before": 0.693551, + "after": 0.693551, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 918.0999999642372, + "before": 0.710673, + "after": 0.710673, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 934.8000000119209, + "before": 0.727453, + "after": 0.727453, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 951.6999999880791, + "before": 0.744348, + "after": 0.744348, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 967.8000000119209, + "before": 0.760428, + "after": 0.760428, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 984.5, + "before": 0.777062, + "after": 0.777062, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1001, + "before": 0.793715, + "after": 0.793715, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1018.3000000119209, + "before": 0.810909, + "after": 0.810909, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1034.800000011921, + "before": 0.827374, + "after": 0.827374, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1051.300000011921, + "before": 0.843944, + "after": 0.843944, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1067.800000011921, + "before": 0.860372, + "after": 0.860372, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1085, + "before": 0.877632, + "after": 0.877632, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1101, + "before": 0.893673, + "after": 0.893673, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1118.5, + "before": 0.911164, + "after": 0.911164, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1134.5999999642372, + "before": 0.927241, + "after": 0.927241, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1150.8999999761581, + "before": 0.943555, + "after": 0.943555, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1384.5, + "before": 2.057756, + "after": 2.057756, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1402.5, + "before": 2.093658, + "after": 2.093658, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1417.3999999761581, + "before": 2.123354, + "after": 2.123354, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1434.8999999761581, + "before": 2.158492, + "after": 2.158492, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1451.5, + "before": 2.19167, + "after": 2.19167, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1467.5999999642372, + "before": 2.22387, + "after": 2.22387, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1484.0999999642372, + "before": 2.256906, + "after": 2.256906, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1501.3999999761581, + "before": 2.291334, + "after": 2.291334, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1518.300000011921, + "before": 2.325188, + "after": 2.325188, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1534, + "before": 2.356588, + "after": 2.356588, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1551.300000011921, + "before": 2.39117, + "after": 2.39117, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1568, + "before": 2.42469, + "after": 2.42469, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1584.8999999761581, + "before": 2.45846, + "after": 2.45846, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1600.8999999761581, + "before": 2.490394, + "after": 2.490394, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1618.199999988079, + "before": 2.525028, + "after": 2.525028, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1635, + "before": 2.55859, + "after": 2.55859, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1651.300000011921, + "before": 2.591224, + "after": 2.591224, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1668.3999999761581, + "before": 2.625514, + "after": 2.625514, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1684, + "before": 2.65664, + "after": 2.65664, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1701, + "before": 2.690678, + "after": 2.690678, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1718, + "before": 2.724634, + "after": 2.724634, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1734.199999988079, + "before": 2.756894, + "after": 2.756894, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1750.699999988079, + "before": 2.790004, + "after": 2.790004, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1767.3999999761581, + "before": 2.823432, + "after": 2.823432, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1784.8999999761581, + "before": 2.858364, + "after": 2.858364, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1800.8999999761581, + "before": 2.89037, + "after": 2.89037, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1818.5999999642372, + "before": 2.925766, + "after": 2.925766, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1834.3999999761581, + "before": 2.957226, + "after": 2.957226, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1851.5999999642372, + "before": 2.991816, + "after": 2.991816, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1868.0999999642372, + "before": 3.024836, + "after": 3.024836, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1884.5, + "before": 3.057556, + "after": 3.057556, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1901, + "before": 3.090712, + "after": 3.090712, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1918.199999988079, + "before": 3.12506, + "after": 3.12506, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1934.199999988079, + "before": 3.157092, + "after": 3.157092, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1951.199999988079, + "before": 3.19101, + "after": 3.19101, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "wait" + } + ] + }, + { + "wall": 1968.3999999761581, + "before": 3.225452, + "after": 3.225452, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + }, + { + "wall": 1984.300000011921, + "before": 3.25738, + "after": 3.25738, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + }, + { + "wall": 2001.3999999761581, + "before": 3.291476, + "after": 3.291476, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + }, + { + "wall": 2017.5, + "before": 3.323686, + "after": 3.323686, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + }, + { + "wall": 2034.5999999642372, + "before": 3.357788, + "after": 3.357788, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + }, + { + "wall": 2052, + "before": 3.392572, + "after": 3.392572, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + }, + { + "wall": 2067.399999976158, + "before": 3.423428, + "after": 3.423428, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + }, + { + "wall": 2084.599999964237, + "before": 3.45786, + "after": 3.45786, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + }, + { + "wall": 2100.699999988079, + "before": 3.49012, + "after": 3.49012, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + }, + { + "wall": 2117.599999964237, + "before": 3.523598, + "after": 3.523598, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + }, + { + "wall": 2135.300000011921, + "before": 3.559058, + "after": 3.559058, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + }, + { + "wall": 2152.099999964237, + "before": 3.592804, + "after": 3.592804, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.266574, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "chromium", + "durationMs": 4029, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "firefox", + "durationMs": 2413, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "firefox", + "durationMs": 795, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.135079 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/4a548f44-c472-4ed0-a791-a070f5a4334f", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "firefox", + "durationMs": 722, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.125349 + }, + { + "index": 0, + "event": "stop", + "time": 0.125349 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/93cebef0-5683-4d9c-88dc-b8a7b9e67672", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2700, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2514, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2565, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2473, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "firefox", + "durationMs": 2108, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "firefox", + "durationMs": 1687, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "firefox", + "durationMs": 2631, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 601.3333129882812, + "next": 601.3333129882812, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 362, + "before": 0.007241, + "after": 0.007241, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 380, + "before": 0.007241, + "after": 0.007241, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 396, + "before": 0.048156, + "after": 0.048156, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 413, + "before": 0.048156, + "after": 0.048156, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 430, + "before": 0.088465, + "after": 0.088465, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 446, + "before": 0.088465, + "after": 0.088465, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 463, + "before": 0.088465, + "after": 0.088465, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 479, + "before": 0.129111, + "after": 0.129111, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 496, + "before": 0.129111, + "after": 0.129111, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 513, + "before": 0.169319, + "after": 0.169319, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 530, + "before": 0.169319, + "after": 0.169319, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 546, + "before": 0.169319, + "after": 0.169319, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 563, + "before": 0.209913, + "after": 0.209913, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 580, + "before": 0.209913, + "after": 0.209913, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 597, + "before": 0.249999, + "after": 0.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 613, + "before": 0.249999, + "after": 0.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 629, + "before": 0.249999, + "after": 0.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 647, + "before": 0.291666, + "after": 0.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 663, + "before": 0.291666, + "after": 0.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 680, + "before": 0.333332, + "after": 0.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 697, + "before": 0.333332, + "after": 0.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 713, + "before": 0.333332, + "after": 0.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 730, + "before": 0.374999, + "after": 0.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 747, + "before": 0.374999, + "after": 0.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 763, + "before": 0.416666, + "after": 0.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 780, + "before": 0.416666, + "after": 0.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 796, + "before": 0.416666, + "after": 0.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 813, + "before": 0.458332, + "after": 0.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 830, + "before": 0.458332, + "after": 0.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 846, + "before": 0.498679, + "after": 0.498679, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 864, + "before": 0.498679, + "after": 0.498679, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 879, + "before": 0.498679, + "after": 0.498679, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 896, + "before": 0.541666, + "after": 0.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 913, + "before": 0.541666, + "after": 0.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 929, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 947, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 963, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 980, + "before": 0.624999, + "after": 0.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 997, + "before": 0.624999, + "after": 0.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1014, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1030, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1046, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1064, + "before": 0.708332, + "after": 0.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1080, + "before": 0.708332, + "after": 0.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1097, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1113, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1131, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1147, + "before": 0.791666, + "after": 0.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1163, + "before": 0.791666, + "after": 0.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1180, + "before": 0.835297, + "after": 0.835297, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1197, + "before": 0.835297, + "after": 0.835297, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1214, + "before": 0.835297, + "after": 0.835297, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1230, + "before": 0.876264, + "after": 0.876264, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1247, + "before": 0.876264, + "after": 0.876264, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1266, + "before": 0.916666, + "after": 0.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1281, + "before": 0.916666, + "after": 0.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1297, + "before": 0.916666, + "after": 0.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1315, + "before": 0.958332, + "after": 0.958332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1480, + "before": 2.000073, + "after": 2.000073, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1497, + "before": 2.041666, + "after": 2.041666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1514, + "before": 2.083332, + "after": 2.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1530, + "before": 2.083332, + "after": 2.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1547, + "before": 2.124999, + "after": 2.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1563, + "before": 2.166666, + "after": 2.166666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1581, + "before": 2.208332, + "after": 2.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1596, + "before": 2.249999, + "after": 2.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1614, + "before": 2.249999, + "after": 2.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1630, + "before": 2.291666, + "after": 2.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1647, + "before": 2.333332, + "after": 2.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1663, + "before": 2.374999, + "after": 2.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1681, + "before": 2.416666, + "after": 2.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1697, + "before": 2.416666, + "after": 2.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1714, + "before": 2.458332, + "after": 2.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1731, + "before": 2.499999, + "after": 2.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1747, + "before": 2.541666, + "after": 2.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1764, + "before": 2.583332, + "after": 2.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1781, + "before": 2.583332, + "after": 2.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1797, + "before": 2.624999, + "after": 2.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1814, + "before": 2.666666, + "after": 2.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1831, + "before": 2.708332, + "after": 2.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1847, + "before": 2.749999, + "after": 2.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1864, + "before": 2.749999, + "after": 2.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1880, + "before": 2.791666, + "after": 2.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1897, + "before": 2.833332, + "after": 2.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1914, + "before": 2.874999, + "after": 2.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1931, + "before": 2.916666, + "after": 2.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1947, + "before": 2.916666, + "after": 2.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1964, + "before": 2.958332, + "after": 2.958332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1981, + "before": 2.999999, + "after": 2.999999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1997, + "before": 3.041666, + "after": 3.041666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 2014, + "before": 3.083332, + "after": 3.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 2031, + "before": 3.124999, + "after": 3.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 2048, + "before": 3.124999, + "after": 3.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 2064, + "before": 3.166666, + "after": 3.166666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 2081, + "before": 3.208332, + "after": 3.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 2098, + "before": 3.249973, + "after": 3.249973, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2115, + "before": 3.291075, + "after": 3.291075, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2134, + "before": 3.291075, + "after": 3.291075, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2147, + "before": 3.333332, + "after": 3.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2164, + "before": 3.374999, + "after": 3.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2180, + "before": 3.416666, + "after": 3.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2197, + "before": 3.416666, + "after": 3.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2214, + "before": 3.458332, + "after": 3.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2230, + "before": 3.499999, + "after": 3.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2247, + "before": 3.540836, + "after": 3.540836, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2264, + "before": 3.581433, + "after": 3.581433, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2281, + "before": 3.621838, + "after": 3.621838, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2297, + "before": 3.621838, + "after": 3.621838, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2315, + "before": 3.663074, + "after": 3.663074, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "firefox", + "durationMs": 4548, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "firefox", + "durationMs": 2467, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "firefox", + "durationMs": 704, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.128048 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/b2b0de47-d8c6-4783-91d3-1c67330ab177", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "firefox", + "durationMs": 625, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.005397 + }, + { + "index": 0, + "event": "stop", + "time": 0.005397 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/15197620-a2d4-414a-ae96-03e09404c394", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2791, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2733, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2537, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2596, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "firefox", + "durationMs": 2033, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "firefox", + "durationMs": 1657, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "firefox", + "durationMs": 2527, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 601.3333129882812, + "next": 601.3333129882812, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 261, + "before": 0.007219, + "after": 0.007219, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 277, + "before": 0.007219, + "after": 0.007219, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 294, + "before": 0.04776, + "after": 0.04776, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 311, + "before": 0.04776, + "after": 0.04776, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 328, + "before": 0.088417, + "after": 0.088417, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 344, + "before": 0.088417, + "after": 0.088417, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 361, + "before": 0.088417, + "after": 0.088417, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 377, + "before": 0.128566, + "after": 0.128566, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 394, + "before": 0.128566, + "after": 0.128566, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 411, + "before": 0.168722, + "after": 0.168722, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 428, + "before": 0.168722, + "after": 0.168722, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 445, + "before": 0.168722, + "after": 0.168722, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 461, + "before": 0.209164, + "after": 0.209164, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 478, + "before": 0.209164, + "after": 0.209164, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 495, + "before": 0.249999, + "after": 0.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 511, + "before": 0.249999, + "after": 0.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 528, + "before": 0.249999, + "after": 0.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 545, + "before": 0.291666, + "after": 0.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 561, + "before": 0.291666, + "after": 0.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 577, + "before": 0.333332, + "after": 0.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 594, + "before": 0.333332, + "after": 0.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 611, + "before": 0.333332, + "after": 0.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 628, + "before": 0.374999, + "after": 0.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 644, + "before": 0.374999, + "after": 0.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 661, + "before": 0.416666, + "after": 0.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 678, + "before": 0.416666, + "after": 0.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 694, + "before": 0.416666, + "after": 0.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 711, + "before": 0.458332, + "after": 0.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 727, + "before": 0.458332, + "after": 0.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 745, + "before": 0.499999, + "after": 0.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 761, + "before": 0.499999, + "after": 0.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 778, + "before": 0.499999, + "after": 0.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 794, + "before": 0.541666, + "after": 0.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 812, + "before": 0.541666, + "after": 0.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 827, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 844, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 861, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 878, + "before": 0.624999, + "after": 0.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 895, + "before": 0.624999, + "after": 0.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 911, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 928, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 945, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 962, + "before": 0.708332, + "after": 0.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 978, + "before": 0.708332, + "after": 0.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 995, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1011, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1028, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1045, + "before": 0.791666, + "after": 0.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1061, + "before": 0.791666, + "after": 0.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1078, + "before": 0.833332, + "after": 0.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1094, + "before": 0.833332, + "after": 0.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1112, + "before": 0.833332, + "after": 0.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1128, + "before": 0.874999, + "after": 0.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1145, + "before": 0.874999, + "after": 0.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1164, + "before": 0.916666, + "after": 0.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1179, + "before": 0.916666, + "after": 0.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1194, + "before": 0.916666, + "after": 0.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1213, + "before": 0.958332, + "after": 0.958332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1378, + "before": 2.000064, + "after": 2.000064, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1395, + "before": 2.041666, + "after": 2.041666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1412, + "before": 2.083332, + "after": 2.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1428, + "before": 2.083332, + "after": 2.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1445, + "before": 2.124999, + "after": 2.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1461, + "before": 2.166666, + "after": 2.166666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1479, + "before": 2.208332, + "after": 2.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1495, + "before": 2.249999, + "after": 2.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1512, + "before": 2.249999, + "after": 2.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1529, + "before": 2.291666, + "after": 2.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1546, + "before": 2.334864, + "after": 2.334864, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1562, + "before": 2.374999, + "after": 2.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1579, + "before": 2.416666, + "after": 2.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1595, + "before": 2.416666, + "after": 2.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1612, + "before": 2.458332, + "after": 2.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1629, + "before": 2.499999, + "after": 2.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1645, + "before": 2.541666, + "after": 2.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1662, + "before": 2.583332, + "after": 2.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1678, + "before": 2.583332, + "after": 2.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1695, + "before": 2.624999, + "after": 2.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1712, + "before": 2.666666, + "after": 2.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1729, + "before": 2.708332, + "after": 2.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1745, + "before": 2.749999, + "after": 2.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1762, + "before": 2.749999, + "after": 2.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1779, + "before": 2.791666, + "after": 2.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1795, + "before": 2.833332, + "after": 2.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1812, + "before": 2.874999, + "after": 2.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1829, + "before": 2.916666, + "after": 2.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1846, + "before": 2.916666, + "after": 2.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1862, + "before": 2.959734, + "after": 2.959734, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1879, + "before": 2.999999, + "after": 2.999999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1896, + "before": 3.041666, + "after": 3.041666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1912, + "before": 3.089442, + "after": 3.089442, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1930, + "before": 3.089442, + "after": 3.089442, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1945, + "before": 3.130082, + "after": 3.130082, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1962, + "before": 3.171009, + "after": 3.171009, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1978, + "before": 3.21251, + "after": 3.21251, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "wait" + } + ] + }, + { + "wall": 1996, + "before": 3.253029, + "after": 3.253029, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2012, + "before": 3.253029, + "after": 3.253029, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2029, + "before": 3.294267, + "after": 3.294267, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2046, + "before": 3.335072, + "after": 3.335072, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2062, + "before": 3.376918, + "after": 3.376918, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2079, + "before": 3.420602, + "after": 3.420602, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2095, + "before": 3.420602, + "after": 3.420602, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2112, + "before": 3.461041, + "after": 3.461041, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2129, + "before": 3.501906, + "after": 3.501906, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2146, + "before": 3.543108, + "after": 3.543108, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2163, + "before": 3.583332, + "after": 3.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2179, + "before": 3.583332, + "after": 3.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2195, + "before": 3.624999, + "after": 3.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + }, + { + "wall": 2213, + "before": 3.666656, + "after": 3.666656, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.291666, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "firefox", + "durationMs": 4594, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "webkit", + "durationMs": 3769, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "webkit", + "durationMs": 1747, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.1416231 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/c5143333-bf8e-4ced-aa71-8cc54c89a884", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "webkit", + "durationMs": 1648, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.0715572 + }, + { + "index": 0, + "event": "stop", + "time": 0.0715858 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/2f467548-e4ec-49f3-96bc-889088d432af", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4489, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4090, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4415, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 3703, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "webkit", + "durationMs": 2928, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "webkit", + "durationMs": 3115, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "webkit", + "durationMs": 4252, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 615.2750244140625, + "next": 615.2750244140625, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 1165, + "before": 0.0159348, + "after": 0.0160002, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1270, + "before": 0.1213008, + "after": 0.1213159, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1331, + "before": 0.1820146, + "after": 0.1820271, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1362, + "before": 0.2130061, + "after": 0.2130194, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1408, + "before": 0.2590538, + "after": 0.2590804, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1454, + "before": 0.3049925, + "after": 0.3050046, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1498, + "before": 0.3492606, + "after": 0.349291, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1559, + "before": 0.4108884, + "after": 0.410913, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1590, + "before": 0.4416609, + "after": 0.4416737, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1636, + "before": 0.4874301, + "after": 0.4874423, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1667, + "before": 0.5187863, + "after": 0.5188139, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1714, + "before": 0.5652963, + "after": 0.5653059, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1744, + "before": 0.5959024, + "after": 0.5959126, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1808, + "before": 0.6592278, + "after": 0.6592459, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1882, + "before": 0.7333501, + "after": 0.7333602, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1928, + "before": 0.7798459, + "after": 0.7798607, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1963, + "before": 0.814367, + "after": 0.8143766, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1964, + "before": 0.8157498, + "after": 0.8157563, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2418, + "before": 2.10286, + "after": 2.102886, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "wait" + } + ] + }, + { + "wall": 2447, + "before": 2.1616508, + "after": 2.1616772, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "wait" + } + ] + }, + { + "wall": 2493, + "before": 2.2537336, + "after": 2.2537574, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "wait" + } + ] + }, + { + "wall": 2524, + "before": 2.3164212, + "after": 2.316444, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "wait" + } + ] + }, + { + "wall": 2570, + "before": 2.407946, + "after": 2.4079758, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "wait" + } + ] + }, + { + "wall": 2617, + "before": 2.5009358, + "after": 2.5009586, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + } + ] + }, + { + "wall": 2663, + "before": 2.5931474, + "after": 2.5931726, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + } + ] + }, + { + "wall": 2725, + "before": 2.7176728, + "after": 2.7176968, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + } + ] + }, + { + "wall": 2771, + "before": 2.8092912, + "after": 2.8093206, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + } + ] + }, + { + "wall": 2802, + "before": 2.872333, + "after": 2.872362, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + } + ] + }, + { + "wall": 2859, + "before": 2.9848032, + "after": 2.9848348, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + } + ] + }, + { + "wall": 2894, + "before": 3.0561996, + "after": 3.056223, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "wait" + } + ] + }, + { + "wall": 2941, + "before": 3.1491002, + "after": 3.1491274, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "wait" + } + ] + }, + { + "wall": 2985, + "before": 3.2378288, + "after": 3.2379482, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "wait" + } + ] + }, + { + "wall": 3030, + "before": 3.3284306, + "after": 3.3284786, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "wait" + } + ] + }, + { + "wall": 3077, + "before": 3.420845, + "after": 3.4208874, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "wait" + } + ] + }, + { + "wall": 3124, + "before": 3.515504, + "after": 3.5155426, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3186, + "before": 3.6397388, + "after": 3.6398134, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3231, + "before": 3.73033, + "after": 3.7303538, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3277, + "before": 3.8210746, + "after": 3.8210958, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3324, + "before": 3.914924, + "after": 3.914941, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3370, + "before": 4.0077072, + "after": 4.00773, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3415, + "before": 4.098272, + "after": 4.0982948, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3476, + "before": 4.2200058, + "after": 4.2200296, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3522, + "before": 4.3117306, + "after": 4.311753, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3567, + "before": 4.4020572, + "after": 4.4020802, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3612, + "before": 4.4919984, + "after": 4.492021, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3627, + "before": 4.522652, + "after": 4.5226694, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + }, + { + "wall": 3629, + "before": 4.5261468, + "after": 4.5261642, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5019035, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5100708, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "webkit", + "durationMs": 6889, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "webkit", + "durationMs": 3771, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "webkit", + "durationMs": 1706, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.1392104 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/0c26076e-d8c5-4a38-89b7-bda526378a80", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "webkit", + "durationMs": 1734, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.2148273 + }, + { + "index": 0, + "event": "stop", + "time": 0.2148543 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/095e1914-6a2e-4dca-a296-95fe2057f84b", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4640, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4133, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4163, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 3907, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "webkit", + "durationMs": 3054, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "webkit", + "durationMs": 3072, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "webkit", + "durationMs": 4664, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 576.4249877929688, + "next": 576.4249877929688, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 1114, + "before": 0.0275833, + "after": 0.027667, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1161, + "before": 0.0743575, + "after": 0.0743736, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1191, + "before": 0.1047969, + "after": 0.1048159, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1242, + "before": 0.1552228, + "after": 0.1552407, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1284, + "before": 0.1980201, + "after": 0.1980339, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1344, + "before": 0.2575539, + "after": 0.2575664, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1390, + "before": 0.3036323, + "after": 0.3036596, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1435, + "before": 0.3489225, + "after": 0.3489343, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1497, + "before": 0.4105937, + "after": 0.4106036, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1529, + "before": 0.4424765, + "after": 0.4424932, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1573, + "before": 0.4868187, + "after": 0.4868405, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1620, + "before": 0.5338339, + "after": 0.5338443, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1650, + "before": 0.5639797, + "after": 0.5639901, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1681, + "before": 0.594222, + "after": 0.5942335, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1726, + "before": 0.6398355, + "after": 0.6398459, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1756, + "before": 0.6696594, + "after": 0.6696711, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1803, + "before": 0.7167381, + "after": 0.7167482, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1834, + "before": 0.7477257, + "after": 0.7477358, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1880, + "before": 0.7937526, + "after": 0.7937645, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1924, + "before": 0.8375983, + "after": 0.8376108, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1971, + "before": 0.8845255, + "after": 0.8845334, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2032, + "before": 0.9457481, + "after": 0.9457562, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2079, + "before": 0.9922146, + "after": 0.9922243, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2125, + "before": 1.0383257, + "after": 1.0383387, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2187, + "before": 1.1006115, + "after": 1.1006228, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2233, + "before": 1.1464477, + "after": 1.1464564, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2281, + "before": 1.1941408, + "after": 1.1941542, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2342, + "before": 1.2552398, + "after": 1.255262, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2345, + "before": 1.2582094, + "after": 1.258221, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2346, + "before": 1.2599337, + "after": 1.2599455, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2817, + "before": 2.0395904, + "after": 2.039659, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "wait" + } + ] + }, + { + "wall": 2862, + "before": 2.1307784, + "after": 2.1308368, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "wait" + } + ] + }, + { + "wall": 2907, + "before": 2.2198808, + "after": 2.2199116, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "wait" + } + ] + }, + { + "wall": 2938, + "before": 2.2820778, + "after": 2.282107, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "wait" + } + ] + }, + { + "wall": 2969, + "before": 2.3440042, + "after": 2.3440482, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "wait" + } + ] + }, + { + "wall": 3014, + "before": 2.4342786, + "after": 2.4343938, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "wait" + } + ] + }, + { + "wall": 3059, + "before": 2.5246888, + "after": 2.5247134, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + } + ] + }, + { + "wall": 3089, + "before": 2.5847162, + "after": 2.5847382, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + } + ] + }, + { + "wall": 3120, + "before": 2.6461868, + "after": 2.6462078, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + } + ] + }, + { + "wall": 3167, + "before": 2.7402986, + "after": 2.7403186, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + } + ] + }, + { + "wall": 3198, + "before": 2.8022574, + "after": 2.802285, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + } + ] + }, + { + "wall": 3237, + "before": 2.8796478, + "after": 2.8796642, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + } + ] + }, + { + "wall": 3273, + "before": 2.952094, + "after": 2.9521478, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "wait" + } + ] + }, + { + "wall": 3320, + "before": 3.0465276, + "after": 3.0465522, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "wait" + } + ] + }, + { + "wall": 3368, + "before": 3.1414532, + "after": 3.1414762, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "wait" + } + ] + }, + { + "wall": 3398, + "before": 3.2027528, + "after": 3.2027978, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "wait" + } + ] + }, + { + "wall": 3445, + "before": 3.295084, + "after": 3.2951464, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "wait" + } + ] + }, + { + "wall": 3490, + "before": 3.3863962, + "after": 3.386446, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "wait" + } + ] + }, + { + "wall": 3552, + "before": 3.5096228, + "after": 3.5096452, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "emit" + } + ] + }, + { + "wall": 3598, + "before": 3.6021884, + "after": 3.602211, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "emit" + } + ] + }, + { + "wall": 3645, + "before": 3.6949332, + "after": 3.6949552, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "emit" + } + ] + }, + { + "wall": 3707, + "before": 3.8200626, + "after": 3.820086, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "emit" + } + ] + }, + { + "wall": 3752, + "before": 3.9104908, + "after": 3.910516, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "emit" + } + ] + }, + { + "wall": 3797, + "before": 3.9998794, + "after": 3.999907, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "emit" + } + ] + }, + { + "wall": 3859, + "before": 4.1237156, + "after": 4.1237348, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "emit" + } + ] + }, + { + "wall": 3905, + "before": 4.215603, + "after": 4.2156254, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "emit" + } + ] + }, + { + "wall": 3952, + "before": 4.3099956, + "after": 4.3100146, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "emit" + } + ] + }, + { + "wall": 3954, + "before": 4.3140048, + "after": 4.3140216, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "emit" + } + ] + }, + { + "wall": 3955, + "before": 4.3166564, + "after": 4.316669, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5014608, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4101818, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "webkit", + "durationMs": 7411, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + } + ] + }, + { + "format": "legacy", + "tests": 138, + "workers": 2, + "file": "refactor/.cache/danmuku05-browser-legacy-resource-final/report.json", + "sha256": "50ced37e6fe77310ec619e6f0782eecd07eb1666014c6d93580251ca3dc15f68", + "run": { + "file": "refactor/.cache/danmuku05-browser-legacy-resource-final/run.log", + "sha256": "e0a6bdbaec9f12796655145d7be1670224cd9aea8ee6413d0e1a836ddb14b05b" + }, + "specs": [ + { + "file": "refactor/.cache/danmuku05-browser-legacy-resource-final/danmuku-input.spec.js", + "sha256": "37a96982ea7bf2fb99df5e4d829ce1c3f512145092a440586c2d5ad7d0a2d6f3" + }, + { + "file": "refactor/.cache/danmuku05-browser-legacy-resource-final/danmuku-scheduler.spec.js", + "sha256": "ee67bc0f204f1f9090859ed9d1d0cfecc545129fff6278d2f17140b009390519" + }, + { + "file": "refactor/.cache/danmuku05-browser-legacy-resource-final/danmuku-resources.spec.js", + "sha256": "f0d09e962cb422a66bd644c52268aff8f288606ae9c93676fa979539743f9c6b" + } + ], + "archiveResultsRoot": "refactor/.cache/danmuku05-browser-legacy-resource-final/results", + "originalResultsRoot": "refactor/.cache/browser/results", + "versions": [ + "chromium: 153.0.8010.12", + "firefox: 155.0", + "webkit: 26.6" + ], + "cases": [ + { + "title": "Danmuku input native Worker startup failure falls back locally (published)", + "browser": "chromium", + "durationMs": 386, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/3d88e94a-b248-4b64-b54c-21d6d4a549c5", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/f386f30d-74d6-4814-84e5-e88f3da95fda", + "blob:http://127.0.0.1:8084/3d88e94a-b248-4b64-b54c-21d6d4a549c5" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/3d88e94a-b248-4b64-b54c-21d6d4a549c5", + "blob:http://127.0.0.1:8084/f386f30d-74d6-4814-84e5-e88f3da95fda" + ], + "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", + "durationMs": 273, + "observations": [ + { + "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/643ba9b4-955d-42e4-b0b9-79e7166a875e", + "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/acaff2f5-e1b8-4679-8525-84552d4a89a7", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/d24fda1a-1c90-4e25-99cf-fb593d7f0cfe", + "blob:http://127.0.0.1:8084/643ba9b4-955d-42e4-b0b9-79e7166a875e", + "blob:http://127.0.0.1:8084/acaff2f5-e1b8-4679-8525-84552d4a89a7" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/643ba9b4-955d-42e4-b0b9-79e7166a875e", + "blob:http://127.0.0.1:8084/acaff2f5-e1b8-4679-8525-84552d4a89a7" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/d24fda1a-1c90-4e25-99cf-fb593d7f0cfe", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/643ba9b4-955d-42e4-b0b9-79e7166a875e", + "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/acaff2f5-e1b8-4679-8525-84552d4a89a7", + "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", + "durationMs": 241, + "observations": [ + { + "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", + "durationMs": 223, + "observations": [ + { + "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", + "durationMs": 399, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/c32baba2-f7a3-4ba2-aa37-8458b0064985", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/e71b6ff5-5dd5-43f7-a8c5-a7bae5401ed5", + "blob:http://127.0.0.1:8084/c32baba2-f7a3-4ba2-aa37-8458b0064985" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/c32baba2-f7a3-4ba2-aa37-8458b0064985", + "blob:http://127.0.0.1:8084/e71b6ff5-5dd5-43f7-a8c5-a7bae5401ed5" + ], + "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", + "durationMs": 281, + "observations": [ + { + "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/c5241b76-f23a-4eed-9faa-2157f9b391b2", + "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/e0a43471-59eb-4ff5-a4b9-c36e14e8d681", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/ddd85100-8ce1-449a-9da5-9044f2d89a20", + "blob:http://127.0.0.1:8084/c5241b76-f23a-4eed-9faa-2157f9b391b2", + "blob:http://127.0.0.1:8084/e0a43471-59eb-4ff5-a4b9-c36e14e8d681" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/c5241b76-f23a-4eed-9faa-2157f9b391b2", + "blob:http://127.0.0.1:8084/e0a43471-59eb-4ff5-a4b9-c36e14e8d681" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/ddd85100-8ce1-449a-9da5-9044f2d89a20", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/c5241b76-f23a-4eed-9faa-2157f9b391b2", + "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/e0a43471-59eb-4ff5-a4b9-c36e14e8d681", + "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", + "durationMs": 259, + "observations": [ + { + "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", + "durationMs": 240, + "observations": [ + { + "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", + "durationMs": 2036, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/1e85ebc6-4fe0-49da-a379-0e0be3a6d0d5", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/7facd423-b29b-47e5-bb73-b5799c71047a", + "blob:http://127.0.0.1:8084/1e85ebc6-4fe0-49da-a379-0e0be3a6d0d5" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/1e85ebc6-4fe0-49da-a379-0e0be3a6d0d5", + "blob:http://127.0.0.1:8084/7facd423-b29b-47e5-bb73-b5799c71047a" + ], + "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", + "durationMs": 486, + "observations": [ + { + "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/c378306f-f185-4d73-b2a5-c68c6662a842", + "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/ca37a1a6-457d-4d40-9e66-5d854ef823a3", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/2a57e037-3ac6-4ba1-8db4-a8b8e7f64bc1", + "blob:http://127.0.0.1:8084/c378306f-f185-4d73-b2a5-c68c6662a842", + "blob:http://127.0.0.1:8084/ca37a1a6-457d-4d40-9e66-5d854ef823a3" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/c378306f-f185-4d73-b2a5-c68c6662a842", + "blob:http://127.0.0.1:8084/ca37a1a6-457d-4d40-9e66-5d854ef823a3" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/2a57e037-3ac6-4ba1-8db4-a8b8e7f64bc1", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/c378306f-f185-4d73-b2a5-c68c6662a842", + "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/ca37a1a6-457d-4d40-9e66-5d854ef823a3", + "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", + "durationMs": 574, + "observations": [ + { + "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", + "durationMs": 421, + "observations": [ + { + "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", + "durationMs": 1890, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/4083df27-c02e-41c8-89c5-27f7af71156d", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/5c60ae55-0471-466e-a74d-6bce0b3bfcee", + "blob:http://127.0.0.1:8084/4083df27-c02e-41c8-89c5-27f7af71156d" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/4083df27-c02e-41c8-89c5-27f7af71156d", + "blob:http://127.0.0.1:8084/5c60ae55-0471-466e-a74d-6bce0b3bfcee" + ], + "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", + "durationMs": 617, + "observations": [ + { + "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/1b9c05c4-ef91-4a62-a209-1e06e6f0d1c1", + "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/35ff1f0f-60b7-4b32-ad05-e93ca5cbe27e", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/2c32276b-9b15-4195-98d7-b7097f6987cf", + "blob:http://127.0.0.1:8084/1b9c05c4-ef91-4a62-a209-1e06e6f0d1c1", + "blob:http://127.0.0.1:8084/35ff1f0f-60b7-4b32-ad05-e93ca5cbe27e" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/1b9c05c4-ef91-4a62-a209-1e06e6f0d1c1", + "blob:http://127.0.0.1:8084/35ff1f0f-60b7-4b32-ad05-e93ca5cbe27e" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/2c32276b-9b15-4195-98d7-b7097f6987cf", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/1b9c05c4-ef91-4a62-a209-1e06e6f0d1c1", + "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/35ff1f0f-60b7-4b32-ad05-e93ca5cbe27e", + "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", + "durationMs": 440, + "observations": [ + { + "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", + "durationMs": 570, + "observations": [ + { + "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", + "durationMs": 1782, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/e3e62931-e387-4c65-8166-6ffb496729dc", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/954becc1-c45b-4905-a97e-ae0ff3b7a821", + "blob:http://127.0.0.1:8084/e3e62931-e387-4c65-8166-6ffb496729dc" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/e3e62931-e387-4c65-8166-6ffb496729dc", + "blob:http://127.0.0.1:8084/954becc1-c45b-4905-a97e-ae0ff3b7a821" + ], + "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", + "durationMs": 1470, + "observations": [ + { + "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/4979bfc0-3c22-4f92-8afd-8842248c265a", + "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/b16334eb-f5b7-4439-b832-fe2460031265", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/c436d1a3-a98c-47b7-97a8-62ef8c943186", + "blob:http://127.0.0.1:8084/4979bfc0-3c22-4f92-8afd-8842248c265a", + "blob:http://127.0.0.1:8084/b16334eb-f5b7-4439-b832-fe2460031265" + ], + "revoked": [ + "", + "blob:http://127.0.0.1:8084/4979bfc0-3c22-4f92-8afd-8842248c265a", + "blob:http://127.0.0.1:8084/b16334eb-f5b7-4439-b832-fe2460031265" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/c436d1a3-a98c-47b7-97a8-62ef8c943186", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/4979bfc0-3c22-4f92-8afd-8842248c265a", + "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/b16334eb-f5b7-4439-b832-fe2460031265", + "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", + "durationMs": 1333, + "observations": [ + { + "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", + "durationMs": 1293, + "observations": [ + { + "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", + "durationMs": 1717, + "observations": [ + { + "name": "danmuku-native-worker-fault", + "value": { + "queue": [ + { + "text": "FALLBACK", + "time": 0, + "mode": 1 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/a036e468-7f3a-4272-8d3a-7fa8acf5cbef", + "terminated": 1, + "replies": [] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/9a41ce02-f5b5-46bf-bade-0b91976bbb1a", + "blob:http://127.0.0.1:8084/a036e468-7f3a-4272-8d3a-7fa8acf5cbef" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/a036e468-7f3a-4272-8d3a-7fa8acf5cbef", + "blob:http://127.0.0.1:8084/9a41ce02-f5b5-46bf-bade-0b91976bbb1a" + ], + "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", + "durationMs": 1286, + "observations": [ + { + "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/2ae184c9-323e-409d-9bfa-612ee760bd02", + "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/33962da8-a6c5-42fa-8bd9-ad02d8a9a0bf", + "terminated": 1, + "replies": [ + { + "danmus": [], + "id": 2 + } + ] + } + ], + "urls": [ + "blob:http://127.0.0.1:8084/d33f1edb-1560-450c-a1ab-ca08b83b87f9", + "blob:http://127.0.0.1:8084/2ae184c9-323e-409d-9bfa-612ee760bd02", + "blob:http://127.0.0.1:8084/33962da8-a6c5-42fa-8bd9-ad02d8a9a0bf" + ], + "revoked": [ + "blob:http://127.0.0.1:8084/2ae184c9-323e-409d-9bfa-612ee760bd02", + "blob:http://127.0.0.1:8084/33962da8-a6c5-42fa-8bd9-ad02d8a9a0bf" + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/d33f1edb-1560-450c-a1ab-ca08b83b87f9", + "terminated": 1, + "replies": [] + }, + { + "url": "blob:http://127.0.0.1:8084/2ae184c9-323e-409d-9bfa-612ee760bd02", + "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/33962da8-a6c5-42fa-8bd9-ad02d8a9a0bf", + "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", + "durationMs": 1519, + "observations": [ + { + "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", + "durationMs": 1321, + "observations": [ + { + "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": "published: shared setting mount preserves the surviving instance and later user changes", + "browser": "chromium", + "durationMs": 277, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "chromium", + "durationMs": 303, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "chromium", + "durationMs": 475, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy clears active send timer and leaves another mounted instance usable", + "browser": "chromium", + "durationMs": 460, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "chromium", + "durationMs": 373, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "chromium", + "durationMs": 428, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "chromium", + "durationMs": 441, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "chromium", + "durationMs": 474, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: shared setting mount preserves the surviving instance and later user changes", + "browser": "chromium", + "durationMs": 317, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "chromium", + "durationMs": 316, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "chromium", + "durationMs": 391, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy clears active send timer and leaves another mounted instance usable", + "browser": "chromium", + "durationMs": 460, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "chromium", + "durationMs": 392, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "chromium", + "durationMs": 441, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "chromium", + "durationMs": 458, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "chromium", + "durationMs": 511, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: shared setting mount preserves the surviving instance and later user changes", + "browser": "firefox", + "durationMs": 686, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "firefox", + "durationMs": 1030, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "firefox", + "durationMs": 1008, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy clears active send timer and leaves another mounted instance usable", + "browser": "firefox", + "durationMs": 965, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "firefox", + "durationMs": 708, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "firefox", + "durationMs": 1344, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "firefox", + "durationMs": 804, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "firefox", + "durationMs": 704, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: shared setting mount preserves the surviving instance and later user changes", + "browser": "firefox", + "durationMs": 1157, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "firefox", + "durationMs": 668, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "firefox", + "durationMs": 794, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy clears active send timer and leaves another mounted instance usable", + "browser": "firefox", + "durationMs": 1098, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "firefox", + "durationMs": 625, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "firefox", + "durationMs": 1423, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "firefox", + "durationMs": 665, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "firefox", + "durationMs": 578, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: shared setting mount preserves the surviving instance and later user changes", + "browser": "webkit", + "durationMs": 1376, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "webkit", + "durationMs": 1484, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "webkit", + "durationMs": 1663, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy clears active send timer and leaves another mounted instance usable", + "browser": "webkit", + "durationMs": 2226, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "webkit", + "durationMs": 1716, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "webkit", + "durationMs": 1444, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "webkit", + "durationMs": 1704, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "webkit", + "durationMs": 1955, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "published", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: shared setting mount preserves the surviving instance and later user changes", + "browser": "webkit", + "durationMs": 1453, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: invalid setting mount rolls back plugin resources while preserving the original error", + "browser": "webkit", + "durationMs": 1418, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy cancels pending setting send without clearing retained input or starting a lock", + "browser": "webkit", + "durationMs": 1921, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "pending", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "PENDING", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy clears active send timer and leaves another mounted instance usable", + "browser": "webkit", + "durationMs": 2326, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "LOCK", + "receiver": true + }, + { + "index": 1, + "text": "SECOND", + "receiver": true + } + ], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: rejected setting callback retains input and original console error without an unhandled rejection", + "browser": "webkit", + "durationMs": 1618, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "reject", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [ + { + "index": 0, + "text": "REJECT", + "receiver": true + } + ], + "messages": [], + "errors": [ + [ + "Error emitting danmuku:", + "controlled setting rejection" + ] + ], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: destroy false releases actual renderer nodes while retaining the player shell", + "browser": "webkit", + "durationMs": 1651, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "VISIBLE" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: replacement load removes a moved renderer node and preserves unrelated body content", + "browser": "webkit", + "durationMs": 1669, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "normal", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + } + ], + "sends": [], + "messages": [ + "MOVED" + ], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "candidate: narrow heatmap invalid sampling stays responsive and two instances have independent gradients", + "browser": "webkit", + "durationMs": 2049, + "observations": [ + { + "name": "danmuku-resources-inputs", + "value": { + "core": "candidate", + "scenario": "heatmap", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "native": "video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock" + } + }, + { + "name": "danmuku-resources-heatmap", + "value": { + "ids": [ + "heatmap-solids", + "heatmap-solids-1" + ], + "fills": [ + "url(#heatmap-solids)", + "url(#heatmap-solids-1)" + ], + "offsets": [ + "25%", + "75%" + ], + "validPaths": true + } + }, + { + "name": "danmuku-resources-evidence", + "value": { + "workers": [ + { + "terminated": 1 + }, + { + "terminated": 1 + } + ], + "sends": [], + "messages": [], + "errors": [], + "retainedSettings": 0, + "timers": 0 + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "chromium", + "durationMs": 2251, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "chromium", + "durationMs": 509, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.127297 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/c8dbfeb9-b569-4f9d-8673-c184346c7ca1", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "chromium", + "durationMs": 465, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.117584 + }, + { + "index": 0, + "event": "stop", + "time": 0.117584 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/f075b93a-8ad2-458a-b058-e86770cff0cc", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2806, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2446, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2325, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2304, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "chromium", + "durationMs": 1307, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "chromium", + "durationMs": 1615, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "chromium", + "durationMs": 2285, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 604.256591796875, + "next": 604.256591796875, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 202, + "before": 0.028137, + "after": 0.028137, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 218.10000002384186, + "before": 0.044278, + "after": 0.044278, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 234.69999998807907, + "before": 0.06098, + "after": 0.06098, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 251.39999997615814, + "before": 0.077651, + "after": 0.077651, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 268.39999997615814, + "before": 0.094642, + "after": 0.094642, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 284.60000002384186, + "before": 0.110876, + "after": 0.110876, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 301.60000002384186, + "before": 0.127934, + "after": 0.127934, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 317.80000001192093, + "before": 0.144083, + "after": 0.144083, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 335.30000001192093, + "before": 0.161599, + "after": 0.161599, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 351.39999997615814, + "before": 0.177664, + "after": 0.177664, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 368.10000002384186, + "before": 0.19445, + "after": 0.19445, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 384.5, + "before": 0.210711, + "after": 0.210711, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 401.60000002384186, + "before": 0.227853, + "after": 0.227853, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 418.19999998807907, + "before": 0.244469, + "after": 0.244469, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 434.69999998807907, + "before": 0.261009, + "after": 0.261009, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 451.10000002384186, + "before": 0.27739, + "after": 0.27739, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 468.10000002384186, + "before": 0.294431, + "after": 0.294431, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 484.60000002384186, + "before": 0.310881, + "after": 0.310881, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 501.39999997615814, + "before": 0.327649, + "after": 0.327649, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 517.5, + "before": 0.343747, + "after": 0.343747, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 534.6999999880791, + "before": 0.360918, + "after": 0.360918, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 551, + "before": 0.377269, + "after": 0.377269, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 568.6999999880791, + "before": 0.394967, + "after": 0.394967, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 585.1999999880791, + "before": 0.411443, + "after": 0.411443, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 601.3999999761581, + "before": 0.427627, + "after": 0.427627, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 618.3000000119209, + "before": 0.444613, + "after": 0.444613, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 634.8000000119209, + "before": 0.461067, + "after": 0.461067, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 651.6999999880791, + "before": 0.477888, + "after": 0.477888, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 667.6000000238419, + "before": 0.493967, + "after": 0.493967, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 684.6000000238419, + "before": 0.510902, + "after": 0.510902, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 700.8000000119209, + "before": 0.527071, + "after": 0.527071, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 718, + "before": 0.544217, + "after": 0.544217, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 734.6999999880791, + "before": 0.560952, + "after": 0.560952, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 751.3999999761581, + "before": 0.577751, + "after": 0.577751, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 768, + "before": 0.59426, + "after": 0.59426, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 784.1999999880791, + "before": 0.610559, + "after": 0.610559, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 801, + "before": 0.627275, + "after": 0.627275, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 817.8000000119209, + "before": 0.64407, + "after": 0.64407, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 835.3000000119209, + "before": 0.661569, + "after": 0.661569, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 851.3999999761581, + "before": 0.677686, + "after": 0.677686, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 867.6000000238419, + "before": 0.693949, + "after": 0.693949, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 884.6999999880791, + "before": 0.710978, + "after": 0.710978, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 900.8000000119209, + "before": 0.727021, + "after": 0.727021, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 918.1000000238419, + "before": 0.74433, + "after": 0.74433, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 935.6999999880791, + "before": 0.762034, + "after": 0.762034, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 951.8000000119209, + "before": 0.778045, + "after": 0.778045, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 969.1999999880791, + "before": 0.795544, + "after": 0.795544, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 985, + "before": 0.811225, + "after": 0.811225, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1000.6999999880791, + "before": 0.827061, + "after": 0.827061, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1017.5, + "before": 0.843785, + "after": 0.843785, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1034.1000000238419, + "before": 0.860393, + "after": 0.860393, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1050.699999988079, + "before": 0.877015, + "after": 0.877015, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1067.6000000238419, + "before": 0.89376, + "after": 0.89376, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1084.1000000238419, + "before": 0.910414, + "after": 0.910414, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1318.6000000238419, + "before": 2.058762, + "after": 2.058762, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1334.5, + "before": 2.090708, + "after": 2.090708, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1351.1000000238419, + "before": 2.123908, + "after": 2.123908, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1368.3999999761581, + "before": 2.158572, + "after": 2.158572, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1384.6000000238419, + "before": 2.190864, + "after": 2.190864, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1401.5, + "before": 2.224562, + "after": 2.224562, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1418.199999988079, + "before": 2.257988, + "after": 2.257988, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1435.300000011921, + "before": 2.292476, + "after": 2.292476, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1450.6000000238419, + "before": 2.322988, + "after": 2.322988, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1467.6000000238419, + "before": 2.356758, + "after": 2.356758, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1484.6000000238419, + "before": 2.39094, + "after": 2.39094, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1501.8999999761581, + "before": 2.425532, + "after": 2.425532, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1518, + "before": 2.457736, + "after": 2.457736, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1534.300000011921, + "before": 2.490356, + "after": 2.490356, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1551.3999999761581, + "before": 2.524436, + "after": 2.524436, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1567.8999999761581, + "before": 2.557588, + "after": 2.557588, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1585.300000011921, + "before": 2.592278, + "after": 2.592278, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1601.300000011921, + "before": 2.624364, + "after": 2.624364, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1617.699999988079, + "before": 2.657112, + "after": 2.657112, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1634.5, + "before": 2.690686, + "after": 2.690686, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1651.1000000238419, + "before": 2.723866, + "after": 2.723866, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1668.199999988079, + "before": 2.758048, + "after": 2.758048, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1684.699999988079, + "before": 2.790942, + "after": 2.790942, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1701.6000000238419, + "before": 2.824994, + "after": 2.824994, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1717.800000011921, + "before": 2.857306, + "after": 2.857306, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1734.5, + "before": 2.890862, + "after": 2.890862, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1751.3999999761581, + "before": 2.924516, + "after": 2.924516, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1767.8999999761581, + "before": 2.957498, + "after": 2.957498, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1785, + "before": 2.991774, + "after": 2.991774, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1801.199999988079, + "before": 3.024064, + "after": 3.024064, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1817.800000011921, + "before": 3.057186, + "after": 3.057186, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1834.800000011921, + "before": 3.091192, + "after": 3.091192, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1851.1000000238419, + "before": 3.123786, + "after": 3.123786, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1868.1000000238419, + "before": 3.157848, + "after": 3.157848, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1884.300000011921, + "before": 3.19047, + "after": 3.19047, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1901, + "before": 3.223686, + "after": 3.223686, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "wait" + } + ] + }, + { + "wall": 1918.699999988079, + "before": 3.258926, + "after": 3.258926, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "emit" + } + ] + }, + { + "wall": 1935.1000000238419, + "before": 3.29187, + "after": 3.29187, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "emit" + } + ] + }, + { + "wall": 1951.199999988079, + "before": 3.324002, + "after": 3.324002, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "emit" + } + ] + }, + { + "wall": 1967.5, + "before": 3.356732, + "after": 3.356732, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "emit" + } + ] + }, + { + "wall": 1984.199999988079, + "before": 3.39014, + "after": 3.39014, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "emit" + } + ] + }, + { + "wall": 2000.800000011921, + "before": 3.42331, + "after": 3.42331, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "emit" + } + ] + }, + { + "wall": 2018.5, + "before": 3.45869, + "after": 3.45869, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "emit" + } + ] + }, + { + "wall": 2034.800000011921, + "before": 3.491362, + "after": 3.491362, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "emit" + } + ] + }, + { + "wall": 2051, + "before": 3.523772, + "after": 3.523772, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "emit" + } + ] + }, + { + "wall": 2068.600000023842, + "before": 3.5591, + "after": 3.5591, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "emit" + } + ] + }, + { + "wall": 2084.300000011921, + "before": 3.590198, + "after": 3.590198, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.295942, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "chromium", + "durationMs": 4072, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "chromium", + "durationMs": 2330, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "chromium", + "durationMs": 458, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.126043 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/601dd5f3-19b3-4e8d-9117-c90a52f83c4d", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "chromium", + "durationMs": 487, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.121592 + }, + { + "index": 0, + "event": "stop", + "time": 0.121592 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/9f10d7a4-798f-4a3b-b786-a8fe0b8e3f2b", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2657, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2443, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2341, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "chromium", + "durationMs": 2317, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "chromium", + "durationMs": 1336, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "chromium", + "durationMs": 1435, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "chromium", + "durationMs": 2393, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 602.7938842773438, + "next": 602.7938842773438, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 235.5, + "before": 0.026708, + "after": 0.026708, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 250, + "before": 0.041245, + "after": 0.041245, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 267.10000002384186, + "before": 0.058347, + "after": 0.058347, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 283.30000001192093, + "before": 0.074521, + "after": 0.074521, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 300.10000002384186, + "before": 0.091309, + "after": 0.091309, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 317.80000001192093, + "before": 0.109032, + "after": 0.109032, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 333.5, + "before": 0.124705, + "after": 0.124705, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 349.4000000357628, + "before": 0.140706, + "after": 0.140706, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 366.7000000476837, + "before": 0.157951, + "after": 0.157951, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 383.80000001192093, + "before": 0.174993, + "after": 0.174993, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 399.9000000357628, + "before": 0.191129, + "after": 0.191129, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 417.4000000357628, + "before": 0.208663, + "after": 0.208663, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 433.4000000357628, + "before": 0.224723, + "after": 0.224723, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 450.2000000476837, + "before": 0.241454, + "after": 0.241454, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 466.5, + "before": 0.257725, + "after": 0.257725, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 483.60000002384186, + "before": 0.274945, + "after": 0.274945, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 500.4000000357628, + "before": 0.29159, + "after": 0.29159, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 516.8000000119209, + "before": 0.308111, + "after": 0.308111, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 533.1000000238419, + "before": 0.324415, + "after": 0.324415, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 550.2000000476837, + "before": 0.341463, + "after": 0.341463, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 566.7000000476837, + "before": 0.357949, + "after": 0.357949, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 583.5, + "before": 0.374721, + "after": 0.374721, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 599.5, + "before": 0.390802, + "after": 0.390802, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 616.7000000476837, + "before": 0.407993, + "after": 0.407993, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 634.4000000357628, + "before": 0.425591, + "after": 0.425591, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 651, + "before": 0.442194, + "after": 0.442194, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 667.3000000119209, + "before": 0.458565, + "after": 0.458565, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 683.3000000119209, + "before": 0.474523, + "after": 0.474523, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 700.4000000357628, + "before": 0.491631, + "after": 0.491631, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 717, + "before": 0.508166, + "after": 0.508166, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 733.8000000119209, + "before": 0.524991, + "after": 0.524991, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 749.8000000119209, + "before": 0.541089, + "after": 0.541089, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 767.7000000476837, + "before": 0.558902, + "after": 0.558902, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 782.9000000357628, + "before": 0.574146, + "after": 0.574146, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 799.9000000357628, + "before": 0.591214, + "after": 0.591214, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 817.6000000238419, + "before": 0.608833, + "after": 0.608833, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 833.5, + "before": 0.624826, + "after": 0.624826, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 850.1000000238419, + "before": 0.641361, + "after": 0.641361, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 866.4000000357628, + "before": 0.657634, + "after": 0.657634, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 883.1000000238419, + "before": 0.67435, + "after": 0.67435, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 899.9000000357628, + "before": 0.691145, + "after": 0.691145, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 917.4000000357628, + "before": 0.708644, + "after": 0.708644, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 933.5, + "before": 0.724761, + "after": 0.724761, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 949.8000000119209, + "before": 0.741035, + "after": 0.741035, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 966.8000000119209, + "before": 0.758053, + "after": 0.758053, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 982.9000000357628, + "before": 0.774109, + "after": 0.774109, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1000.1000000238419, + "before": 0.791404, + "after": 0.791404, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1017, + "before": 0.808269, + "after": 0.808269, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1033.9000000357628, + "before": 0.82512, + "after": 0.82512, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1051.4000000357628, + "before": 0.842619, + "after": 0.842619, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1067, + "before": 0.8583, + "after": 0.8583, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1082.9000000357628, + "before": 0.874136, + "after": 0.874136, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1100.5, + "before": 0.891826, + "after": 0.891826, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1116.300000011921, + "before": 0.907467, + "after": 0.907467, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1132.9000000357628, + "before": 0.92409, + "after": 0.92409, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1366.6000000238419, + "before": 2.058032, + "after": 2.058032, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1383.6000000238419, + "before": 2.091932, + "after": 2.091932, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1400.4000000357628, + "before": 2.125586, + "after": 2.125586, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1416.6000000238419, + "before": 2.15793, + "after": 2.15793, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1433.2000000476837, + "before": 2.191214, + "after": 2.191214, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1450.5, + "before": 2.225856, + "after": 2.225856, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1466.7000000476837, + "before": 2.25814, + "after": 2.25814, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1483.6000000238419, + "before": 2.291868, + "after": 2.291868, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1500.2000000476837, + "before": 2.325294, + "after": 2.325294, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1517.5, + "before": 2.359784, + "after": 2.359784, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1533.800000011921, + "before": 2.392344, + "after": 2.392344, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1550.4000000357628, + "before": 2.425586, + "after": 2.425586, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1567, + "before": 2.458876, + "after": 2.458876, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1584, + "before": 2.492838, + "after": 2.492838, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1600.9000000357628, + "before": 2.526638, + "after": 2.526638, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1616.5, + "before": 2.55769, + "after": 2.55769, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1633.4000000357628, + "before": 2.5916, + "after": 2.5916, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1650, + "before": 2.624872, + "after": 2.624872, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1667.4000000357628, + "before": 2.659554, + "after": 2.659554, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1683.4000000357628, + "before": 2.691672, + "after": 2.691672, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1699.800000011921, + "before": 2.72442, + "after": 2.72442, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1716.6000000238419, + "before": 2.758004, + "after": 2.758004, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1733.2000000476837, + "before": 2.791104, + "after": 2.791104, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1751.2000000476837, + "before": 2.827378, + "after": 2.827378, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1766.7000000476837, + "before": 2.858224, + "after": 2.858224, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1783.800000011921, + "before": 2.8923, + "after": 2.8923, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1800.7000000476837, + "before": 2.92638, + "after": 2.92638, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1816.7000000476837, + "before": 2.958224, + "after": 2.958224, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1833.5, + "before": 2.991864, + "after": 2.991864, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1849.6000000238419, + "before": 3.023954, + "after": 3.023954, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1867.1000000238419, + "before": 3.059088, + "after": 3.059088, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1883.300000011921, + "before": 3.091478, + "after": 3.091478, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1899.800000011921, + "before": 3.12441, + "after": 3.12441, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1916.9000000357628, + "before": 3.158468, + "after": 3.158468, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1933.2000000476837, + "before": 3.191156, + "after": 3.191156, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1950.2000000476837, + "before": 3.225154, + "after": 3.225154, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "wait" + } + ] + }, + { + "wall": 1966.7000000476837, + "before": 3.25817, + "after": 3.25817, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + }, + { + "wall": 1983.2000000476837, + "before": 3.291098, + "after": 3.291098, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + }, + { + "wall": 1999.7000000476837, + "before": 3.32421, + "after": 3.32421, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + }, + { + "wall": 2017.2000000476837, + "before": 3.359176, + "after": 3.359176, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + }, + { + "wall": 2033.300000011921, + "before": 3.391276, + "after": 3.391276, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + }, + { + "wall": 2049.600000023842, + "before": 3.424038, + "after": 3.424038, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + }, + { + "wall": 2066.400000035763, + "before": 3.457494, + "after": 3.457494, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + }, + { + "wall": 2082.900000035763, + "before": 3.490616, + "after": 3.490616, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + }, + { + "wall": 2100.5, + "before": 3.525968, + "after": 3.525968, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + }, + { + "wall": 2116.900000035763, + "before": 3.558646, + "after": 3.558646, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + }, + { + "wall": 2133.100000023842, + "before": 3.591178, + "after": 3.591178, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + }, + { + "wall": 2150, + "before": 3.62481, + "after": 3.62481, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.302252, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "chromium", + "durationMs": 4228, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "firefox", + "durationMs": 2461, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "firefox", + "durationMs": 815, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.091192 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/a1f29cd3-7eb6-46eb-b95f-6fa89cefb32c", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "firefox", + "durationMs": 665, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.129594 + }, + { + "index": 0, + "event": "stop", + "time": 0.129594 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/8f0a53df-6b15-411e-8917-37c0190afd78", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2987, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2608, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2750, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2445, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "firefox", + "durationMs": 2123, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "firefox", + "durationMs": 1637, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "firefox", + "durationMs": 2618, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 602.7999877929688, + "next": 602.7999877929688, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 394, + "before": 0.008572, + "after": 0.008572, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 411, + "before": 0.008572, + "after": 0.008572, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 428, + "before": 0.049185, + "after": 0.049185, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 445, + "before": 0.049185, + "after": 0.049185, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 461, + "before": 0.049185, + "after": 0.049185, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 478, + "before": 0.091367, + "after": 0.091367, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 494, + "before": 0.091367, + "after": 0.091367, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 511, + "before": 0.131691, + "after": 0.131691, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 527, + "before": 0.131691, + "after": 0.131691, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 545, + "before": 0.131691, + "after": 0.131691, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 561, + "before": 0.172888, + "after": 0.172888, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 578, + "before": 0.172888, + "after": 0.172888, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 594, + "before": 0.213061, + "after": 0.213061, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 612, + "before": 0.213061, + "after": 0.213061, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 628, + "before": 0.253323, + "after": 0.253323, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 644, + "before": 0.253323, + "after": 0.253323, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 661, + "before": 0.253323, + "after": 0.253323, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 677, + "before": 0.293844, + "after": 0.293844, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 694, + "before": 0.293844, + "after": 0.293844, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 711, + "before": 0.334258, + "after": 0.334258, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 728, + "before": 0.334258, + "after": 0.334258, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 744, + "before": 0.334258, + "after": 0.334258, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 761, + "before": 0.374999, + "after": 0.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 779, + "before": 0.374999, + "after": 0.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 795, + "before": 0.417383, + "after": 0.417383, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 812, + "before": 0.417383, + "after": 0.417383, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 829, + "before": 0.417383, + "after": 0.417383, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 844, + "before": 0.458332, + "after": 0.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 861, + "before": 0.458332, + "after": 0.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 878, + "before": 0.499999, + "after": 0.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 895, + "before": 0.499999, + "after": 0.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 911, + "before": 0.499999, + "after": 0.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 928, + "before": 0.541666, + "after": 0.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 945, + "before": 0.541666, + "after": 0.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 961, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 978, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 995, + "before": 0.583332, + "after": 0.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1012, + "before": 0.624999, + "after": 0.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1028, + "before": 0.624999, + "after": 0.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1045, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1061, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1078, + "before": 0.666666, + "after": 0.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1094, + "before": 0.708332, + "after": 0.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1111, + "before": 0.708332, + "after": 0.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1128, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1144, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1161, + "before": 0.749999, + "after": 0.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1178, + "before": 0.790551, + "after": 0.790551, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1195, + "before": 0.790551, + "after": 0.790551, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1211, + "before": 0.833332, + "after": 0.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1228, + "before": 0.833332, + "after": 0.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1245, + "before": 0.833332, + "after": 0.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1262, + "before": 0.874069, + "after": 0.874069, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1278, + "before": 0.874069, + "after": 0.874069, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1295, + "before": 0.916666, + "after": 0.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1311, + "before": 0.916666, + "after": 0.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1329, + "before": 0.916666, + "after": 0.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1495, + "before": 2.000062, + "after": 2.000062, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1512, + "before": 2.041666, + "after": 2.041666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1528, + "before": 2.083332, + "after": 2.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1545, + "before": 2.083332, + "after": 2.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1562, + "before": 2.124999, + "after": 2.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1579, + "before": 2.166666, + "after": 2.166666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1596, + "before": 2.208332, + "after": 2.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1612, + "before": 2.249999, + "after": 2.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1629, + "before": 2.249999, + "after": 2.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1645, + "before": 2.291666, + "after": 2.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1661, + "before": 2.333332, + "after": 2.333332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1678, + "before": 2.374999, + "after": 2.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1695, + "before": 2.416666, + "after": 2.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1711, + "before": 2.416666, + "after": 2.416666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1728, + "before": 2.458332, + "after": 2.458332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1745, + "before": 2.499999, + "after": 2.499999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1762, + "before": 2.541666, + "after": 2.541666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1779, + "before": 2.583332, + "after": 2.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1796, + "before": 2.583332, + "after": 2.583332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1813, + "before": 2.624999, + "after": 2.624999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1829, + "before": 2.666666, + "after": 2.666666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1845, + "before": 2.708332, + "after": 2.708332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1862, + "before": 2.749999, + "after": 2.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1878, + "before": 2.749999, + "after": 2.749999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1895, + "before": 2.791666, + "after": 2.791666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1912, + "before": 2.833332, + "after": 2.833332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1929, + "before": 2.874999, + "after": 2.874999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1945, + "before": 2.916666, + "after": 2.916666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1962, + "before": 2.958332, + "after": 2.958332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1979, + "before": 2.958332, + "after": 2.958332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 1995, + "before": 2.999999, + "after": 2.999999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 2012, + "before": 3.041666, + "after": 3.041666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 2029, + "before": 3.083332, + "after": 3.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 2045, + "before": 3.083332, + "after": 3.083332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 2062, + "before": 3.124999, + "after": 3.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 2079, + "before": 3.166666, + "after": 3.166666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "wait" + } + ] + }, + { + "wall": 2095, + "before": 3.208332, + "after": 3.208332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2112, + "before": 3.249999, + "after": 3.249999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2130, + "before": 3.291427, + "after": 3.291427, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2146, + "before": 3.291427, + "after": 3.291427, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2162, + "before": 3.332417, + "after": 3.332417, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2179, + "before": 3.372611, + "after": 3.372611, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2196, + "before": 3.413454, + "after": 3.413454, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2212, + "before": 3.454571, + "after": 3.454571, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2229, + "before": 3.454571, + "after": 3.454571, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2247, + "before": 3.495006, + "after": 3.495006, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2262, + "before": 3.535452, + "after": 3.535452, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2279, + "before": 3.576684, + "after": 3.576684, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + }, + { + "wall": 2297, + "before": 3.617044, + "after": 3.617044, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.249999, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "firefox", + "durationMs": 4553, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "firefox", + "durationMs": 2536, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "firefox", + "durationMs": 635, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.12931 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/bfa91bcd-be2f-4c95-b86a-38fdefe13574", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "firefox", + "durationMs": 724, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.127259 + }, + { + "index": 0, + "event": "stop", + "time": 0.127259 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/baac5548-4832-4c4c-8e35-7b8b06f23f7d", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2981, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2644, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2753, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "firefox", + "durationMs": 2488, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "firefox", + "durationMs": 2045, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "firefox", + "durationMs": 1565, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "firefox", + "durationMs": 2652, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 602.7999877929688, + "next": 602.7999877929688, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 409, + "before": 0.007043, + "after": 0.007043, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 426, + "before": 0.007043, + "after": 0.007043, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 442, + "before": 0.047447, + "after": 0.047447, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 459, + "before": 0.047447, + "after": 0.047447, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 475, + "before": 0.047447, + "after": 0.047447, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 493, + "before": 0.088696, + "after": 0.088696, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 509, + "before": 0.088696, + "after": 0.088696, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 526, + "before": 0.129415, + "after": 0.129415, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 543, + "before": 0.129415, + "after": 0.129415, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 559, + "before": 0.169573, + "after": 0.169573, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 576, + "before": 0.169573, + "after": 0.169573, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 592, + "before": 0.169573, + "after": 0.169573, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 609, + "before": 0.210857, + "after": 0.210857, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 626, + "before": 0.210857, + "after": 0.210857, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 643, + "before": 0.251096, + "after": 0.251096, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 660, + "before": 0.251096, + "after": 0.251096, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 676, + "before": 0.251096, + "after": 0.251096, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 693, + "before": 0.291666, + "after": 0.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 710, + "before": 0.291666, + "after": 0.291666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 726, + "before": 0.334332, + "after": 0.334332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 744, + "before": 0.334332, + "after": 0.334332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 759, + "before": 0.334332, + "after": 0.334332, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 775, + "before": 0.374999, + "after": 0.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 793, + "before": 0.374999, + "after": 0.374999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 809, + "before": 0.41594, + "after": 0.41594, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 826, + "before": 0.41594, + "after": 0.41594, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 843, + "before": 0.41594, + "after": 0.41594, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 859, + "before": 0.456108, + "after": 0.456108, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 876, + "before": 0.456108, + "after": 0.456108, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 893, + "before": 0.496281, + "after": 0.496281, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 909, + "before": 0.496281, + "after": 0.496281, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 926, + "before": 0.536452, + "after": 0.536452, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 943, + "before": 0.536452, + "after": 0.536452, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 960, + "before": 0.536452, + "after": 0.536452, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 976, + "before": 0.576857, + "after": 0.576857, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 993, + "before": 0.576857, + "after": 0.576857, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1009, + "before": 0.617363, + "after": 0.617363, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1026, + "before": 0.617363, + "after": 0.617363, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1043, + "before": 0.617363, + "after": 0.617363, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1059, + "before": 0.657748, + "after": 0.657748, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1077, + "before": 0.657748, + "after": 0.657748, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1093, + "before": 0.698341, + "after": 0.698341, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1109, + "before": 0.698341, + "after": 0.698341, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1126, + "before": 0.698341, + "after": 0.698341, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1142, + "before": 0.738592, + "after": 0.738592, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1159, + "before": 0.738592, + "after": 0.738592, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1177, + "before": 0.778803, + "after": 0.778803, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1193, + "before": 0.778803, + "after": 0.778803, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1209, + "before": 0.819264, + "after": 0.819264, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1226, + "before": 0.819264, + "after": 0.819264, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1243, + "before": 0.819264, + "after": 0.819264, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1259, + "before": 0.859615, + "after": 0.859615, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1276, + "before": 0.859615, + "after": 0.859615, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1293, + "before": 0.900074, + "after": 0.900074, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1309, + "before": 0.900074, + "after": 0.900074, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1328, + "before": 0.900074, + "after": 0.900074, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1345, + "before": 0.940379, + "after": 0.940379, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1510, + "before": 2.00006, + "after": 2.00006, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1526, + "before": 2.041666, + "after": 2.041666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1543, + "before": 2.082755, + "after": 2.082755, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1560, + "before": 2.082755, + "after": 2.082755, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1577, + "before": 2.124999, + "after": 2.124999, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1593, + "before": 2.166666, + "after": 2.166666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1609, + "before": 2.207892, + "after": 2.207892, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1627, + "before": 2.249088, + "after": 2.249088, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1644, + "before": 2.290002, + "after": 2.290002, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1660, + "before": 2.290002, + "after": 2.290002, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1676, + "before": 2.330662, + "after": 2.330662, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1694, + "before": 2.371182, + "after": 2.371182, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1710, + "before": 2.411494, + "after": 2.411494, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "wait" + } + ] + }, + { + "wall": 1727, + "before": 2.452702, + "after": 2.452702, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1743, + "before": 2.452702, + "after": 2.452702, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1760, + "before": 2.493385, + "after": 2.493385, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1776, + "before": 2.534337, + "after": 2.534337, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1794, + "before": 2.574772, + "after": 2.574772, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1810, + "before": 2.615084, + "after": 2.615084, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1826, + "before": 2.65539, + "after": 2.65539, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1843, + "before": 2.65539, + "after": 2.65539, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1860, + "before": 2.696335, + "after": 2.696335, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1877, + "before": 2.736909, + "after": 2.736909, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1895, + "before": 2.777629, + "after": 2.777629, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + } + ] + }, + { + "wall": 1910, + "before": 2.81879, + "after": 2.81879, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 1927, + "before": 2.81879, + "after": 2.81879, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 1944, + "before": 2.859895, + "after": 2.859895, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 1960, + "before": 2.900599, + "after": 2.900599, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 1977, + "before": 2.941677, + "after": 2.941677, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 1993, + "before": 2.982401, + "after": 2.982401, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 2011, + "before": 3.023225, + "after": 3.023225, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 2027, + "before": 3.023225, + "after": 3.023225, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 2044, + "before": 3.064223, + "after": 3.064223, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 2060, + "before": 3.104946, + "after": 3.104946, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 2077, + "before": 3.145078, + "after": 3.145078, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 2094, + "before": 3.186021, + "after": 3.186021, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "wait" + } + ] + }, + { + "wall": 2110, + "before": 3.226477, + "after": 3.226477, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2127, + "before": 3.226477, + "after": 3.226477, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2143, + "before": 3.267558, + "after": 3.267558, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2160, + "before": 3.308393, + "after": 3.308393, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2178, + "before": 3.349575, + "after": 3.349575, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2194, + "before": 3.38981, + "after": 3.38981, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2211, + "before": 3.38981, + "after": 3.38981, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2227, + "before": 3.430954, + "after": 3.430954, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2243, + "before": 3.471493, + "after": 3.471493, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2260, + "before": 3.511971, + "after": 3.511971, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2277, + "before": 3.553164, + "after": 3.553164, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2293, + "before": 3.553164, + "after": 3.553164, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2310, + "before": 3.594176, + "after": 3.594176, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + }, + { + "wall": 2327, + "before": 3.634823, + "after": 3.634823, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5, + "state": "emit" + }, + { + "id": "appended", + "time": 3.277629, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "firefox", + "durationMs": 4437, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "webkit", + "durationMs": 3922, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "webkit", + "durationMs": 1738, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.1411414 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/8a9ddc76-60b6-4b13-a290-90d9e1982c81", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "webkit", + "durationMs": 1697, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.1457316 + }, + { + "index": 0, + "event": "stop", + "time": 0.145779 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/cee08f5f-60ee-43d6-9d8b-d2bcc93bd283", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4351, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 3854, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4215, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 3498, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "webkit", + "durationMs": 2408, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "webkit", + "durationMs": 3121, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "webkit", + "durationMs": 4145, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 617.2000122070312, + "next": 617.2000122070312, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 1054, + "before": 0.0228873, + "after": 0.0229379, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1100, + "before": 0.0693917, + "after": 0.0694077, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1131, + "before": 0.1002055, + "after": 0.1002208, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1177, + "before": 0.1462612, + "after": 0.1462737, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1241, + "before": 0.2097266, + "after": 0.2097338, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1285, + "before": 0.2544607, + "after": 0.2544722, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1318, + "before": 0.2868073, + "after": 0.2868385, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1348, + "before": 0.3166545, + "after": 0.3166794, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1393, + "before": 0.3623165, + "after": 0.3623328, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1424, + "before": 0.3925826, + "after": 0.3926047, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1471, + "before": 0.4394969, + "after": 0.4395227, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1518, + "before": 0.4873621, + "after": 0.4873729, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1549, + "before": 0.5177579, + "after": 0.5177702, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1596, + "before": 0.5648463, + "after": 0.5648637, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1660, + "before": 0.6284954, + "after": 0.6285142, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1703, + "before": 0.6718221, + "after": 0.671831, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1749, + "before": 0.7182764, + "after": 0.7182861, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1795, + "before": 0.7635975, + "after": 0.7636117, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1856, + "before": 0.8249366, + "after": 0.8249459, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1858, + "before": 0.8268354, + "after": 0.8268454, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1859, + "before": 0.8280751, + "after": 0.8280813, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2332, + "before": 2.0400136, + "after": 2.0400314, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "wait" + } + ] + }, + { + "wall": 2379, + "before": 2.1346078, + "after": 2.1346342, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "wait" + } + ] + }, + { + "wall": 2425, + "before": 2.226155, + "after": 2.2262092, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "wait" + } + ] + }, + { + "wall": 2471, + "before": 2.3174384, + "after": 2.3174666, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "wait" + } + ] + }, + { + "wall": 2516, + "before": 2.408291, + "after": 2.4083306, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "wait" + } + ] + }, + { + "wall": 2547, + "before": 2.4707746, + "after": 2.4708086, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + } + ] + }, + { + "wall": 2579, + "before": 2.5343928, + "after": 2.534417, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + } + ] + }, + { + "wall": 2608, + "before": 2.592877, + "after": 2.5928988, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + } + ] + }, + { + "wall": 2640, + "before": 2.6564802, + "after": 2.6565346, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + } + ] + }, + { + "wall": 2685, + "before": 2.7468996, + "after": 2.7469786, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + } + ] + }, + { + "wall": 2731, + "before": 2.8381784, + "after": 2.8382078, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + } + ] + }, + { + "wall": 2777, + "before": 2.9304642, + "after": 2.930489, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "wait" + } + ] + }, + { + "wall": 2839, + "before": 3.0541652, + "after": 3.0542004, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "wait" + } + ] + }, + { + "wall": 2883, + "before": 3.1424126, + "after": 3.1424544, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "wait" + } + ] + }, + { + "wall": 2929, + "before": 3.234471, + "after": 3.2346206, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "wait" + } + ] + }, + { + "wall": 2990, + "before": 3.3562606, + "after": 3.356313, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "wait" + } + ] + }, + { + "wall": 3037, + "before": 3.4504656, + "after": 3.4504892, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3068, + "before": 3.5121512, + "after": 3.5121736, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3114, + "before": 3.6049912, + "after": 3.6050122, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3162, + "before": 3.6996648, + "after": 3.699698, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3223, + "before": 3.8211644, + "after": 3.8211812, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3268, + "before": 3.9124788, + "after": 3.9125026, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3314, + "before": 4.0041666, + "after": 4.0041886, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3377, + "before": 4.1305742, + "after": 4.1305928, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3422, + "before": 4.219931, + "after": 4.219959, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3467, + "before": 4.3107776, + "after": 4.3108004, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3529, + "before": 4.4336204, + "after": 4.4336678, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3531, + "before": 4.4389278, + "after": 4.4389514, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + }, + { + "wall": 3533, + "before": 4.4423304, + "after": 4.4423458, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.5012244, + "state": "emit" + }, + { + "id": "appended", + "time": 3.4145138, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "published / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "webkit", + "durationMs": 6333, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "published", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: accepted NaN timestamp keeps the historical current-time fallback", + "browser": "webkit", + "durationMs": 3961, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-numeric-compatibility", + "value": { + "expected": 0.5, + "inputTime": 0.5, + "queueTime": 0.5, + "isNaN": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous destroy from the error listener", + "browser": "webkit", + "durationMs": 1635, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "destroy", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": true, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.21373 + }, + { + "index": 0, + "event": "stop", + "time": 0 + }, + { + "index": 0, + "event": "destroy", + "time": 0 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/c5d5cb5e-6993-41a8-a80d-841e352f1bad", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: failed recovery respects synchronous stop from the error listener", + "browser": "webkit", + "durationMs": 1855, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-recovery-reentry", + "value": { + "action": "stop", + "sameOwner": true, + "rowState": "stop", + "isStop": true, + "isDestroy": false, + "events": [ + { + "index": 0, + "event": "error", + "time": 0.2149887 + }, + { + "index": 0, + "event": "stop", + "time": 0.2150355 + } + ], + "workers": [ + { + "url": "blob:http://127.0.0.1:8084/eb5b11b0-b297-4d8c-a0b4-f77a2bd67f2c", + "requests": [ + { + "type": "getDanmuTop", + "target": { + "mode": 1, + "height": 23, + "speed": 90 + }, + "visibles": [], + "antiOverlap": true, + "clientWidth": 640, + "clientHeight": 360, + "marginBottom": 10, + "marginTop": 10, + "id": 1 + } + ], + "replies": [ + { + "id": 1, + "result": 10 + } + ], + "terminated": 1, + "errors": [ + "controlled track failure" + ] + } + ], + "fault": "Actual video/DOM/track Worker; dispatch an explicit native ErrorEvent and inject a throwing createWorker recovery seam. This does not claim a real CSP or browser Worker construction failure." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4441, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "pause", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native reset cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 3752, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "reset", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native seek cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 4229, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "seek", + "before": { + "requests": [ + 1 + ], + "visible": [ + "fresh-after-cancel" + ] + }, + "after": [ + "fresh-after-cancel" + ], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native destroy cancels pending visibility without blocking later work", + "browser": "webkit", + "durationMs": 3748, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-native-cancel", + "value": { + "action": "destroy", + "before": { + "requests": [ + 0 + ], + "visible": [] + }, + "after": [], + "cancelledSource": "An unresolved user beforeVisible Promise; native RAF, Worker and media remain active." + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native Worker renders three modes with synchronous filter and asynchronous beforeVisible", + "browser": "webkit", + "durationMs": 2326, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "modes", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: six simultaneous fixed comments preserve usable track geometry and input order", + "browser": "webkit", + "durationMs": 3119, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "density", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-dense-tracks", + "value": [ + { + "id": "dense-0", + "mode": "1", + "state": "stop", + "text": "TRACK 0", + "x": 285, + "y": 10, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-1", + "mode": "1", + "state": "stop", + "text": "TRACK 1", + "x": 285, + "y": 33, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-2", + "mode": "1", + "state": "stop", + "text": "TRACK 2", + "x": 285, + "y": 56, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-3", + "mode": "1", + "state": "stop", + "text": "TRACK 3", + "x": 285, + "y": 79, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-4", + "mode": "1", + "state": "stop", + "text": "TRACK 4", + "x": 285, + "y": 102, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + }, + { + "id": "dense-5", + "mode": "1", + "state": "stop", + "text": "TRACK 5", + "x": 285, + "y": 125, + "width": 70, + "height": 22.5, + "visibility": "visible", + "display": "block", + "transition": "", + "opacity": "1" + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: native pause seek rate and public reset/load retain historical pool semantics", + "browser": "webkit", + "durationMs": 4584, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "playback", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + }, + { + "name": "danmuku-pool-and-rate", + "value": { + "frozen": { + "left": 587.1875, + "next": 587.1875, + "state": "stop" + }, + "reused": { + "poolReused": true, + "visibleSameNode": true, + "original": { + "id": "reused", + "state": "emit", + "visibility": "visible", + "text": "REUSED" + }, + "extraNodes": [], + "transition": "4s", + "rate": 2 + }, + "loadWithoutArgument": "replaces from original empty option; explicit array load appends" + } + }, + { + "name": "danmuku-native-eligibility", + "value": [ + { + "wall": 993, + "before": 0.0130648, + "after": 0.0131173, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1035, + "before": 0.0549802, + "after": 0.0549962, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1065, + "before": 0.0852038, + "after": 0.0852179, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1111, + "before": 0.1317353, + "after": 0.1317466, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1142, + "before": 0.1621851, + "after": 0.1621957, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1174, + "before": 0.1947158, + "after": 0.1947303, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1221, + "before": 0.2412238, + "after": 0.2412594, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1252, + "before": 0.2726183, + "after": 0.2726269, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1297, + "before": 0.3173417, + "after": 0.3173531, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1328, + "before": 0.3478769, + "after": 0.3478974, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1375, + "before": 0.3952643, + "after": 0.3952887, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1421, + "before": 0.4409978, + "after": 0.4410155, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [ + "moving" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + } + ] + }, + { + "wall": 1467, + "before": 0.4869792, + "after": 0.4869909, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1528, + "before": 0.5480809, + "after": 0.548092, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1558, + "before": 0.5783289, + "after": 0.5783385, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1604, + "before": 0.6246278, + "after": 0.6246348, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1636, + "before": 0.6561986, + "after": 0.6562097, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1681, + "before": 0.7013863, + "after": 0.7013971, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1727, + "before": 0.7470314, + "after": 0.7470497, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1774, + "before": 0.7941729, + "after": 0.7941857, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1834, + "before": 0.8540455, + "after": 0.8540573, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1865, + "before": 0.8856981, + "after": 0.8857096, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1910, + "before": 0.9299703, + "after": 0.9299837, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1940, + "before": 0.9605552, + "after": 0.9605703, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 1986, + "before": 1.0060919, + "after": 1.0061197, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2017, + "before": 1.0372431, + "after": 1.0372547, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2064, + "before": 1.0840408, + "after": 1.084052, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2110, + "before": 1.130107, + "after": 1.1301193, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2142, + "before": 1.1619106, + "after": 1.1619295, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2144, + "before": 1.1647283, + "after": 1.1647427, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2146, + "before": 1.1663142, + "after": 1.1663236, + "playing": true, + "paused": false, + "seeking": false, + "rate": 1, + "stop": false, + "generation": 3, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "emit" + } + ] + }, + { + "wall": 2663, + "before": 2.1330718, + "after": 2.1331416, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "wait" + } + ] + }, + { + "wall": 2723, + "before": 2.2532162, + "after": 2.253244, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "wait" + } + ] + }, + { + "wall": 2754, + "before": 2.3153652, + "after": 2.3154008, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "wait" + } + ] + }, + { + "wall": 2801, + "before": 2.4089448, + "after": 2.4090024, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "reused" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "wait" + } + ] + }, + { + "wall": 2831, + "before": 2.470887, + "after": 2.470951, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + } + ] + }, + { + "wall": 2878, + "before": 2.564194, + "after": 2.5642124, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + } + ] + }, + { + "wall": 2909, + "before": 2.6264638, + "after": 2.6264914, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + } + ] + }, + { + "wall": 2954, + "before": 2.716301, + "after": 2.7163208, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + } + ] + }, + { + "wall": 2985, + "before": 2.7779892, + "after": 2.7780118, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + } + ] + }, + { + "wall": 3031, + "before": 2.8703986, + "after": 2.8704264, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + } + ] + }, + { + "wall": 3076, + "before": 2.9598136, + "after": 2.959838, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + } + ] + }, + { + "wall": 3107, + "before": 3.021565, + "after": 3.0215818, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + } + ] + }, + { + "wall": 3139, + "before": 3.0859154, + "after": 3.0859368, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "wait" + } + ] + }, + { + "wall": 3183, + "before": 3.1738206, + "after": 3.1738396, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "wait" + } + ] + }, + { + "wall": 3213, + "before": 3.234496, + "after": 3.234554, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "wait" + } + ] + }, + { + "wall": 3259, + "before": 3.3260314, + "after": 3.3260916, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "wait" + } + ] + }, + { + "wall": 3289, + "before": 3.3864202, + "after": 3.3864458, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "wait" + } + ] + }, + { + "wall": 3335, + "before": 3.4788946, + "after": 3.4789168, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [ + "appended" + ], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "wait" + } + ] + }, + { + "wall": 3382, + "before": 3.5711118, + "after": 3.5711542, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3428, + "before": 3.6634264, + "after": 3.6634484, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3474, + "before": 3.7565586, + "after": 3.756593, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3505, + "before": 3.817457, + "after": 3.8174768, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3535, + "before": 3.8776714, + "after": 3.8776856, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3582, + "before": 3.9712334, + "after": 3.9712518, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3613, + "before": 4.0334472, + "after": 4.0334672, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3675, + "before": 4.15878, + "after": 4.1588092, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3736, + "before": 4.2795264, + "after": 4.2795428, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3835, + "before": 4.477656, + "after": 4.4777094, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3890, + "before": 4.5877412, + "after": 4.5877646, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3912, + "before": 4.6310494, + "after": 4.6310694, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + }, + { + "wall": 3913, + "before": 4.634418, + "after": 4.6344324, + "playing": true, + "paused": false, + "seeking": false, + "rate": 2, + "stop": false, + "generation": 6, + "fault": false, + "ready": [], + "queue": [ + { + "id": "moving", + "time": 0.5, + "state": "wait" + }, + { + "id": "reused", + "time": 2.503317, + "state": "emit" + }, + { + "id": "appended", + "time": 3.5450142, + "state": "emit" + } + ] + } + ] + } + ] + }, + { + "title": "candidate / Candidate Danmuku candidate: actual settings, asynchronous send lock, external fullscreen mount and two-player destruction", + "browser": "webkit", + "durationMs": 6606, + "observations": [ + { + "name": "danmuku-browser-inputs", + "value": { + "core": "candidate", + "plugin": "artplayer-plugin-danmuku", + "format": "candidate", + "artifact": "D:\\github\\ArtPlayer\\packages\\artplayer-plugin-danmuku\\dist\\artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "media": "/test/pattern.mp4", + "scenario": "settings", + "worker": "Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked", + "heatmap": false + } + } + ] + } + ] + } + ], + "sizes": [ + { + "file": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba", + "beforeBytes": 59100, + "afterBytes": 64909, + "beforeGzipBytes": 16799, + "afterGzipBytes": 18728 + }, + { + "file": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0", + "beforeBytes": 60243, + "afterBytes": 66169, + "beforeGzipBytes": 17160, + "afterGzipBytes": 19167 + }, + { + "file": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs", + "sha256": "964870b2f5f5bb282b5aec5fd5a793243c94ec1cfb36dadeb0fa410ce1345457", + "beforeBytes": 87065, + "afterBytes": 97753, + "beforeGzipBytes": 20852, + "afterGzipBytes": 23277 + } + ], + "checks": [ + { + "file": "refactor/.cache/danmuku05-build-final.log", + "sha256": "71e8f8b0a553acf7e227b7d0225eb2bece3beeef7d9c3ad2cf36ffd4d8995976" + }, + { + "file": "refactor/.cache/danmuku05-lint-final.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "file": "refactor/.cache/danmuku05-frozen-install.log", + "sha256": "c7cb9ac14616b0fc30f316ac79623c2a16e66e74bb7eb6d3e13c34c2f9197228" + }, + { + "file": "refactor/.cache/danmuku05-toolchain.log", + "sha256": "922a33e7f2330c880fd8fcaeb321618f116f6d048b7202cc977b163915ce94e4" + }, + { + "file": "refactor/.cache/danmuku05-typecheck-complete.log", + "sha256": "2b24379004c1f3ecba09c64eea5f02533c572273a5753017fea3be6d96a4878e" + } + ], + "nativeBefore": { + "file": "refactor/.cache/danmuku05-browser-before/report.json", + "sha256": "4b7c22630144eef4834d316e9175eb05e7faa160973afcd40d351235a4e4fe64", + "tests": 6, + "failed": 6, + "explanation": "Actual task04 main, two cores, Chromium: retained setting DOM, late clearing/lock, active send timer and retained renderer nodes." + }, + "initialMatrix": { + "file": "refactor/.cache/danmuku05-browser-source-initial-matrix/report.json", + "sha256": "f11721c3b599ab91c5d55f33ea3599d9da8424d7e6a779dd08cdede8960274e5", + "passed": 128, + "failed": 4, + "explanation": "Two WebKit timer cases counted unrelated one-second timers; observe the send continuation timer instead. Two lifecycle cases did not reach visibility eligibility; now explicitly seed ready only for cancellation/recovery cases. Original native clock/geometry tests remain. LOAD-01 tracks real frame/window evidence for task07." + }, + "fixtureCorrection": { + "file": "refactor/.cache/danmuku05-browser-source-ready-initial/report.json", + "sha256": "c4490f9c55e6bd3bb0db82e45841c41e792d86efc0937844a31b876da6b5ec43", + "passed": 125, + "failed": 7, + "explanation": "Seeding the obsolete item as ready changed its post-seek eligibility, reentering the unresolved gate (six failures); restore timestamp-based old item at native 0.25 playbackRate. NaN clock case also uses that real rate. Only recovery/new continuation items seed ready. No production change or timeout increase." + }, + "targetedFixtureValidation": { + "file": "refactor/.cache/danmuku05-browser-fixture-final/report.json", + "sha256": "20cd361d46b31577a0c3a607601604f9a6dccab181733ef36873a98057c3a550", + "tests": 42 + }, + "mainParallelAttempt": { + "file": "refactor/.cache/danmuku05-browser-main-initial-matrix/report.json", + "sha256": "4f2d60e48b2ec7b742e7c7709c9fd39fbf0e809413caad1d73c75a20848e6849", + "passed": 131, + "failed": 1, + "workers": 3, + "explanation": "Published-core WebKit pool/rate case never entered beforeVisible for reused, with no corresponding Worker request. The exact main matrix is rerun serially without assertion, timeout or production changes. LOAD-01 remains open: the original failure has no sampled scheduler/media clock proof and a later pass does not establish load stability." + }, + "mainSerialAttempt": { + "file": "refactor/.cache/danmuku05-browser-main-serial-before-clear/report.json", + "sha256": "ae1134ea44a7b84a5bd860d84ac43cdeecf1cc10fab0b3954eea65950eb655ef", + "passed": 128, + "failed": 4, + "workers": 1, + "explanation": "Both pool/rate cases passed. Four different WebKit failures: two initial media-readiness timeouts, one context teardown timeout, one page fixture setup timeout with null page. These are retained separately from the earlier missed eligibility, without claiming a proven host root cause. This artifact predates the moved-node clear fix." + }, + "movedNodeClear": { + "unitRed": { + "file": "refactor/.cache/danmuku05-renderer-clear-red.log", + "sha256": "239867dff2572164ebfccd852379f62c3dcb6d02e1cac693c149fb8c2758ac37" + }, + "unitGreen": { + "file": "refactor/.cache/danmuku05-renderer-clear-final.log", + "sha256": "16cdb9dd05759047a81dad0d697c4d422d0622abfb560fcfa519b025813a013e" + }, + "nativeRed": { + "file": "refactor/.cache/danmuku05-browser-clear-red/report.json", + "sha256": "b5a78d381e47cced94cc1a8f8f5e915fc964513d6838097f5123509d45808c0e" + }, + "explanation": "Final review reproduced moved owned node lost by clear() before destroy(). Clear now releases owned nodes before retaining old whole-layer clearing. Unit regression also preserves unrelated external nodes. Native replacement regression uses public load() and joins the final three-format matrix." + }, + "resourceFixtureCorrection": { + "file": "refactor/.cache/danmuku05-browser-source-clear-final/report.json", + "sha256": "ba15198f2c609084a39ee1ad0627bf7fee5b0f91016d887313d4450bc29ff176", + "passed": 137, + "failed": 1, + "explanation": "New moved-node resource case never observed its initial MOVED row in candidate-core WebKit; no cleanup assertion ran. Both renderer resource cases now explicitly establish eligible state, then require native Worker and visible DOM before public cleanup. Original native timing/rate/track tests remain, with bounded readys getter observation added to rate test." + }, + "resourceFixtureValidation": { + "file": "refactor/.cache/danmuku05-browser-resource-fixture/report.json", + "sha256": "2d7448f875c7654d8a1ede2fbdf3e415f895ae2d16d542a9617fd59855825858", + "tests": 18, + "explanation": "Two renderer cleanup cases and native pause/seek/rate case, both cores and all three engines, no retries." + }, + "additionalEvidence": [ + { + "file": "refactor/.cache/danmuku05-heatmap-first.log", + "sha256": "a800f5a6b24e5ddca48f6e7088ce2287b9d1e9f402fe7e2d667c7d2320119103" + }, + { + "file": "refactor/.cache/danmuku05-heatmap-cap-red.log", + "sha256": "e53c44bbb336fb40f1895064e8ced81f1e1dc466f0e4a9b73353b0f0aaaca9c9" + }, + { + "file": "refactor/.cache/danmuku05-heatmap-final.log", + "sha256": "5450b739b0b9b7e8cead9543346b3093326abeb0469f093367fb20f1bd211e05" + }, + { + "file": "refactor/.cache/danmuku05-renderer-initial.log", + "sha256": "5b97fb151f5ec1ff495c0fb4a7b047de52a4ef0bdbc30f7cc6cc6db114cb818c" + }, + { + "file": "refactor/.cache/danmuku05-existing-initial.log", + "sha256": "926daed6b25518f616088095519656e03798e434216656d0986ec7a26eb930be" + }, + { + "file": "refactor/.cache/danmuku05-setting-source-check.log", + "sha256": "bda4206a9e6b3194f754bb53d3c2ec5c2fe54c7dc2d63ec1b8db18a3090b9f92" + }, + { + "file": "refactor/.cache/danmuku05-setting-final-review.log", + "sha256": "1f33d21bcb1d2ff1a654deafa9ded7e78e8af71a25810f51d84359b4c55b3480" + } + ], + "initialManualDemo": { + "file": "refactor/.cache/danmuku05-iab-demo.json", + "sha256": "d5414e26d8de8f9d589faff2f8f80db5de28dd399e693d4281f852228a0858d0", + "url": "http://localhost:8082/?libs=./uncompiled/artplayer-plugin-danmuku/index.js&example=danmuku", + "browser": "Codex in-app browser", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36", + "coreVersion": "5.4.1", + "scenario": "Existing docs/assets/example/danmuku.js with local video.mp4 and danmuku.xml, built by yarn dev artplayer-plugin-danmuku --no-open", + "observations": [ + { + "action": "load", + "visibleTime": "00:00 / 01:30", + "events": [ + "loadedmetadata", + "loadeddata", + "canplay", + "canplaythrough" + ] + }, + { + "action": "click play", + "events": [ + "play", + "playing" + ], + "visibleDanmuku": true + }, + { + "action": "read media before pause", + "time": 46.89784, + "paused": false, + "videoWidth": 640 + }, + { + "action": "click pause", + "visibleTime": "00:46 / 01:30", + "event": "pause" + }, + { + "action": "send local test text", + "text": "Danmuku refactor smoke", + "beforeEmitLogged": true, + "time": 46.911033 + }, + { + "action": "later observation", + "time": 46.911033, + "paused": true, + "videoWidth": 640, + "input": "", + "send": "发送", + "gradient": "heatmap-solids" + } + ], + "limits": [ + "Manual developer smoke with actual local media; no device or release-package acceptance.", + "Read-only Playwright evaluation could not access navigator. One CDP evaluation had a syntax error, then the corrected read-only command returned the recorded data; these are inspection mistakes, not application errors.", + "Lock countdown timing and multi-instance cleanup are asserted in the committed automated browser suite, not inferred from this manual smoke." + ], + "devArtifact": { + "file": "refactor/.cache/danmuku05-iab-demo-artifact-before-clear.js", + "sha256": "56ceea16bde2ef07543dca8b5e03c943358c044cc92de72261a7353943185f02" + }, + "scope": "Initial docs smoke preceded the moved-node clear correction." + }, + "manualDemo": { + "file": "refactor/.cache/danmuku05-iab-demo-final.json", + "sha256": "82c141e5f113365bd4987ff84494405e623a4531c6546d7f3628d1853cd488f9", + "url": "http://localhost:8082/?libs=./uncompiled/artplayer-plugin-danmuku/index.js&example=danmuku", + "browser": "Codex in-app browser", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36", + "coreVersion": "5.4.1", + "buildLog": "refactor/.cache/danmuku05-dev-clear-final.log", + "workflow": "Reloaded after unit tests ended; clicked play, filled actual example input and clicked send, observed native time and comments, clicked pause.", + "observations": { + "sentText": "Final resource verification", + "beforeEmitMediaTime": 0.267138, + "playingUiTime": "00:47 / 01:30", + "commentsVisible": true, + "pausedTime": 53.404841, + "laterPausedTime": 53.404841, + "paused": true, + "videoWidth": 640, + "input": "", + "sendButton": "发送", + "pageWarningAndErrorLogs": [] + }, + "inspectionLimitations": [ + "An earlier accessibility index became stale after development reload; reacquired current page controls.", + "A read-only video locator evaluation timed out while the actual page kept playing; used supported CDP read-only Runtime.evaluate for final native media readings.", + "Real docs demo smoke is not installed npm package or physical-device acceptance. No external publishing or sending service was involved." + ], + "devArtifact": { + "file": "docs/uncompiled/artplayer-plugin-danmuku/index.js", + "sha256": "6a561bee80534cfe7bb532e165a68027aeee17b2fba044ba8b8a09e3ad2e3f0f" + } + }, + "dependency": { + "name": "linkedom", + "version": "0.18.13", + "workspace": "root", + "category": "devDependencies", + "purpose": "Actual HTML template parsing, DOM selectors and events for controlled Setting/renderer tests; not shipped in player packages.", + "source": "https://github.com/WebReflection/linkedom", + "limits": "Not a complete browser or layout engine. Controlled rectangles/timers and real published core utils are explicit in helper." + }, + "inputs": [ + { + "path": "docs/assets/example/danmuku.js", + "algorithm": "sha256-lf", + "sha256": "3f91461e2466f13ed4e72e6bd69986461192d8c6a9ca866fa347599f6958e021" + }, + { + "path": "package.json", + "algorithm": "sha256-lf", + "sha256": "cd3e4cdd96c871bac021883509a4798e8cf0e5cd84cce1a7023a7e6891832ee7" + }, + { + "path": "packages/artplayer-plugin-danmuku/ARCHITECTURE.md", + "algorithm": "sha256-lf", + "sha256": "6365b0e0303d8b9b614807660d1e8a86445efa826ffddfa0fce7474eca89bbb9" + }, + { + "path": "packages/artplayer-plugin-danmuku/README.md", + "algorithm": "sha256-lf", + "sha256": "b86a8837ec2d051f0c0526dec3ce38f60151eeb9f952dc004c4de052c3b5759e" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js", + "algorithm": "sha256-lf", + "sha256": "f60047d3ba6a47b7e4e65813e5c26bf7bebcc79addfbb36829d5bba0a57cfeba" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.legacy.js", + "algorithm": "sha256-lf", + "sha256": "249f576e256457c2e47e945b9eb18f78877b658163b266c1153028d1f46d30b0" + }, + { + "path": "packages/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs", + "algorithm": "sha256-lf", + "sha256": "964870b2f5f5bb282b5aec5fd5a793243c94ec1cfb36dadeb0fa410ce1345457" + }, + { + "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": "108fc0002f7dd8f1ebd1773758bcc0f951aa9eed58482f3f205af47babf259af" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/heatmap-geometry.js", + "algorithm": "sha256-lf", + "sha256": "5713ad838160609f24130120e625db327814eb877fcce32e74638daca1d64361" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/heatmap-sampling.js", + "algorithm": "sha256-lf", + "sha256": "f78ef3502ea695ff82f2ba7cb4c49e7c517c9d7d86713848700a5a392d7e769a" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/heatmap.js", + "algorithm": "sha256-lf", + "sha256": "8be1e5c9d1d291500284b422eb9bf3a9c21918e6022317486fb73ac8f28bffa3" + }, + { + "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": "01b12cc2af1df564157348bb1bc53669bd5f01215138a433e4301c2caa632f16" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/input.js", + "algorithm": "sha256-lf", + "sha256": "6740c347018290de2a8d90925148f63ec005d294426ed8d3192e69313171b8fc" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/queue.js", + "algorithm": "sha256-lf", + "sha256": "d566e312686fa486daee0c1618766c41cfb074c04bf872b7bf1bb783a7c57595" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/renderer.js", + "algorithm": "sha256-lf", + "sha256": "db793c1c2bfef3b1efce777ffaa9d4ded04b58a734ff52c1e95eb15ce5517f61" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/scheduler.js", + "algorithm": "sha256-lf", + "sha256": "2a035ec6869b4cbc8464c6e4dde083c2a5b685f4276c2355580902e46cc60e6b" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/setting-lifecycle.js", + "algorithm": "sha256-lf", + "sha256": "9f624f5c7922171dc5b81fb68ccd73987ed425204fae64043d06d9d419559a8b" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/setting-send.js", + "algorithm": "sha256-lf", + "sha256": "7bd281e87c2ceeffd29b8bb392f9581748ba0c0a873bff3d8b0a717f6aecc327" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/setting-slider.js", + "algorithm": "sha256-lf", + "sha256": "c49d7764a4217c524f5cbf48db907a770e8822188e9ab059dd6b6ea2c0fb8264" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/setting-style.js", + "algorithm": "sha256-lf", + "sha256": "bffbebd3c88a13808ecb98ba92b736be2691363a3d7e5f7387a6a0a564dd0061" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/setting-template.js", + "algorithm": "sha256-lf", + "sha256": "e58b86ca973f72db73ac9fc27263f0f90ade76552c9f04a53bd74bdf4f006940" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/setting.js", + "algorithm": "sha256-lf", + "sha256": "3f103097f3a34493b0f027716aa116db82e5b93b413ed546241eb2f9854225d8" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/style.less", + "algorithm": "sha256-lf", + "sha256": "7148a1bd32f5d3d743db4a4ae8d39dc0e1406d2bff18df42cbb20e6705d5fed9" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/worker-client.js", + "algorithm": "sha256-lf", + "sha256": "aca4c442a5722787b28d6b4b0aaa50521fe71c4c0e63e17636dc5e2268cbd925" + }, + { + "path": "packages/artplayer-plugin-danmuku/src/worker.js", + "algorithm": "sha256-lf", + "sha256": "05995a16cd728dd5ca513587a777b4be9012b8cc4b77e3a46fed1826e6769c53" + }, + { + "path": "test/browser/danmuku-input.spec.js", + "algorithm": "sha256-lf", + "sha256": "37a96982ea7bf2fb99df5e4d829ce1c3f512145092a440586c2d5ad7d0a2d6f3" + }, + { + "path": "test/browser/danmuku-resources.spec.js", + "algorithm": "sha256-lf", + "sha256": "f0d09e962cb422a66bd644c52268aff8f288606ae9c93676fa979539743f9c6b" + }, + { + "path": "test/browser/danmuku-scheduler.spec.js", + "algorithm": "sha256-lf", + "sha256": "c3a7507eff60cdcd5160e46dd3dcc957496bfe35192a7249defe7b5f1c04159d" + }, + { + "path": "test/browser/fixtures.js", + "algorithm": "sha256-lf", + "sha256": "313414b8958eb5b03a2da8595fa0bbf6bfdc01484373898c6cb7179545df175d" + }, + { + "path": "test/browser/server.mjs", + "algorithm": "sha256-lf", + "sha256": "1406fac75eabc83b987e79ec7b5f97a948b27770bfd5f5290d7cb9155196f08f" + }, + { + "path": "test/danmuku-heatmap.test.js", + "algorithm": "sha256-lf", + "sha256": "81c1a04a5a5ab032765893bfed96de8c10fa7ef00769cb8345e1f30fa17b4129" + }, + { + "path": "test/danmuku-input.test.js", + "algorithm": "sha256-lf", + "sha256": "bccd24096dabd5270ba3624dc071d56a492cf44b3045c78ae261d4510ae6e712" + }, + { + "path": "test/danmuku-parser.test.js", + "algorithm": "sha256-lf", + "sha256": "ae2dae43f447c1361662f70e90009c8978e1d96bbb133b7b07baafc9d765d70e" + }, + { + "path": "test/danmuku-renderer.test.js", + "algorithm": "sha256-lf", + "sha256": "2f3eaff3b9e9a849af1dbe85f9eef2399d3d055f6bc24267f5a9da2a0bb98f67" + }, + { + "path": "test/danmuku-scheduler.test.js", + "algorithm": "sha256-lf", + "sha256": "7c1c3d81c4cc37f1ddcd18f3c35c8825061887ada6e1f395cb4d8efb73f66727" + }, + { + "path": "test/danmuku-setting.test.js", + "algorithm": "sha256-lf", + "sha256": "4f339b1806900842e9382ba96da6335a26f0ade5b10de2ac01656c476344bfe2" + }, + { + "path": "test/danmuku-worker-client.test.js", + "algorithm": "sha256-lf", + "sha256": "55d7754224afa5567cafd880c28d923b9ff0e0661ecfb4ea1d9c923ddb84ae3b" + }, + { + "path": "test/helpers/danmuku-candidate.js", + "algorithm": "sha256-lf", + "sha256": "917e1ca3e8b6ecd9f51055f9a0ce5144870e66a656afd22c3f023b56c8d76ffc" + }, + { + "path": "test/helpers/danmuku-setting.js", + "algorithm": "sha256-lf", + "sha256": "9263c67c7499d4a60c3a85aad067503e856e0289bf2e3998d817339207fca26a" + }, + { + "path": "yarn.lock", + "algorithm": "sha256", + "sha256": "3816c6f1602ef76f27ccd2491c3e34e2fcb630eb9b1baac726c84cf01eca0d11" + } + ], + "limitations": [ + "Owned JS responsibilities separated; TS and installed declaration contracts remain task06.", + "Long-run real workload and heap/device acceptance remain task07.", + "Browser recovery errors are explicit fixture faults, not actual CSP or SDK failures.", + "ESM built and copied byte-identically; not counted as a fourth runtime matrix.", + "Initial arbitrary 100000 point cap was rejected during review. Its two tests failed (one generated an oversized assertion diff); final valid >100000 sampling/points pass. Final protection covers invalid/unrepresentable/non-progressing numeric inputs, not arbitrary CPU cost.", + "An early renderer check failed because the old fake Element lacked removeChild; only the candidate helper gained deletion semantics. Frozen helper remains unchanged.", + "Interrupted earlier typecheck log is incomplete and not counted; only typecheck-complete is final.", + "No npm publication, remote CI run or physical Safari claimed." + ], + "releaseReady": false +} diff --git a/refactor/changes/2026-09-13-PKG-DANMUKU-05-resources.md b/refactor/changes/2026-09-13-PKG-DANMUKU-05-resources.md new file mode 100644 index 000000000..63a56b378 --- /dev/null +++ b/refactor/changes/2026-09-13-PKG-DANMUKU-05-resources.md @@ -0,0 +1,118 @@ +# PKG-DANMUKU-05 渲染、设置和热力图资源归属 + +本步完成JS职责整理,完整自有TS、Worker消息和已安装声明消费者归06。渲染模块负责 +节点和几何,设置模块拆为协调器、模板/icons、Slider、发送、生命周期及全局样式; +热力图拆为采样、曲线计算和control生命周期。实际维护地图见包内 +[ARCHITECTURE.md](../../packages/artplayer-plugin-danmuku/ARCHITECTURE.md)。 + +## 实现与兼容 + +- renderer接管原节点分配、样式、位置快照及暂停/恢复几何,scheduler继续决定操作 + 是否有效;两者之间保留generation与节点身份检查。destroy(false)仍移除自己的 + 弹幕节点、清除引用与空闲池,保留其他节点。替换加载仍执行旧整层清空,并在 + 释放归属记录前移除已被移动到层外的自有节点,保留层外无关内容。 +- Setting在创建资源前登记销毁;自身初始化失败清理资源,入口再回滚已创建的 + Danmuku/Worker并保留原错。精确解绑art监听、proxy和registry条目;某项解绑 + 抛错仍清理其他项。旧核心5.4.0的proxy返回函数需配合events.remove删除登记。 +- pending beforeEmit与销毁竞速,销毁后不清input、不加锁、不迟到发送。保留 + option receiver及严格true;普通发送仍同步插入、清input和启动原倒计时。 + 新增对emit返回Promise的局部观察,继续使用原console.error出口,不改为插件error。 +- mount/fullscreen移动同一节点;销毁只移除自有设置DOM。宿主dataset/display按 + 最后写入值及实例归属恢复;共享mount保留存活实例,用户后续写入不被覆盖。 + 原始display的!important通过真实浏览器验证。全局样式仍在import时安装且不随 + 单实例销毁,DOMContentLoaded等待在重复bundle间去重,触发后解除监听。 +- 热力图窄宽度和无效sampling使用正步长,非法尺寸/duration不生成NaN SVG。采样 + 由逐bin扫描整队列改为排序及二分计数,保留原浮点步进、严格左开右闭和数值边界。 + 曲线与旧源码、实际5.3.0 main/legacy逐字对照;points事件仍只复制外层,内层y + 原地变化及重复调用累积保持,未静默改成immutable。 +- 不设置任意100000数量上限,合法100001+采样/自定义点继续工作。仅拒绝不可表示 + 的数组长度、非有限参数或无法推进的数值步长;不声称任意巨大输入CPU成本有界。 +- 无冲突的首个gradient保留旧heatmap-solids,其他实例使用独立ID;同文档重复 + bundle也检查实际ID占用。heatmap-start/stop局部钩子、control名、样式与进度保持。 + control卸载解除订阅,销毁不误删后来替换的同名control。 +- 工厂门面、返回身份、getter、静态icons、合法输入和原轨道Worker算法保持。 + 不修改公共声明、分发入口或消费者运行时依赖。版本递增仍由统一版本任务实施。 + +## 验证及证据边界 + +源码、正式main、正式legacy各159项联合测试通过:原输入/解析/调度/Worker104项, +Setting36项、heatmap14项、renderer5项。每组101项随候选实现变化,另58项固定为 +原Worker模块18、历史数值3、历史Setting18、直接heatmap14、直接renderer5;不能 +把这些共同测试重复描述成不同产物覆盖。完整yarn test:danmuku为245项通过。 + +最终源码、main、legacy各138项原生浏览器通过:输入24、调度66、资源48,共414项。 +每种实现使用2并发,最终报告unexpected/skipped/flaky均为0,retries为0。此前的 +手工诊断和失败分别留档,不把最终通过描述成此前从未失败或负载问题已关闭。 +实际已发布5.4.0和候选5.4.1核心,Chromium、Firefox、Windows WebKit。 +覆盖真实视频/Worker、旧调度轨道、pending发送和计时器、外部及共享mount、初始化 +回滚、原生!important、destroy(false)和重复bundle热力图。WebKit不是Safari真机。 + +完整构建生成main/legacy/esm并与docs/compiled逐一核对字节;ESM只计构建与复制, +不列入额外运行矩阵。源码/测试lint、Yarn冻结安装、严格工具链、全仓366生产TS与 +消费者检查通过;未迁移JS不计入TS覆盖。早前被中断的typecheck日志不作为成功证据。 + +内置浏览器Chrome/152.0.0.0打开现有8082 docs编辑器及danmuku示例,使用正常yarn dev +构建和本地video.mp4/danmuku.xml:播放时间推进至46.89784、实际解码宽640,暂停后 +保持46.911033;真实弹幕可见,示例异步beforeEmit发送完成后input清空,按钮恢复。 +这是开发示例smoke,不是实际发布包或设备验收。读媒体时两次调试调用失败已记录, +不混同为页面错误,也不从一次截图推断无后台工作。 + +移动节点修正后,重新正常构建dev示例并在单元测试结束后重载验证:发送 +Final resource verification,beforeEmit记录媒体时间0.267138,真实播放UI推进到 +00:47,随后暂停在53.404841,后次原生读取仍为同值,解码宽640,input为空且发送 +按钮恢复。页面warning/error列表为空。一次旧AX索引因开发重载失效,一次只读 +video locator读取超时;使用当前UI和支持的CDP只读接口完成最终取证,均单独记录。 + +本步新增根devDependency linkedom@0.18.13并维护唯一yarn.lock,用于真实模板解析、 +selector和事件测试,不随播放器发布。其[官方说明](https://github.com/WebReflection/linkedom) +明确不是完整浏览器;测试受控几何/计时器与真实浏览器证据分别记录。根test:unit和 +test:danmuku加入三组新测试,浏览器spec沿既有CI发现。远端CI未在本步执行。 + +## 失败如何处置 + +- 旧04正式main的两核心Chrome六项资源回归均失败:外部DOM残留、pending完成后 + 清input并加锁、锁计时器保留及renderer节点引用残留。修复后由最终矩阵验证。 +- renderer首轮受控检查因冻结假Element没有removeChild报错,仅给候选helper补 + 删除语义;冻结历史helper不改。随后原109项联合检查通过,另用Linkedom和原生 + DOM断言真实节点删除,避免假DOM掩盖清理。 +- 最后复审复现另一条资源遗漏:节点移到body后,替换加载清空归属集合但未移除 + 层外节点,后续destroy也无法清理。增加红例后,clear先释放自有节点再执行原 + 整层清空;受控回归验证层外无关节点保留,并新增两核心三引擎真实DOM用例。 +- 中间热力图100000硬上限在复审中被否决。大输入两个红例中,一个巨大差异格式化 + 导致断言报告内存分配失败,另一个实际返回null。移除上限,并在比较大数组前先 + 断言长度,最终完整采样、曲线及内层mutation通过,不保留不兼容限制。 +- 三引擎首轮128/132通过。两个WebKit例把打字/点击期间所有一秒定时器都当作 + 发送锁,捕获了额外计时器;改为从beforeEmit的同步发送续体观察初始锁ID,仍 + 要求该真实ID在销毁时清除。另两个取消/恢复例在预期时间未进入beforeVisible, + Worker请求为0。其目的在验证进入准备后的生命周期,改为明确建立ready前置 + 状态;原真实时间、轨道及数值例保持。不能据此宣称所有负载下的时间窗口无遗漏, + 此项由LOAD-01留给07采集真实帧/媒体时间和压力证据。第一次ready夹具修改又引入 + 六个seek失败:旧项变为永久ready,seek后重新进入未结束的gate;恢复它按媒体时间 + 触发,仅恢复/新续行项设ready。NaN与旧gate用真实0.25倍率扩大对应墙钟窗口, + 不代表任意调度延迟下均可送达。该轮还有一项NaN错过窗口,125/132通过,完整归档。最终受影响42项 + 三引擎定向复验通过后重跑全矩阵。未扩大超时、改变生产逻辑或删除最终断言。 +- 后续源码132项通过,主构建三并发131/132通过:WebKit旧核心的reused弹幕未进入 + beforeVisible,只有初始moving的Worker请求。queue与04及冻结源码的±0.1秒规则 + 相同;2倍速时窗口约100毫秒,但没有当时逐帧媒体读数,不能断言帧间隔是根因。 +- 同一主构建、同一132项改为单并发后128通过、4失败,原两核心倍速复用例均通过。 + 新失败为两个初始媒体ready超时、一个context关闭超时、一个page创建超时及其 + null page后续错误。完整报告保留;这些不是移动节点修复的红例,也不能证明 + 主机或WebKit自身是唯一原因。此后修复清理遗漏,新产物使用与CI一致的2并发 + 重新运行完整矩阵,retries仍为0,前述失败并未被删除或算作已修复。 +- 移动节点修复后的源码137/138通过,唯一失败在新增清理例的MOVED出现之前。 + 清理例不负责固定时间窗口,把两个renderer资源例的起点明确设为ready,再要求 + 原生视频、真实Worker和可见DOM后执行清理;原生时间/倍速/轨道用例继续保留。 + 倍速用例额外代理原readys getter一次且原样返回,记录最多2000份真实媒体时间、 + 队列与ready样本,方便后续区分资格遗漏与渲染失败,不修改生产时间或轨道判断。 + 受影响18项定向验证通过后,重新运行三种产物完整414项,均通过。 + +机器记录、文件指纹、失败及最终报告见 +[danmuku-resources-validation.json](../baselines/danmuku-resources-validation.json)。 +首次三引擎失败完整归档,最终档案的截图附件保留原报告路径到archive目录的映射。 + +## 交接和回退 + +CLEANUP-01/HEATMAP-01按本步具体标准关闭。LOAD-01、完整TS/声明、真实长负载、 +设备及发布前复盘仍归后续任务,不由本步通过替代。下一项06在提交核验后启动。 +本步对应独立本地commit;若回退,整体revert该提交并同步任务/风险状态,不单独 +恢复dist或只退资源模块。没有推送、标签或npm发布。 diff --git a/refactor/plan.md b/refactor/plan.md index 88f67b651..1f88aaa95 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 228 项,范围 22 个包及工作区/示例。 -状态:todo 70 / doing 15 / blocked 0 / done 143 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 69 / doing 15 / blocked 0 / done 144 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -313,8 +313,8 @@ | 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 | done | | PKG-DANMUKU-04 | artplayer-plugin-danmuku
整理时钟、队列与轨道调度 | PKG-DANMUKU-03, CORE-10 | danmuku 调度器和确定性时钟测试 | seek/暂停/倍率/长时间运行无顺序和碰撞回归 | H | done | -| PKG-DANMUKU-05 | artplayer-plugin-danmuku
整理 DOM 渲染、设置、热力图与 worker | PKG-DANMUKU-04, CORE-14, CORE-18 | renderer/setting/heatmap/worker 职责及资源归属 | mount/icons/设置和 worker 协议保持,销毁无后台工作 | H | doing | -| PKG-DANMUKU-06 | artplayer-plugin-danmuku
迁移 TS 与公开声明 | PKG-DANMUKU-05, ENG-04, ENG-06 | 自有模块、worker 消息、option/item/result 类型 | 旧参数、扩展字段和事件消费通过,vendored 边界清楚 | H | todo | +| PKG-DANMUKU-05 | artplayer-plugin-danmuku
整理 DOM 渲染、设置、热力图与 worker | PKG-DANMUKU-04, CORE-14, CORE-18 | renderer/setting/heatmap/worker 职责及资源归属 | mount/icons/设置和 worker 协议保持,销毁无后台工作 | H | done | +| PKG-DANMUKU-06 | artplayer-plugin-danmuku
迁移 TS 与公开声明 | PKG-DANMUKU-05, ENG-04, ENG-06 | 自有模块、worker 消息、option/item/result 类型 | 旧参数、扩展字段和事件消费通过,vendored 边界清楚 | H | doing | | PKG-DANMUKU-07 | artplayer-plugin-danmuku
弹幕能力稳定性验收 | PKG-DANMUKU-06 | 负载/渲染/资源对比及 mask 可依赖的稳定边界 | 在试点核心中旧插件 API 与关键帧路径通过 | H | todo | | PKG-DANMUKU-08 | artplayer-plugin-danmuku
完成新旧核心与组合验收 | PKG-DANMUKU-07, CORE-22 | 最终核心/旧核心、mask/fullscreen/PiP 组合报告 | 性能无未解释退化,反复装卸无累计资源 | H | todo | | PKG-DANMUKU-09 | artplayer-plugin-danmuku
完成分发、示例与文档 | PKG-DANMUKU-08, ENG-07 | danmuku.js、README、声明、worker 与产物 | tarball 和静态 icons 等旧调用通过,有独立回退版本 | H | todo | @@ -557,6 +557,7 @@ - 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-DANMUKU-04: [记录](changes/2026-09-13-PKG-DANMUKU-04-scheduler.md) [记录](baselines/danmuku-scheduler-validation.json) +- PKG-DANMUKU-05: [记录](changes/2026-09-13-PKG-DANMUKU-05-resources.md) [记录](baselines/danmuku-resources-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 ceb80070e..7d24d1ce3 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,17 @@ # 进度与证据 +## PKG-DANMUKU-05 渲染、设置与热力图完成 + +拆renderer、Setting模板/滑块/发送/资源/全局样式和heatmap采样/几何/生命周期。 +修复外部DOM、锁计时器、订阅与节点清理、初始化回滚及窄热力图卡死,保留共享宿主 +用户写入、原points mutation、首实例SVG钩子与合法大数据。源码/main/legacy各159项 +联合测试,完整test:danmuku245项、三格式×138共414原生浏览器项通过;冻结安装、 +严格工具链、完整类型检查和正常构建通过。内置浏览器完成现有docs示例播放/暂停/发送。 +关闭CLEANUP-01/HEATMAP-01,见[变更](changes/2026-09-13-PKG-DANMUKU-05-resources.md)及[机器证据](baselines/danmuku-resources-validation.json)。 +228项:144 done、15 doing、69 todo;06进入完整TS。 +真实长负载、发布前复盘仍待对应任务;无推送或发布。 + + ## PKG-DANMUKU-04 时钟、队列与轨道调度完成 拆分scheduler/queue/worker-client,修复多RAF、同毫秒请求串线、取消后续写、 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index c21b56dd3..eae633a79 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -230,10 +230,11 @@ | 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 | 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-CLEANUP-01 | resolved / 已复现 | 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 | resolved / 已复现 | 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 | -| DANMUKU-HEATMAP-01 | open / 已复现 | Danmuku heatmap sampling can become zero and points/instance ownership need browser validation | PKG-DANMUKU-02, PKG-DANMUKU-05 | +| DANMUKU-HEATMAP-01 | resolved / 已复现 | Danmuku heatmap sampling can become zero and points/instance ownership need browser validation | PKG-DANMUKU-02, PKG-DANMUKU-05 | | DANMUKU-SOURCE-01 | open / 已复现 | Danmuku registry Git associations differ from releases and static icon provenance remains incomplete | PKG-DANMUKU-09 | | DANMUKU-TRACK-01 | resolved / 已复现 | Native simultaneous fixed Danmuku comments can overlap with duplicate start loops and repeated Worker IDs | PKG-DANMUKU-04, PKG-DANMUKU-05, PKG-DANMUKU-07 | | DANMUKU-INPUT-02 | resolved / 已复现 | Intermediate explicit-zero correction regressed accepted NaN timestamp fallback | PKG-DANMUKU-04 | +| DANMUKU-LOAD-01 | open / 待取证 | Native lifecycle fixtures sometimes miss timestamp eligibility during concurrent workload; real frame/window behavior needs load evidence | PKG-DANMUKU-07 | diff --git a/refactor/risks.json b/refactor/risks.json index 861cd6daa..7e434a5c0 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -5094,7 +5094,7 @@ "id": "DANMUKU-CLEANUP-01", "title": "Danmuku removes a different resize callback and retains the settings lock timer on destroy", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-DANMUKU-02", "PKG-DANMUKU-05" @@ -5108,10 +5108,20 @@ "refactor/changes/2026-09-13-PKG-DANMUKU-03-input.md", "refactor/baselines/danmuku-input-validation.json", "refactor/changes/2026-09-13-PKG-DANMUKU-04-scheduler.md", - "refactor/baselines/danmuku-scheduler-validation.json" + "refactor/baselines/danmuku-scheduler-validation.json", + "refactor/changes/2026-09-13-PKG-DANMUKU-05-resources.md", + "refactor/baselines/danmuku-resources-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." + "closureCriteria": "Repeated lifecycle and pending settings actions leave no owned subscriptions, timers or late DOM writes.", + "resolutionEvidence": [ + "refactor/changes/2026-09-13-PKG-DANMUKU-05-resources.md", + "refactor/baselines/danmuku-resources-validation.json", + "test/danmuku-setting.test.js", + "test/danmuku-heatmap.test.js", + "test/browser/danmuku-resources.spec.js" + ], + "resolutionRationale": "Exact subscriptions/proxy registry entries, send countdowns, external settings and owned renderer nodes are released; stale sends are cancelled and shared host values restored conditionally. Native two-core source/main/legacy matrices and real docs smoke passed." }, { "id": "DANMUKU-TYPE-02", @@ -5169,7 +5179,7 @@ "id": "DANMUKU-HEATMAP-01", "title": "Danmuku heatmap sampling can become zero and points/instance ownership need browser validation", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-DANMUKU-02", "PKG-DANMUKU-05" @@ -5181,10 +5191,20 @@ "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-05-resources.md", + "refactor/baselines/danmuku-resources-validation.json" ], "compatibleResolution": "Use isolated timeout-protected reproduction for narrow dimensions, then define finite sampling and per-instance rendering.", - "closureCriteria": "Narrow widths terminate, points/config behavior is explicit and multiple SVG/control instances remain independent." + "closureCriteria": "Narrow widths terminate, points/config behavior is explicit and multiple SVG/control instances remain independent.", + "resolutionEvidence": [ + "refactor/changes/2026-09-13-PKG-DANMUKU-05-resources.md", + "refactor/baselines/danmuku-resources-validation.json", + "test/danmuku-setting.test.js", + "test/danmuku-heatmap.test.js", + "test/browser/danmuku-resources.spec.js" + ], + "resolutionRationale": "Narrow/invalid sampling terminates; exact historical curves and nested point mutation retained; valid large inputs are not capped arbitrarily. Native multi-instance and repeated-bundle gradient isolation preserves the first legacy ID and independent progress/control cleanup." }, { "id": "DANMUKU-SOURCE-01", @@ -5252,6 +5272,22 @@ "test/browser/danmuku-scheduler.spec.js" ], "resolutionRationale": "Historical source/main/legacy establish old behavior; source/main/legacy final input and native matrices confirm the one-condition correction. Task03 frozen evidence is preserved." + }, + { + "id": "DANMUKU-LOAD-01", + "title": "Native lifecycle fixtures sometimes miss timestamp eligibility during concurrent workload; real frame/window behavior needs load evidence", + "confirmation": "unverified", + "status": "open", + "owners": [ + "PKG-DANMUKU-07" + ], + "evidence": [ + "refactor/changes/2026-09-13-PKG-DANMUKU-05-resources.md", + "refactor/baselines/danmuku-resources-validation.json", + "test/browser/danmuku-scheduler.spec.js" + ], + "compatibleResolution": "Capture native RAF/media-time gaps and scheduled/eligible/visible item counts under realistic load before deciding whether the old plus/minus 0.1 second eligibility rule needs a compatible correction.", + "closureCriteria": "Real load runs explain missing scheduled items with frame and media evidence, and any confirmed implementation defect has a regression and compatible fix; seeding ready in lifecycle fixtures is not closure evidence." } ] } diff --git a/refactor/tasks.json b/refactor/tasks.json index 47c090a99..597d53214 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -3341,11 +3341,14 @@ "CORE-14", "CORE-18" ], - "status": "doing", + "status": "done", "risk": "H", "deliverable": "renderer/setting/heatmap/worker 职责及资源归属", "acceptance": "mount/icons/设置和 worker 协议保持,销毁无后台工作", - "evidence": [] + "evidence": [ + "changes/2026-09-13-PKG-DANMUKU-05-resources.md", + "baselines/danmuku-resources-validation.json" + ] }, { "id": "PKG-DANMUKU-06", @@ -3359,7 +3362,7 @@ "ENG-04", "ENG-06" ], - "status": "todo", + "status": "doing", "risk": "H", "deliverable": "自有模块、worker 消息、option/item/result 类型", "acceptance": "旧参数、扩展字段和事件消费通过,vendored 边界清楚", diff --git a/test/browser/danmuku-resources.spec.js b/test/browser/danmuku-resources.spec.js new file mode 100644 index 000000000..b7ae608f2 --- /dev/null +++ b/test/browser/danmuku-resources.spec.js @@ -0,0 +1,291 @@ +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, scenario, testInfo) { + const origin = new URL(testInfo.project.use.baseURL).origin + await page.route('**/*', (route) => { + return new URL(route.request().url()).origin === origin ? route.continue() : route.abort() + }) + await page.goto(`/test/player.html?core=${core}`) + await page.addScriptTag({ content: candidate }) + await page.evaluate(({ scenario }) => { + window.resourcePlayers = [] + window.resourcePlugins = [] + window.resourceOwners = [] + window.resourceMessages = [] + window.resourceSends = [] + window.resourceErrors = [] + window.resourceWorkers = [] + window.resourceTimers = new Set() + const nativeSetTimeout = window.setTimeout.bind(window) + const nativeClearTimeout = window.clearTimeout.bind(window) + window.setTimeout = (callback, delay, ...args) => { + const id = nativeSetTimeout(() => { + window.resourceTimers.delete(id) + callback(...args) + }, delay) + if (window.captureNextResourceTimer && delay === 1000) { + window.resourceTimers.add(id) + window.captureNextResourceTimer = false + } + return id + } + window.clearTimeout = (id) => { + window.resourceTimers.delete(id) + nativeClearTimeout(id) + } + const NativeWorker = window.Worker + window.Worker = class extends NativeWorker { + constructor(...args) { + super(...args) + this.record = { terminated: 0 } + window.resourceWorkers.push(this.record) + } + + terminate() { + this.record.terminated++ + super.terminate() + } + } + const originalError = console.error + console.error = (...args) => { + window.resourceErrors.push(args.map(arg => arg?.message || String(arg))) + originalError(...args) + } + window.sendGate = new Promise((resolve, reject) => { + window.resolveSend = resolve + window.rejectSend = reject + }) + window.createResourcePlayer = (index, extra = {}) => { + const container = index === 0 ? document.querySelector('.player') : document.body.appendChild(document.createElement('div')) + container.style.cssText = 'width:640px;height:360px' + const mount = document.body.appendChild(document.createElement('div')) + mount.id = `resource-mount-${index}` + const art = new window.Artplayer({ container, url: '/test/pattern.mp4', muted: true, fullscreenWeb: true, setting: true }) + window.resourcePlayers[index] = art + art.on('artplayerPluginDanmuku:visible', item => window.resourceMessages.push(item.text)) + const option = { + danmuku: [], + mount, + heatmap: scenario === 'heatmap' ? { sampling: 0 } : false, + lockTime: 10, + margin: [10, 10], + fontSize: 20, + speed: 2, + beforeEmit(danmu) { + window.resourceSends.push({ index, text: danmu.text, receiver: this === window.resourcePlugins[index].option }) + // Capture the lock scheduled in this send's await continuation, not + // unrelated browser/core one-second timers while typing or clicking. + if (window.observeResourceTimers) + window.captureNextResourceTimer = true + return scenario === 'pending' || scenario === 'reject' ? window.sendGate : true + }, + ...extra, + } + art.plugins.add(window.artplayerPluginDanmuku(option)) + const plugin = art.plugins.artplayerPluginDanmuku + window.resourcePlugins[index] = plugin + window.resourceOwners[index] = plugin.show() + return art + } + window.art = window.createResourcePlayer(0) + document.querySelector('#play').onclick = () => window.art.play() + }, { scenario }) + await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true) + await testInfo.attach('danmuku-resources-inputs', { contentType: 'application/json', body: JSON.stringify({ core, scenario, artifact: process.env.ARTPLAYER_DANMUKU_ARTIFACT || 'source UMD', sha256: hash(candidate), native: 'video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock' }) }) +} + +test.afterEach(async ({ page }, testInfo) => { + await testInfo.attach('danmuku-resources-page', { contentType: 'image/png', body: await page.screenshot() }) + const evidence = await page.evaluate(() => { + for (const art of window.resourcePlayers || []) { + if (!art.isDestroy) + art.destroy(false) + } + return { workers: window.resourceWorkers, sends: window.resourceSends, messages: window.resourceMessages, errors: window.resourceErrors, retainedSettings: document.querySelectorAll('[id^="resource-mount-"] .artplayer-plugin-danmuku').length, timers: window.resourceTimers?.size } + }) + await testInfo.attach('danmuku-resources-evidence', { contentType: 'application/json', body: JSON.stringify(evidence) }) + expect(evidence.retainedSettings).toBe(0) + for (const worker of evidence.workers || []) expect(worker.terminated).toBe(1) +}) + +async function renderResourceRow(page, text) { + await page.evaluate(async (text) => { + const owner = await window.resourcePlugins[0].emit({ text, time: 0.2, mode: 1 }) + // Resource tests start with an eligible row, then require a real Worker and + // rendered DOM. Native timestamp delivery is covered by the scheduler spec. + owner.setState(owner.queue[owner.queue.length - 1], 'ready') + }, text) + await page.click('#play') + await expect.poll(() => page.evaluate(() => window.resourceMessages)).toContain(text) +} + +for (const core of ['published', 'candidate']) { + test(`${core}: shared setting mount preserves the surviving instance and later user changes`, async ({ page }, testInfo) => { + await setup(page, core, 'normal', testInfo) + const result = await page.evaluate(() => { + const mount = document.querySelector('#resource-mount-0') + window.createResourcePlayer(1, { mount, theme: 'light' }) + window.art.destroy(false) + const survivor = { panels: mount.querySelectorAll('.artplayer-plugin-danmuku').length, theme: mount.dataset.danmukuTheme } + mount.dataset.danmukuTheme = 'user-value' + window.resourcePlayers[1].destroy(false) + return { survivor, theme: mount.dataset.danmukuTheme, ownedKeys: Object.keys(mount.dataset).filter(key => key.startsWith('danmuku') && key !== 'danmukuTheme'), panels: mount.querySelectorAll('.artplayer-plugin-danmuku').length } + }) + expect(result).toEqual({ survivor: { panels: 1, theme: 'light' }, theme: 'user-value', ownedKeys: [], panels: 0 }) + }) + + test(`${core}: invalid setting mount rolls back plugin resources while preserving the original error`, async ({ page }, testInfo) => { + await setup(page, core, 'normal', testInfo) + const result = await page.evaluate(() => { + const container = document.body.appendChild(document.createElement('div')) + container.style.cssText = 'width:640px;height:360px' + const art = new window.Artplayer({ container, url: '/test/pattern.mp4', muted: true }) + window.resourcePlayers.push(art) + art.template.$controlsCenter.style.setProperty('display', 'grid', 'important') + const display = art.template.$controlsCenter.style.display + let error + try { + art.plugins.add(window.artplayerPluginDanmuku({ danmuku: [], mount: '#missing-setting-mount' })) + } + catch (failure) { + error = failure.message + } + return { error, panels: container.querySelectorAll('.artplayer-plugin-danmuku').length, worker: window.resourceWorkers.at(-1), display: art.template.$controlsCenter.style.display, priority: art.template.$controlsCenter.style.getPropertyPriority('display'), previousDisplay: display } + }) + expect(result.error).toContain('Can not find the mount point: #missing-setting-mount') + expect(result.panels).toBe(0) + expect(result.worker.terminated).toBe(1) + expect(result.display).toBe(result.previousDisplay) + expect(result.priority).toBe('important') + }) + + test(`${core}: destroy cancels pending setting send without clearing retained input or starting a lock`, async ({ page }, testInfo) => { + await setup(page, core, 'pending', testInfo) + const mount = page.locator('#resource-mount-0') + await mount.locator('.apd-input').fill('PENDING') + await mount.locator('.apd-send').click() + expect(await page.evaluate(() => window.resourceSends)).toEqual([{ index: 0, text: 'PENDING', receiver: true }]) + const result = await page.evaluate(async () => { + const input = document.querySelector('#resource-mount-0 .apd-input') + const send = document.querySelector('#resource-mount-0 .apd-send') + window.art.destroy(false) + window.resolveSend(true) + await new Promise(resolve => setTimeout(resolve, 30)) + return { value: input.value, locked: send.classList.contains('apd-lock'), queue: window.resourceOwners[0].queue.map(item => item.text), connected: input.isConnected } + }) + expect(result).toEqual({ value: 'PENDING', locked: false, queue: [], connected: false }) + expect(await page.evaluate(() => window.resourceErrors)).toEqual([]) + }) + + test(`${core}: destroy clears active send timer and leaves another mounted instance usable`, async ({ page }, testInfo) => { + await setup(page, core, 'normal', testInfo) + await page.evaluate(() => window.observeResourceTimers = true) + await page.locator('#resource-mount-0 .apd-input').fill('LOCK') + await page.locator('#resource-mount-0 .apd-send').click() + await expect(page.locator('#resource-mount-0 .apd-send')).toHaveClass(/apd-lock/u) + expect(await page.evaluate(() => window.resourceTimers.size)).toBe(1) + await page.evaluate(() => { + window.observeResourceTimers = false + window.createResourcePlayer(1) + window.art.destroy(false) + }) + expect(await page.evaluate(() => window.resourceTimers.size)).toBe(0) + await expect(page.locator('#resource-mount-0 .artplayer-plugin-danmuku')).toHaveCount(0) + await page.locator('#resource-mount-1 .apd-input').fill('SECOND') + await page.locator('#resource-mount-1 .apd-send').click() + await expect.poll(() => page.evaluate(() => window.resourceOwners[1].queue.map(row => row.text))).toEqual(['SECOND']) + expect(await page.locator('style#artplayer-plugin-danmuku').count()).toBe(1) + }) + + test(`${core}: rejected setting callback retains input and original console error without an unhandled rejection`, async ({ page }, testInfo) => { + await setup(page, core, 'reject', testInfo) + await page.locator('#resource-mount-0 .apd-input').fill('REJECT') + await page.locator('#resource-mount-0 .apd-send').click() + await page.evaluate(() => window.rejectSend(new Error('controlled setting rejection'))) + await expect.poll(() => page.evaluate(() => window.resourceErrors)).toEqual([['Error emitting danmuku:', 'controlled setting rejection']]) + await expect(page.locator('#resource-mount-0 .apd-input')).toHaveValue('REJECT') + await expect(page.locator('#resource-mount-0 .apd-send')).not.toHaveClass(/apd-lock/u) + expect(await page.evaluate(() => window.resourceOwners[0].queue.length)).toBe(0) + }) + + test(`${core}: destroy false releases actual renderer nodes while retaining the player shell`, async ({ page }, testInfo) => { + await setup(page, core, 'normal', testInfo) + await renderResourceRow(page, 'VISIBLE') + const result = await page.evaluate(() => { + const layer = window.art.template.$danmuku + const node = [...layer.children].find(item => item.textContent === 'VISIBLE') + const owner = window.resourceOwners[0] + window.art.destroy(false) + return { connected: node.isConnected, nodes: layer.children.length, refs: owner.$refs.length, dangling: owner.queue.filter(item => item.$ref).length, shell: !!window.art.template.$player } + }) + expect(result).toEqual({ connected: false, nodes: 0, refs: 0, dangling: 0, shell: true }) + }) + + test(`${core}: replacement load removes a moved renderer node and preserves unrelated body content`, async ({ page }, testInfo) => { + await setup(page, core, 'normal', testInfo) + await renderResourceRow(page, 'MOVED') + const result = await page.evaluate(async () => { + window.art.pause() + const layer = window.art.template.$danmuku + const node = [...layer.children].find(item => item.textContent === 'MOVED') + const foreign = document.createElement('aside') + document.body.append(node, foreign) + await window.resourcePlugins[0].load() + const replacement = { connected: node.isConnected, nodes: layer.childElementCount, foreign: foreign.isConnected, refs: window.resourceOwners[0].$refs.length } + window.art.destroy(false) + return { replacement, foreignAfterDestroy: foreign.isConnected } + }) + expect(result).toEqual({ replacement: { connected: false, nodes: 0, foreign: true, refs: 0 }, foreignAfterDestroy: true }) + }) + + test(`${core}: narrow heatmap invalid sampling stays responsive and two instances have independent gradients`, async ({ page }, testInfo) => { + await setup(page, core, 'heatmap', testInfo) + // A second evaluation has fresh module counters but shares this document. + await page.addScriptTag({ content: candidate }) + await page.evaluate(() => { + window.createResourcePlayer(1) + const first = window.art.controls.heatmap + first.style.width = '99px' + window.art.emit('resize') + }) + await expect.poll(() => page.evaluate(() => window.resourcePlayers[1].isReady)).toBe(true) + await expect.poll(() => page.locator('.art-control-heatmap svg').count()).toBe(2) + const result = await page.evaluate(() => { + const first = window.resourcePlayers[0].controls.heatmap + const second = window.resourcePlayers[1].controls.heatmap + const gradient = node => node.querySelector('linearGradient').id + window.resourcePlayers[0].emit('artplayerPluginDanmuku:points', [[0, 0], [49, 40], [99, 10]]) + window.resourcePlayers[1].emit('artplayerPluginDanmuku:points', [[0, 20], [300, 0], [640, 35]]) + window.resourcePlayers[0].emit('setBar', 'played', 0.25) + window.resourcePlayers[1].emit('setBar', 'played', 0.75) + return { + ids: [gradient(first), gradient(second)], + fills: [first.querySelector('path').getAttribute('fill'), second.querySelector('path').getAttribute('fill')], + offsets: [first.querySelector('#heatmap-start').getAttribute('offset'), second.querySelector('#heatmap-start').getAttribute('offset')], + validPaths: [first, second].every(node => !/NaN|Infinity/.test(node.querySelector('path').getAttribute('d'))), + } + }) + expect(new Set(result.ids).size).toBe(2) + expect(result.ids[0]).toBe('heatmap-solids') + expect(result.fills).toEqual(result.ids.map(id => `url(#${id})`)) + expect(result.offsets).toEqual(['25%', '75%']) + expect(result.validPaths).toBe(true) + await testInfo.attach('danmuku-resources-heatmap', { contentType: 'application/json', body: JSON.stringify(result) }) + await testInfo.attach('danmuku-heatmap-live-page', { contentType: 'image/png', body: await page.screenshot({ fullPage: true }) }) + await page.evaluate(() => window.art.destroy(false)) + await expect(page.locator('.art-control-heatmap')).toHaveCount(1) + await page.evaluate(() => window.resourcePlayers[1].emit('resize')) + await expect(page.locator('.art-control-heatmap svg')).toHaveCount(1) + }) +} diff --git a/test/browser/danmuku-scheduler.spec.js b/test/browser/danmuku-scheduler.spec.js index 5005bbd4d..2e6f8a51c 100644 --- a/test/browser/danmuku-scheduler.spec.js +++ b/test/browser/danmuku-scheduler.spec.js @@ -133,6 +133,15 @@ async function geometry(page) { const visibleIds = page => page.evaluate(() => [...new Set(window.danmukuEvidence.events.filter(item => item.event === 'visible' && item.id).map(item => item.id))]) +async function queueReady(page, item) { + await page.evaluate(async (item) => { + const owner = await window.danmukuPlugins[0].emit(item) + // These cases verify cancellation/recovery after eligibility. Seed that + // precondition; the separate timing/geometry cases still use the media clock. + owner.setState(owner.queue[owner.queue.length - 1], 'ready') + }, item) +} + test.afterEach(async ({ page }, testInfo) => { await testInfo.attach('danmuku-layout', { contentType: 'application/json', body: JSON.stringify(await geometry(page)) }) await testInfo.attach('danmuku-native-page', { contentType: 'image/png', body: await page.screenshot() }) @@ -158,6 +167,7 @@ for (const core of ['published', 'candidate']) { test(`${label}: accepted NaN timestamp keeps the historical current-time fallback`, async ({ page }, testInfo) => { const external = await setup(page, core, format, 'modes', testInfo) + await page.evaluate(() => window.art.playbackRate = 0.25) const numeric = await page.evaluate(async () => { const input = { id: 'nan-time', text: 'NAN FALLBACK', mode: 1, time: Number.NaN } const expected = window.art.currentTime + 0.5 @@ -176,7 +186,7 @@ for (const core of ['published', 'candidate']) { for (const action of ['destroy', 'stop']) { test(`${label}: failed recovery respects synchronous ${action} from the error listener`, async ({ page }, testInfo) => { const external = await setup(page, core, format, 'modes', testInfo) - await page.evaluate(() => window.danmukuPlugins[0].emit({ id: 'recovery-row', text: 'RECOVERY', mode: 1, time: 0.5 })) + await queueReady(page, { id: 'recovery-row', text: 'RECOVERY', mode: 1, time: 0.5 }) await page.click('#play') await expect.poll(() => visibleIds(page)).toContain('recovery-row') const result = await page.evaluate((action) => { @@ -216,7 +226,10 @@ for (const core of ['published', 'candidate']) { for (const action of ['pause', 'reset', 'seek', 'destroy']) { test(`${label}: native ${action} cancels pending visibility without blocking later work`, async ({ page }, testInfo) => { const external = await setup(page, core, format, 'modes', testInfo) - await page.evaluate(() => window.danmukuPlugins[0].emit({ id: 'obsolete-gate', text: 'GATED', mode: 1, time: 0.5 })) + await page.evaluate(async () => { + window.art.playbackRate = 0.25 + await window.danmukuPlugins[0].emit({ id: 'obsolete-gate', text: 'GATED', mode: 1, time: 0.5 }) + }) await page.click('#play') await expect.poll(() => page.evaluate(() => window.danmukuEvidence.gates.some(item => item.id === 'obsolete-gate'))).toBe(true) if (action === 'pause') { @@ -237,7 +250,7 @@ for (const core of ['published', 'candidate']) { await page.evaluate(() => window.danmukuPlugins[0].load()) } if (action !== 'destroy') { - await page.evaluate(() => window.danmukuPlugins[0].emit({ id: 'fresh-after-cancel', text: 'FRESH', mode: 1, time: window.art.currentTime + 0.5 })) + await queueReady(page, { id: 'fresh-after-cancel', text: 'FRESH', mode: 1 }) if (action === 'pause') await page.click('#play') // The old user Promise is still unresolved. A cancelled frame must @@ -325,7 +338,21 @@ for (const core of ['published', 'candidate']) { test(`${label}: native pause seek rate and public reset/load retain historical pool semantics`, async ({ page }, testInfo) => { const external = await setup(page, core, format, 'playback', testInfo) await page.evaluate(async () => { - await window.danmukuPlugins[0].emit({ id: 'moving', text: 'MOVING', mode: 0, time: 0.5 }) + const owner = await window.danmukuPlugins[0].emit({ id: 'moving', text: 'MOVING', mode: 0, time: 0.5 }) + const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(owner), 'readys') + window.danmukuEvidence.eligibility = [] + Object.defineProperty(owner, 'readys', { + configurable: true, + get() { + const before = owner.art.currentTime + const rows = descriptor.get.call(owner) + const samples = window.danmukuEvidence.eligibility + if (samples.length < 2000) { + samples.push({ wall: performance.now(), before, after: owner.art.currentTime, playing: owner.art.playing, paused: owner.art.template.$video.paused, seeking: owner.art.template.$video.seeking, rate: owner.art.playbackRate, stop: owner.isStop, generation: owner.scheduler.generation, fault: owner.scheduler.fault, ready: rows.map(row => row.id), queue: owner.queue.map(row => ({ id: row.id, time: row.time, state: row.$state })) }) + } + return rows + }, + }) }) await page.click('#play') await expect.poll(() => visibleIds(page)).toContain('moving') @@ -431,8 +458,8 @@ for (const core of ['published', 'candidate']) { await expect.poll(() => page.evaluate(() => window.danmukuPlayers[1].isReady)).toBe(true) await page.evaluate(() => window.danmukuPlayers[0].destroy()) expect(await page.evaluate(() => window.danmukuEvidence.workers.map(worker => worker.terminated))).toEqual([1, 0]) - // The released plugin does not dispose external Setting DOM; retain this observation. - await expect(mount.locator('.artplayer-plugin-danmuku')).toHaveCount(1) + // The candidate owns external Setting DOM and removes it on destruction. + await expect(mount.locator('.artplayer-plugin-danmuku')).toHaveCount(0) await page.locator('#danmuku-mount-1 .apd-input').fill('SECOND') await page.locator('#danmuku-mount-1 .apd-send').click() await page.click('#play-second-danmuku') diff --git a/test/danmuku-heatmap.test.js b/test/danmuku-heatmap.test.js new file mode 100644 index 000000000..1b0aa94e5 --- /dev/null +++ b/test/danmuku-heatmap.test.js @@ -0,0 +1,352 @@ +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 -- Heatmap compatibility and isolated historical hangs use Node. +import test from 'node:test' +import { fileURLToPath } from 'node:url' +import { danmukuEnvironment, danmukuHistorical } from './helpers/danmuku.js' +import { loadModules } from './helpers/load.js' + +const { heatmap, heatmapGeometry, sampleHeatmap } = await loadModules({ + heatmap: 'packages/artplayer-plugin-danmuku/src/heatmap', + heatmapGeometry: { file: 'packages/artplayer-plugin-danmuku/src/heatmap-geometry', name: 'heatmapGeometry' }, + sampleHeatmap: { file: 'packages/artplayer-plugin-danmuku/src/heatmap-sampling', name: 'sampleHeatmap' }, +}) +const historical = await danmukuHistorical() + +function fixture({ width = 400, height = 100, duration = 60, queue = [] } = {}) { + const listeners = new Map() + const removed = [] + const element = { offsetWidth: width, offsetHeight: height, writes: 0, nodes: new Map(), removed: 0 } + let html = '' + Object.defineProperty(element, 'innerHTML', { + get: () => html, + set(value) { + html = value + element.writes++ + element.nodes = new Map(['#heatmap-start', '#heatmap-stop'].map(key => [key, { + attributes: {}, + writes: 0, + setAttribute(name, value) { + this.attributes[name] = value + this.writes++ + }, + }])) + }, + }) + const art = { + duration, + played: 0.25, + option: { isLive: false }, + constructor: { utils: { query: (selector, root) => root.nodes.get(selector) } }, + on(name, callback) { listeners.set(name, [...listeners.get(name) || [], callback]) }, + off(name, callback) { listeners.set(name, (listeners.get(name) || []).filter(item => item !== callback)) }, + emit(name, ...args) { + for (const callback of [...listeners.get(name) || []]) + callback(...args) + }, + controls: { + add(option) { + this.option = option + this.heatmap = element + option.mounted(element) + return undefined + }, + remove(name) { + assert.equal(name, 'heatmap') + this.option.beforeUnmount?.(this.heatmap) + this.heatmap.removed++ + delete this.heatmap + removed.push(name) + }, + }, + } + const danmuku = { queue } + return { + art, + danmuku, + element, + listeners, + removed, + install: option => heatmap(art, danmuku, option), + destroy() { + art.isDestroy = true + art.emit('destroy') + }, + } +} + +function pathOf(element) { + return element.innerHTML.match(/]+d="([^"]*)"/u)?.[1] +} + +function listenerCount(env) { + return [...env.listeners.values()].reduce((sum, list) => sum + list.length, 0) +} + +test('heatmap: sampled SVG path matches frozen source and actual 5.3.0 main/legacy for legal options', async () => { + const queue = [0, 0.6, 1, 2, 2, 2.1, 8, 15, 28, 59.9, 60, Number.NaN, Infinity, -Infinity].map(time => ({ time })) + for (const option of [true, { sampling: 2.5, smoothing: 0.3, flattening: 0.1, scale: 0.4, opacity: 0.7, minHeight: 2, xMin: -10, xMax: 450, yMin: -1, yMax: 40 }]) { + const candidate = fixture({ queue }) + candidate.install(option) + candidate.art.emit('ready') + assert(pathOf(candidate.element)) + for (const implementation of historical) { + const env = danmukuEnvironment(implementation, { heatmapWidth: 400 }) + const plugin = env.factory({ danmuku: [], heatmap: option })(env.art) + await env.flush() + plugin.show().queue = queue + env.art.emit('ready') + assert.equal(pathOf(candidate.element), pathOf(env.controls[0].element), `${implementation.name} / ${JSON.stringify(option)}`) + env.destroy() + } + candidate.destroy() + } +}) + +test('heatmap: custom points retain nested mutation, repeated transformation and exact historical SVG curves', async () => { + const option = { sampling: 4, scale: 0.25, minHeight: 5, smoothing: 0.2, flattening: 0.2 } + const candidate = fixture() + candidate.install(option) + const points = [[0, 10], [200, 20], [300, 0]] + const references = [...points] + const snapshots = [] + for (let index = 0; index < 2; index++) { + candidate.art.emit('artplayerPluginDanmuku:points', points) + snapshots.push({ points: structuredClone(points), path: pathOf(candidate.element) }) + } + assert.deepEqual(snapshots[0].points, [[0, 12.5], [200, 30], [300, 5]]) + assert.equal(points.length, 3, 'The closing point is appended only to the copied outer array') + assert.equal(points[1], references[1]) + for (const implementation of historical) { + const env = danmukuEnvironment(implementation, { heatmapWidth: 400 }) + env.factory({ danmuku: [], heatmap: option, points: [[0, 999], [400, 999]] })(env.art) + await env.flush() + const oldPoints = [[0, 10], [200, 20], [300, 0]] + for (const expected of snapshots) { + env.art.emit('artplayerPluginDanmuku:points', oldPoints) + assert.deepEqual(oldPoints, expected.points, implementation.name) + assert.equal(pathOf(env.controls[0].element), expected.path, implementation.name) + } + env.destroy() + } + candidate.art.emit('resize') + assert.notEqual(pathOf(candidate.element), snapshots[1].path, 'A later automatic update resamples the queue, not the previous custom points') + candidate.destroy() +}) + +test('heatmap: historical zero/default-narrow/negative sampling hangs are bounded in isolated processes', () => { + for (const implementation of historical) { + for (const scenario of [{ width: 99, option: true }, { width: 400, option: { sampling: 0 } }, { width: 400, option: { sampling: -1 } }]) { + const program = ` + import fs from 'node:fs'; + import vm from 'node:vm'; + import { danmukuEnvironment } from './test/helpers/danmuku.js'; + const { implementation, scenario } = JSON.parse(fs.readFileSync(0, 'utf8')); + const env = danmukuEnvironment(implementation, { heatmapWidth: scenario.width }); + env.factory({ danmuku: [], heatmap: scenario.option })(env.art); + const sandbox = vm.createContext({ update: () => env.art.emit('ready') }); + try { vm.runInContext('update()', sandbox, { timeout: 100 }); process.stdout.write('unexpected-completion'); } + catch (error) { if (error.code !== 'ERR_SCRIPT_EXECUTION_TIMEOUT') throw error; process.stdout.write(error.code); } + ` + const child = spawnSync(process.execPath, ['--input-type=module', '-e', program], { + cwd: fileURLToPath(new URL('../', import.meta.url)), + input: JSON.stringify({ implementation, scenario }), + encoding: 'utf8', + timeout: 5000, + maxBuffer: 1024 * 1024, + }) + assert.equal(child.error, undefined) + assert.equal(child.status, 0, child.stderr) + assert.equal(child.stdout, 'ERR_SCRIPT_EXECUTION_TIMEOUT') + } + } +}) + +test('heatmap: narrow widths and invalid sampling produce finite bounded curves without a hanging loop', () => { + for (const width of [1, 50, 99, 100, 640]) { + for (const sampling of [undefined, 0, -1, Number.NaN, Infinity, -Infinity]) { + const result = heatmapGeometry({ width, height: 100, duration: 60, queue: [{ time: 1 }], option: { sampling } }) + assert(result, `${width}/${sampling}`) + assert.doesNotMatch(result.path, /NaN|Infinity/u) + assert(result.path.length < 100000) + } + } +}) + +test('heatmap: invalid dimensions, duration and coordinate ranges leave an empty chart', () => { + const base = { width: 400, height: 100, duration: 60, queue: [] } + for (const key of ['width', 'height', 'duration']) { + for (const value of [0, -1, Number.NaN, Infinity, -Infinity]) + assert.equal(heatmapGeometry({ ...base, [key]: value }), null, `${key}/${value}`) + } + for (const option of [{ xMin: 1, xMax: 1 }, { yMin: 1, yMax: 1 }]) + assert.equal(heatmapGeometry({ ...base, option }), null) + const env = fixture() + env.install(true) + env.art.emit('ready') + assert(pathOf(env.element)) + env.art.duration = Infinity + env.art.emit('resize') + assert.equal(env.element.innerHTML, '') + env.art.duration = 60 + env.art.option.isLive = true + env.art.emit('ready') + assert.equal(env.element.innerHTML, '') + env.destroy() +}) + +test('heatmap sampling: strict-left inclusive-right bins preserve fractional step boundaries and numeric edge values', () => { + const queue = [0, -1, 0.1, 0.2, 0.3, 0.6, 1, 60, Number.NaN, Infinity, -Infinity].map(time => ({ time })) + for (const [width, duration, sampling] of [[10, 1, 0.3], [400, 60, 2.5], [100, 60, 1], [1, 1e308, 1e308]]) { + const expected = [] + const gap = duration / width + for (let x = 0; x <= width; x += sampling) + expected.push([x, queue.filter(({ time }) => time > x * gap && time <= (x + sampling) * gap).length]) + assert.deepEqual(sampleHeatmap(queue, width, duration, sampling), expected) + } +}) + +test('heatmap sampling: only unrepresentable array lengths are rejected before scanning the queue', () => { + const queue = { + map() { assert.fail('Oversized sampling must stop before reading the queue') }, + } + assert.deepEqual(sampleHeatmap(queue, 400, 60, Number.MIN_VALUE), []) + assert.deepEqual(sampleHeatmap(queue, 0xFFFFFFFF, 60, 1), []) + assert.equal(heatmapGeometry({ width: 400, height: 100, duration: 60, queue, option: { sampling: Number.MIN_VALUE } }), null) +}) + +test('heatmap sampling: legal small steps retain more than 100000 exact historical bins', () => { + const queue = [0, 0.001, 1, 50, 100, 100.001, 101].map(time => ({ time })) + const expected = [] + for (let x = 0; x <= 101; x += 0.001) + expected.push([x, queue.filter(({ time }) => time > x && time <= x + 0.001).length]) + assert(expected.length > 100000) + const actual = sampleHeatmap(queue, 101, 101, 0.001) + assert.equal(actual.length, expected.length) + assert.deepEqual(actual, expected) + assert.equal(sampleHeatmap([], 100001, 60, 1).length, 100002) +}) + +test('heatmap geometry: more than 100000 custom points retain all segments and nested y writes', () => { + const points = Array.from({ length: 100001 }, (_, index) => [index, index % 2 ? 20 : 10]) + const first = points[0] + const result = heatmapGeometry({ width: 100001, height: 100, duration: 60, queue: [], points }) + assert(result) + assert.equal(points.length, 100001) + assert.equal(points[0], first) + assert.deepEqual(points[0], [0, 12.5]) + assert.deepEqual(points[1], [1, 30]) + assert.equal(result.path.match(/C /gu).length, 100001, 'Every caller segment and the closing width point remain') + assert.doesNotMatch(result.path, /NaN|Infinity/u) + assert(result.path.endsWith(' z')) +}) + +test('heatmap: malformed points do not generate NaN SVG or change caller data', () => { + for (const points of [[undefined], [[1]], [[0, Number.NaN]], [[Infinity, 2]], [{ time: 1, value: 2 }]]) { + const original = structuredClone(points) + assert.equal(heatmapGeometry({ width: 400, height: 100, duration: 60, queue: [], points }), null) + assert.deepEqual(points, original) + } +}) + +test('heatmap: instances own different gradients while preserving local stop hooks and progress updates', () => { + const a = fixture() + const b = fixture() + a.install(true) + b.install({ opacity: 0.7 }) + a.art.emit('ready') + b.art.emit('ready') + const gradient = env => env.element.innerHTML.match(/linearGradient id="([^"]+)"/u)[1] + assert.notEqual(gradient(a), gradient(b)) + for (const env of [a, b]) { + assert(env.element.innerHTML.includes(`fill="url(#${gradient(env)})"`)) + assert.equal(env.element.nodes.get('#heatmap-start').attributes.offset, '25%') + assert.equal(env.art.controls.option.name, 'heatmap') + assert.equal(env.art.controls.option.position, 'top') + } + a.art.played = 0.5 + a.art.emit('video:timeupdate') + assert.equal(a.element.nodes.get('#heatmap-start').attributes.offset, '50%') + assert.equal(b.element.nodes.get('#heatmap-start').attributes.offset, '25%') + a.art.emit('setBar', 'loaded', 0.9) + assert.equal(a.element.nodes.get('#heatmap-stop').attributes.offset, '50%') + a.art.emit('setBar', 'played', 0.9) + assert.equal(a.element.nodes.get('#heatmap-stop').attributes.offset, '90%') + a.destroy() + assert.equal(listenerCount(a), 0) + assert.deepEqual(a.removed, ['heatmap']) + b.art.emit('resize') + assert(pathOf(b.element)) + assert.equal(b.removed.length, 0) + b.destroy() +}) + +test('heatmap: control unmount detaches all subscriptions and stale callbacks cannot write', () => { + const env = fixture() + const dispose = env.install(true) + env.art.emit('ready') + const oldCallbacks = [...env.listeners].map(([name, callbacks]) => [name, [...callbacks]]) + const oldStops = [...env.element.nodes.values()] + env.art.controls.remove('heatmap') + assert.equal(listenerCount(env), 0) + const writes = env.element.writes + const stopWrites = oldStops.map(node => node.writes) + for (const [name, callbacks] of oldCallbacks) { + for (const callback of callbacks) { + if (name === 'artplayerPluginDanmuku:points') + callback([[0, 10], [400, 20]]) + else if (name === 'setBar') + callback('played', 0.9) + else callback() + } + } + dispose() + assert.equal(env.element.writes, writes) + assert.deepEqual(oldStops.map(node => node.writes), stopWrites) + assert.deepEqual(env.removed, ['heatmap']) +}) + +test('heatmap: destroy preserves a later same-name control and cleanup attempts every owned removal', () => { + const env = fixture() + const dispose = env.install(true) + const replacement = { removed: 0 } + env.art.controls.heatmap = replacement + const originalOff = env.art.off.bind(env.art) + const failure = new Error('off-original') + let attempts = 0 + env.art.off = (...args) => { + originalOff(...args) + attempts++ + if (attempts === 1) + throw failure + } + assert.throws(dispose, error => error === failure) + assert.equal(attempts, 7) + assert.equal(listenerCount(env), 0) + assert.equal(env.art.controls.heatmap, replacement) + assert.equal(replacement.removed, 0) + dispose() +}) + +test('heatmap: setup and update failures clean subscriptions/control and preserve their original errors', () => { + const first = fixture() + const setupError = new Error('add-original') + first.art.controls.add = () => { + throw setupError + } + assert.throws(() => first.install(true), error => error === setupError) + assert.equal(listenerCount(first), 0) + const second = fixture() + second.install(true) + const geometryError = new Error('points-original') + const point = [0, 1] + Object.defineProperty(point, '1', { + get: () => 1, + set() { throw geometryError }, + }) + assert.throws(() => second.art.emit('artplayerPluginDanmuku:points', [point, [400, 2]]), error => error === geometryError) + assert.equal(listenerCount(second), 0) + assert.deepEqual(second.removed, ['heatmap']) + assert.equal(second.art.controls.heatmap, undefined) +}) diff --git a/test/danmuku-renderer.test.js b/test/danmuku-renderer.test.js new file mode 100644 index 000000000..4183ca79e --- /dev/null +++ b/test/danmuku-renderer.test.js @@ -0,0 +1,115 @@ +import assert from 'node:assert/strict' +// eslint-disable-next-line test/no-import-node-test -- Deterministic owned-node tests use the Node runner. +import test from 'node:test' +import { parseHTML } from 'linkedom' +import Renderer from '../packages/artplayer-plugin-danmuku/src/renderer.js' + +function fixture(t) { + const { document } = parseHTML('
') + const previous = globalThis.document + globalThis.document = document + t.after(() => { + if (previous === undefined) + delete globalThis.document + else + globalThis.document = previous + }) + const owner = { + constructor: { cssText: 'visibility:hidden;position:absolute' }, + $danmuku: document.querySelector('#layer'), + $refs: [], + queue: [], + option: { opacity: 0.7 }, + fontSize: 20, + utils: { setStyles: (node, styles) => Object.assign(node.style, styles) }, + } + const renderer = new Renderer(owner) + Object.defineProperty(owner, '$ref', { get: () => renderer.acquire() }) + return { owner, renderer, document } +} + +test('Danmuku renderer destroys only owned nodes, including moved and idle nodes, and clears retained references', (t) => { + const { owner, renderer, document } = fixture(t) + const active = renderer.acquire() + const idle = renderer.acquire() + owner.$danmuku.append(active, idle) + owner.queue.push({ $ref: active }) + owner.$refs.push(idle) + document.body.append(active) + renderer.destroy() + assert.equal(active.parentElement, null) + assert.equal(idle.parentElement, null) + assert.equal(owner.$danmuku.firstElementChild.id, 'foreign') + assert.equal(owner.queue[0].$ref, null) + assert.equal(owner.$refs.length, 0) + assert.equal(renderer.nodes.size, 0) + renderer.destroy() + assert.equal(owner.$danmuku.childElementCount, 1) +}) + +test('Danmuku renderer cancellation cannot recycle a different operation node', (t) => { + const { owner, renderer } = fixture(t) + const old = renderer.acquire() + const current = renderer.acquire() + const row = { $ref: current } + const operation = { danmu: row, ref: old } + renderer.release(operation) + assert.equal(row.$ref, current) + assert.equal(operation.ref, null) + assert.deepEqual(owner.$refs, []) + const actual = { danmu: row, ref: current } + renderer.release(actual) + renderer.release(actual) + assert.deepEqual(owner.$refs, [current]) + assert.equal(row.$ref, null) + assert.equal(current.style.visibility, 'hidden') +}) + +test('Danmuku renderer reuse clears custom style and identity before preparing the next row', (t) => { + const { owner, renderer } = fixture(t) + const first = { text: 'first', color: '#fff', border: true, style: { letterSpacing: '3px' } } + const operation = {} + const ref = renderer.prepare(first, operation) + ref.dataset.id = 'old' + ref.className = 'old-class' + assert.equal(ref.style.letterSpacing, '3px') + renderer.release(operation) + const second = { text: 'second', color: '#000', style: {} } + const next = renderer.prepare(second, {}) + assert.equal(next, ref) + assert.equal(next.textContent, 'second') + assert.equal(next.dataset.id, '') + assert.equal(next.className, '') + assert.equal(next.style.letterSpacing || '', '') + assert.equal(next.style.visibility, 'hidden') + assert.equal(owner.$danmuku.querySelectorAll('div').length, 1) +}) + +test('Danmuku renderer replacement clears the historical whole layer and moved owned nodes before starting a new pool', (t) => { + const { owner, renderer, document } = fixture(t) + const old = renderer.acquire() + const moved = renderer.acquire() + const foreign = document.createElement('aside') + owner.$danmuku.append(old) + document.body.append(moved, foreign) + owner.$refs.push(old) + renderer.clear() + assert.equal(owner.$danmuku.childElementCount, 0) + assert.equal(old.parentElement, null) + assert.equal(moved.isConnected, false) + assert.equal(foreign.isConnected, true) + assert.equal(renderer.nodes.size, 0) + assert.equal(owner.$refs.length, 0) + assert.notEqual(renderer.acquire(), old) + renderer.destroy() + assert.equal(foreign.isConnected, true) +}) + +test('Danmuku renderer left uses the existing auto-orientation axis', (t) => { + const { owner, renderer } = fixture(t) + const node = renderer.acquire() + node.getBoundingClientRect = () => ({ top: 31, left: 72 }) + assert.equal(renderer.left(node), 72) + owner.isRotate = true + assert.equal(renderer.left(node), 31) +}) diff --git a/test/danmuku-setting.test.js b/test/danmuku-setting.test.js new file mode 100644 index 000000000..8d59103f1 --- /dev/null +++ b/test/danmuku-setting.test.js @@ -0,0 +1,411 @@ +import assert from 'node:assert/strict' +// eslint-disable-next-line test/no-import-node-test -- Real-template controlled DOM regressions use the Node runner. +import test from 'node:test' +import { danmukuCandidate } from './helpers/danmuku-candidate.js' +import { danmukuSettingEnvironment } from './helpers/danmuku-setting.js' +import { danmukuHistorical, deferred } from './helpers/danmuku.js' + +const candidate = await danmukuCandidate() +const historical = await danmukuHistorical() +const emitted = env => env.events.filter(event => event.name === 'artplayerPluginDanmuku:emit') + +function fixture(implementation = candidate, option = {}, environment = {}) { + const env = danmukuSettingEnvironment(implementation, environment) + const plugin = env.factory({ danmuku: [], mount: '#external', ...option })(env.art) + return { env, plugin, owner: plugin.show(), ui: env.query('.artplayer-plugin-danmuku') } +} + +for (const implementation of [...historical, candidate]) { + test(`Danmuku Setting ${implementation.name}: static icons and the actual template retain published structure`, () => { + const { env, plugin, ui } = fixture(implementation) + const reference = danmukuSettingEnvironment(historical[0]).factory.icons + assert.deepEqual(Object.keys(env.factory.icons), Object.keys(reference)) + for (const key of Object.keys(reference)) + assert.equal(env.factory.icons[key], reference[key]) + assert.equal(Object.getOwnPropertyDescriptor(env.factory, 'icons').writable, true) + assert.equal(ui.parentElement.id, 'external') + assert.equal(ui.querySelectorAll('.apd-config-mode .apd-mode').length, 3) + assert.equal(ui.querySelectorAll('.apd-style-mode .apd-mode').length, 3) + assert.equal(ui.querySelectorAll('.apd-color').length, 14) + assert.equal(ui.querySelectorAll('.apd-slider').length, 4) + assert.equal(ui.querySelectorAll('.apd-config-speed .apd-slider-step').length, 3) + assert.equal(ui.querySelector('.apd-input').getAttribute('maxLength'), String(plugin.option.maxLength)) + assert.equal(env.query('#unrelated').textContent, 'Keep me') + env.destroy() + }) + + test(`Danmuku Setting ${implementation.name}: mount/fullscreen and responsive moves preserve one actual UI subtree`, () => { + const { env, plugin, ui } = fixture(implementation) + assert.equal(plugin.mount('#second'), undefined) + assert.equal(ui.parentElement.id, 'second') + assert.equal(env.query('#external').children.length, 1) + for (const type of ['fullscreen', 'fullscreenWeb']) { + env.art.emit(type, true) + assert.equal(ui.parentElement, env.art.template.$controlsCenter) + env.art.emit(type, false) + assert.equal(ui.parentElement.id, 'second') + } + assert.equal(plugin.mount(env.art.template.$controlsCenter), undefined) + env.art.width = 400 + env.art.emit('resize') + assert.equal(ui.parentElement, env.art.template.$player) + env.art.width = 640 + env.art.emit('resize') + assert.equal(ui.parentElement, env.art.template.$controlsCenter) + assert.equal(env.document.querySelectorAll('.artplayer-plugin-danmuku').length, 1) + env.destroy() + }) + + test(`Danmuku Setting ${implementation.name}: Slider clicks, pointer dragging and rotation update actual controls`, () => { + const { env, plugin, ui } = fixture(implementation) + const opacity = ui.querySelector('.apd-config-opacity .apd-slider') + opacity.testRect = { left: 20, top: 30, width: 100, height: 200 } + env.dispatch(opacity, 'click', { clientX: 45 }) + assert.equal(plugin.option.opacity, 0.25) + assert.equal(ui.querySelector('.apd-config-opacity .apd-value').textContent, '25%') + assert.equal(opacity.querySelector('.apd-slider-dot').style.left, '25%') + env.dispatch(opacity, 'pointerdown', { button: 0 }) + env.art.emit('document:pointermove', { clientX: 90 }) + assert.equal(plugin.option.opacity, 0.7) + env.art.emit('document:pointerup', { clientX: 120 }) + assert.equal(plugin.option.opacity, 1) + env.art.emit('document:pointermove', { clientX: 20 }) + assert.equal(plugin.option.opacity, 1) + env.art.isRotate = true + env.dispatch(opacity, 'click', { clientY: 130 }) + assert.equal(plugin.option.opacity, 0.5) + const speed = ui.querySelector('.apd-config-speed .apd-slider') + speed.testRect = { width: 100, height: 100 } + env.dispatch(speed, 'click', { clientY: 25 }) + assert.equal(plugin.option.speed, 7.5, 'Hidden step labels do not remove numeric slider indices') + assert.equal(ui.querySelector('.apd-config-speed .apd-value').textContent, '\u8F83\u6162') + env.destroy() + }) + + test(`Danmuku Setting ${implementation.name}: beforeEmit receiver, strict true acceptance and local original rejection stay compatible`, async () => { + const gate = deferred() + let receiver + let inputDanmu + const { env, plugin, owner, ui } = fixture(implementation, { + beforeEmit(danmu) { + receiver = this + inputDanmu = danmu + return gate.promise + }, + }) + const input = ui.querySelector('.apd-input') + input.value = ' accepted text ' + env.dispatch(ui.querySelector('.apd-send'), 'click') + assert.equal(receiver, plugin.option) + assert.equal(inputDanmu.text, 'accepted text') + assert.equal(inputDanmu.time, 10) + gate.resolve(true) + await env.flush() + assert.equal(owner.queue.length, 1) + assert.equal(owner.queue[0].border, true) + assert.equal(owner.queue[0].time, 10.5) + assert.equal(input.value, '') + assert.equal(env.timers.size, 1) + assert.equal(ui.querySelector('.apd-send').classList.contains('apd-lock'), true) + env.destroy() + + const error = new Error('beforeEmit original') + const denied = fixture(implementation, { beforeEmit: () => Promise.reject(error) }) + denied.ui.querySelector('.apd-input').value = 'retained' + denied.env.dispatch(denied.ui.querySelector('.apd-send'), 'click') + await denied.env.flush() + assert.equal(denied.owner.queue.length, 0) + assert.equal(denied.ui.querySelector('.apd-input').value, 'retained') + assert.equal(denied.env.timers.size, 0) + assert.deepEqual(denied.env.consoleErrors, [['Error emitting danmuku:', error]]) + assert.equal(denied.env.events.filter(event => event.name === 'artplayerPluginDanmuku:error').length, 0) + denied.plugin.config({ beforeEmit: () => 1 }) + denied.env.dispatch(denied.ui.querySelector('.apd-send'), 'click') + await denied.env.flush() + assert.equal(denied.owner.queue.length, 0) + assert.equal(denied.env.timers.size, 0) + denied.env.destroy() + }) +} + +test(`Danmuku Setting ${candidate.name}: destroy removes external UI, subscriptions, proxy handlers and pending lock work`, async () => { + const { env, ui } = fixture() + const input = ui.querySelector('.apd-input') + input.value = 'send once' + env.dispatch(ui.querySelector('.apd-send'), 'click') + await env.flush() + assert.equal(env.timers.size, 1) + assert.ok(env.listenerCount() > 0) + assert.ok(env.proxyCount() > 0) + env.destroy() + assert.equal(ui.parentElement, null) + assert.equal(env.query('#unrelated').textContent, 'Keep me') + assert.equal(env.timers.size, 0) + assert.equal(env.listenerCount(), 0) + assert.equal(env.proxyCount(), 0) + const events = env.events.length + env.dispatch(ui.querySelector('.apd-toggle'), 'click') + env.dispatch(ui.querySelector('.apd-send'), 'click') + await env.flush() + assert.equal(env.events.length, events) + assert.ok(env.workers.every(worker => worker.terminated)) +}) + +test(`Danmuku Setting ${candidate.name}: destroy cancels pending beforeEmit without sending, clearing input or locking`, async () => { + const gate = deferred() + const { env, owner, ui } = fixture(candidate, { beforeEmit: () => gate.promise }) + const input = ui.querySelector('.apd-input') + input.value = 'pending user text' + env.dispatch(ui.querySelector('.apd-send'), 'click') + env.destroy() + const count = emitted(env).length + gate.resolve(true) + await env.flush() + assert.equal(owner.queue.length, 0) + assert.equal(emitted(env).length, count) + assert.equal(input.value, 'pending user text') + assert.equal(ui.querySelector('.apd-send').classList.contains('apd-lock'), false) + assert.equal(env.timers.size, 0) +}) + +test(`Danmuku Setting ${candidate.name}: invalid initial mount rolls back template, Worker, subscriptions and proxies`, () => { + const env = danmukuSettingEnvironment(candidate) + assert.throws(() => env.factory({ danmuku: [], mount: '#missing' })(env.art), /Can not find the mount point: #missing/u) + assert.equal(env.listenerCount(), 0) + assert.equal(env.proxyCount(), 0) + assert.equal(env.timers.size, 0) + assert.equal(env.document.querySelectorAll('.artplayer-plugin-danmuku').length, 0) + assert.ok(env.workers.every(worker => worker.terminated)) + assert.equal(env.query('#unrelated').textContent, 'Keep me') +}) + +test(`Danmuku Setting ${candidate.name}: late DOMContentLoaded installs only the shared style and releases its listener after instance destruction`, () => { + const { env, ui } = fixture(candidate, {}, { readyState: 'loading' }) + assert.equal(env.document.getElementById('artplayer-plugin-danmuku'), null) + env.destroy() + env.dispatch(env.document, 'DOMContentLoaded') + const style = env.document.getElementById('artplayer-plugin-danmuku') + assert.ok(style) + assert.ok(style.textContent.includes('.artplayer-plugin-danmuku')) + assert.equal(ui.parentElement, null) + assert.equal(env.proxyCount(), 0) + assert.equal(env.documentListeners.filter(record => record.type === 'DOMContentLoaded' && record.active).length, 0) + env.dispatch(env.document, 'DOMContentLoaded') + assert.equal(env.document.querySelectorAll('#artplayer-plugin-danmuku').length, 1) +}) + +test(`Danmuku Setting ${candidate.name}: repeated bundle evaluation shares pending style installation`, () => { + const env = danmukuSettingEnvironment(candidate, { readyState: 'loading' }) + env.reloadPlugin() + env.reloadPlugin() + assert.equal(env.documentListeners.filter(record => record.type === 'DOMContentLoaded' && record.active).length, 1) + env.dispatch(env.document, 'DOMContentLoaded') + assert.equal(env.document.querySelectorAll('#artplayer-plugin-danmuku').length, 1) + assert.equal(env.documentListeners.filter(record => record.type === 'DOMContentLoaded' && record.active).length, 0) + env.reloadPlugin() + assert.equal(env.document.querySelectorAll('#artplayer-plugin-danmuku').length, 1) + assert.equal(env.documentListeners.filter(record => record.type === 'DOMContentLoaded' && record.active).length, 0) +}) + +test(`Danmuku Setting ${candidate.name}: Enter starts one pending send and preserves a synchronous destroy decision`, async () => { + let calls = 0 + const env = danmukuSettingEnvironment(candidate) + const plugin = env.factory({ + danmuku: [], + beforeEmit() { + calls++ + env.destroy() + return true + }, + })(env.art) + const input = env.query('.apd-input') + const send = env.query('.apd-send') + input.value = 'retain on synchronous destroy' + const keypress = env.event('keypress', { key: 'Enter' }) + input.dispatchEvent(keypress) + await env.flush() + assert.equal(keypress.defaultPrevented, true) + assert.equal(calls, 1) + assert.equal(plugin.show().queue.length, 0) + assert.equal(input.value, 'retain on synchronous destroy') + assert.equal(send.classList.contains('apd-lock'), false) + assert.equal(env.timers.size, 0) + assert.equal(env.proxyCount(), 0) +}) + +test(`Danmuku Setting ${candidate.name}: pending sends are deduplicated and a rejection after destroy is observed without late UI work`, async () => { + const gate = deferred() + let calls = 0 + const { env, ui } = fixture(candidate, { + beforeEmit() { + calls++ + return gate.promise + }, + }) + const input = ui.querySelector('.apd-input') + input.value = 'pending rejection' + env.dispatch(ui.querySelector('.apd-send'), 'click') + env.dispatch(input, 'keypress', { key: 'Enter' }) + assert.equal(calls, 1) + env.destroy() + gate.reject(new Error('cancelled beforeEmit rejection')) + await env.flush() + assert.equal(input.value, 'pending rejection') + assert.equal(env.timers.size, 0) + assert.equal(emitted(env).length, 0) + assert.equal(env.consoleErrors.length, 0) +}) + +test(`Danmuku Setting ${candidate.name}: rejection from the invoked emit Promise is observed with the original error`, async () => { + const error = new Error('filter failed during accepted send') + const { env, ui } = fixture(candidate, { + filter() { + throw error + }, + }) + const input = ui.querySelector('.apd-input') + input.value = 'accepted but rejected by filter' + env.dispatch(ui.querySelector('.apd-send'), 'click') + await env.flush() + assert.deepEqual(env.consoleErrors, [['Error emitting danmuku:', error]]) + assert.equal(env.events.filter(event => event.name === 'artplayerPluginDanmuku:error').length, 0) + assert.equal(input.value, '', 'Sending still clears and locks immediately after invoking emit') + assert.equal(env.timers.size, 1) + env.destroy() +}) + +test(`Danmuku Setting ${candidate.name}: a complete lock countdown retains its existing delay and restores the actual send control`, async () => { + const { env, ui } = fixture(candidate, { lockTime: 1 }) + ui.querySelector('.apd-input').value = 'countdown' + const send = ui.querySelector('.apd-send') + env.dispatch(send, 'click') + await env.flush() + assert.equal(send.textContent, '1') + assert.equal([...env.timers.values()][0].delay, 1000) + env.timeout() + assert.equal(send.textContent, '0') + assert.equal(send.classList.contains('apd-lock'), true) + env.timeout() + assert.equal(send.textContent, '\u53D1\u9001') + assert.equal(send.classList.contains('apd-lock'), false) + assert.equal(env.timers.size, 0) + env.destroy() +}) + +for (const first of ['older', 'newer']) { + test(`Danmuku Setting ${candidate.name}: shared mount restores remaining owner and original host data when ${first} owner is destroyed first`, () => { + const env = danmukuSettingEnvironment(candidate) + const external = env.query('#external') + external.dataset.danmukuMode = 'host mode' + env.art.template.$controlsCenter.style.display = 'grid' + const peer = env.createPeer() + peer.art.template.$controlsCenter.style.display = 'block' + env.factory({ danmuku: [], mount: '#external', mode: 0 })(env.art) + env.factory({ danmuku: [], mount: '#external', mode: 2 })(peer.art) + assert.equal(external.dataset.danmukuMode, '2') + assert.equal(external.querySelectorAll('.artplayer-plugin-danmuku').length, 2) + if (first === 'older') { + env.destroy() + assert.equal(external.dataset.danmukuMode, '2') + peer.destroy() + } + else { + peer.destroy() + assert.equal(external.dataset.danmukuMode, '0') + env.destroy() + } + assert.equal(external.dataset.danmukuMode, 'host mode') + assert.equal(external.hasAttribute('data-danmuku-visible'), false) + assert.equal(external.querySelectorAll('.artplayer-plugin-danmuku').length, 0) + assert.equal(env.art.template.$controlsCenter.style.display, 'grid') + assert.equal(peer.art.template.$controlsCenter.style.display, 'block') + assert.equal(env.query('#unrelated').textContent, 'Keep me') + }) +} + +test(`Danmuku Setting ${candidate.name}: destroy preserves host dataset and display values changed after initialization`, () => { + const env = danmukuSettingEnvironment(candidate) + const external = env.query('#external') + external.dataset.danmukuMode = 'original host value' + external.dataset.getPropertyPriority = 'a host data attribute, not a method' + env.factory({ danmuku: [], mount: '#external' })(env.art) + external.dataset.danmukuMode = 'new host value' + external.dataset.danmukuVisible = 'host visibility' + env.art.template.$controlsCenter.style.display = 'inline-grid' + env.destroy() + assert.equal(external.dataset.danmukuMode, 'new host value') + assert.equal(external.dataset.danmukuVisible, 'host visibility') + assert.equal(env.art.template.$controlsCenter.style.display, 'inline-grid') + assert.equal(external.hasAttribute('data-danmuku-color'), false) + assert.equal(external.dataset.getPropertyPriority, 'a host data attribute, not a method') +}) + +test(`Danmuku Setting ${candidate.name}: a disposer failure preserves the original initialization error and cleans other resources`, () => { + const env = danmukuSettingEnvironment(candidate) + const proxy = env.art.proxy + const cleanupError = new Error('one proxy disposer failed') + let shouldThrow = true + env.art.proxy = (...args) => { + const dispose = proxy(...args) + return () => { + dispose() + if (shouldThrow) { + shouldThrow = false + throw cleanupError + } + } + } + assert.throws(() => env.factory({ danmuku: [], mount: '#missing' })(env.art), /Can not find the mount point: #missing/u) + assert.equal(env.proxyCount(), 0) + assert.equal(env.listenerCount(), 0) + assert.ok(env.workers.every(worker => worker.terminated)) + assert.ok(env.consoleWarnings.some(args => args.includes(cleanupError))) +}) + +test(`Danmuku Setting ${candidate.name}: synchronous destroy during proxy registration leaves an inert mount facade`, () => { + const env = danmukuSettingEnvironment(candidate) + const proxy = env.art.proxy + let first = true + env.art.proxy = (...args) => { + const dispose = proxy(...args) + if (first) { + first = false + env.destroy() + } + return dispose + } + const plugin = env.factory({ danmuku: [], mount: '#external' })(env.art) + assert.equal(plugin.mount('#second'), undefined) + assert.equal(env.document.querySelectorAll('.artplayer-plugin-danmuku').length, 0) + assert.equal(env.proxyCount(), 0) + assert.equal(env.listenerCount(), 0) + assert.equal(env.timers.size, 0) + assert.ok(env.workers.every(worker => worker.terminated)) +}) + +for (const implementation of historical) { + test(`Danmuku Setting historical defect ${implementation.name}: destroy retains external UI, subscriptions, proxies and lock timer`, async () => { + const { env, ui } = fixture(implementation) + ui.querySelector('.apd-input').value = 'historical lock' + env.dispatch(ui.querySelector('.apd-send'), 'click') + await env.flush() + env.destroy() + assert.equal(ui.parentElement.id, 'external') + assert.ok(env.listenerCount() > 0) + assert.ok(env.proxyCount() > 0) + assert.equal(env.timers.size, 1) + }) + + test(`Danmuku Setting historical defect ${implementation.name}: late beforeEmit still clears input and locks after destroy`, async () => { + const gate = deferred() + const { env, ui } = fixture(implementation, { beforeEmit: () => gate.promise }) + ui.querySelector('.apd-input').value = 'historical pending' + env.dispatch(ui.querySelector('.apd-send'), 'click') + env.destroy() + gate.resolve(true) + await env.flush() + assert.equal(ui.querySelector('.apd-input').value, '') + assert.equal(env.timers.size, 1) + assert.equal(ui.querySelector('.apd-send').classList.contains('apd-lock'), true) + }) +} diff --git a/test/helpers/danmuku-candidate.js b/test/helpers/danmuku-candidate.js index 641384861..6e6c2ce43 100644 --- a/test/helpers/danmuku-candidate.js +++ b/test/helpers/danmuku-candidate.js @@ -33,6 +33,24 @@ export function danmukuCandidateEnvironment(implementation, settings = {}) { ? { ...implementation, code: `globalThis.Worker = class { constructor() { throw globalThis.__danmukuCandidateWorkerError } };\n${implementation.code}` } : implementation const env = danmukuEnvironment(initialWorker, settings) + const createElement = env.context.document.createElement.bind(env.context.document) + const removeChild = function (child) { + const index = this.children.indexOf(child) + if (index < 0) + throw new Error('Cannot remove a node from a different parent') + this.children.splice(index, 1) + child.parentElement = null + return child + } + env.context.document.createElement = (...args) => { + const element = createElement(...args) + element.removeChild = removeChild + return element + } + for (const element of Object.values(env.art.template)) { + if (element?.children) + element.removeChild = removeChild + } if (settings.initialWorkerError) env.context.__danmukuCandidateWorkerError = settings.initialWorkerError const consoleWarnings = [] diff --git a/test/helpers/danmuku-setting.js b/test/helpers/danmuku-setting.js new file mode 100644 index 000000000..5f966cbe1 --- /dev/null +++ b/test/helpers/danmuku-setting.js @@ -0,0 +1,122 @@ +import assert from 'node:assert/strict' +import vm from 'node:vm' +import { parseHTML } from 'linkedom' +import { danmukuCandidateEnvironment } from './danmuku-candidate.js' + +export function danmukuSettingEnvironment(implementation, { readyState = 'complete' } = {}) { + const env = danmukuCandidateEnvironment(implementation) + // Load actual core utilities without initializing the core's browser banner/style. + delete env.context.document + const core = evaluate(implementation.coreCode) + const { document, window } = parseHTML('
Keep me
') + Object.defineProperty(document, 'readyState', { value: readyState, configurable: true }) + // Linkedom parses the real templates and dispatches events, but has no layout. + Object.defineProperties(window.HTMLElement.prototype, { + clientWidth: { configurable: true, get() { return this.testRect?.width ?? 640 } }, + clientHeight: { configurable: true, get() { return this.testRect?.height ?? 360 } }, + offsetWidth: { configurable: true, get() { return this.clientWidth } }, + offsetHeight: { configurable: true, get() { return this.clientHeight } }, + }) + window.HTMLElement.prototype.getBoundingClientRect = function () { + const { left = 0, top = 0, width = 640, height = 360 } = this.testRect || {} + return { x: left, y: top, left, top, width, height, right: left + width, bottom: top + height } + } + env.context.document = document + env.context.Element = window.Element + env.context.HTMLElement = window.HTMLElement + env.context.window.document = document + env.context.window.getComputedStyle = element => element.style + env.context.getComputedStyle = element => element.style + const documentListeners = [] + const add = document.addEventListener.bind(document) + const remove = document.removeEventListener.bind(document) + document.addEventListener = (type, callback, options) => { + const record = { type, callback, active: true, options } + documentListeners.push(record) + record.wrapped = (...args) => { + if (options?.once) + record.active = false + return callback(...args) + } + add(type, record.wrapped, options) + } + document.removeEventListener = (type, callback, options) => { + for (const record of documentListeners) { + if (record.type === type && record.callback === callback && record.active) { + record.active = false + remove(type, record.wrapped, options) + } + } + } + function evaluate(code) { + env.context.__settingModule = { exports: {} } + vm.runInContext(`(function(module, exports) {\n${code}\n})(__settingModule, __settingModule.exports)`, env.context, { timeout: 1000 }) + return env.context.__settingModule.exports + } + env.art.constructor.utils = core.utils + env.art.template = { + $player: document.querySelector('#player'), + $controlsCenter: document.querySelector('#controls'), + $danmuku: document.querySelector('#danmuku'), + } + const exported = evaluate(implementation.code) + env.factory = typeof exported === 'function' ? exported : exported.default + env.proxies.length = 0 + env.art.proxy = (target, type, callback, options) => { + assert.ok(target?.addEventListener, `A real template target is required for ${type}`) + const record = { target, type, callback, options, active: true } + env.proxies.push(record) + target.addEventListener(type, callback, options) + record.dispose = () => { + record.active = false + target.removeEventListener(type, callback, options) + } + return record.dispose + } + env.art.events = { remove: dispose => dispose() } + const event = (type, values = {}) => Object.assign(new window.Event(type, { bubbles: true, cancelable: true }), values) + return { + ...env, + document, + domWindow: window, + documentListeners, + event, + query: selector => document.querySelector(selector), + reloadPlugin() { return evaluate(implementation.code) }, + createPeer() { + const peerListeners = new Map() + const player = document.createElement('div') + const controls = document.createElement('div') + const danmuku = document.createElement('div') + player.append(controls, danmuku) + document.body.appendChild(player) + const art = { + ...env.art, + isDestroy: false, + template: { $player: player, $controlsCenter: controls, $danmuku: danmuku }, + on(name, callback) { peerListeners.set(name, [...peerListeners.get(name) || [], callback]) }, + off(name, callback) { peerListeners.set(name, (peerListeners.get(name) || []).filter(item => item !== callback)) }, + emit(name, ...args) { + for (const callback of [...peerListeners.get(name) || []]) + callback(...args) + }, + } + return { + art, + destroy() { + art.isDestroy = true + art.emit('destroy') + }, + } + }, + listenerCount: () => [...env.listeners.values()].reduce((count, callbacks) => count + callbacks.length, 0), + proxyCount: () => env.proxies.filter(record => record.active).length, + dispatch(target, type, values) { return target.dispatchEvent(event(type, values)) }, + timeout() { + const [id, timer] = env.timers.entries().next().value || [] + assert.ok(timer, 'Expected a pending controlled timer') + env.timers.delete(id) + timer.callback() + }, + } +} diff --git a/yarn.lock b/yarn.lock index d42494ce0..f51582bb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2431,6 +2431,11 @@ boolbase@^1.0.0: resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +boolbase@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-2.0.0.tgz#832968d211d5c008d833b86e049bb3943036e5b1" + integrity sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA== + brace-expansion@^1.1.7: version "1.1.12" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz" @@ -2969,6 +2974,17 @@ css-select@^6.0.0: domutils "^3.2.2" nth-check "^2.1.1" +css-select@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-7.0.0.tgz#7532bab5977b1f0e83bc16036661dbe0968b4dec" + integrity sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g== + dependencies: + boolbase "^2.0.0" + css-what "^8.0.0" + domhandler "^6.0.1" + domutils "^4.0.2" + nth-check "^3.0.1" + css-tree@^3.0.1: version "3.1.0" resolved "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz" @@ -2990,6 +3006,11 @@ css-what@^7.0.0: resolved "https://registry.yarnpkg.com/css-what/-/css-what-7.0.0.tgz#5796fbebd43571d73c60ba0dd7a6e75dd0d22fe4" integrity sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ== +css-what@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-8.0.0.tgz#237889d60c4afef4736892f39e133ab4c8c21f0c" + integrity sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" @@ -3002,6 +3023,11 @@ csso@^5.0.5: dependencies: css-tree "~2.2.0" +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + csstype@^3.1.3, csstype@^3.2.3: version "3.2.3" resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz" @@ -3112,11 +3138,25 @@ dom-serializer@^2.0.0: domhandler "^5.0.2" entities "^4.2.0" +dom-serializer@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-3.1.1.tgz#54be70ee4fcc2da010f488165e62294798dc57d3" + integrity sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw== + dependencies: + domelementtype "^3.0.0" + domhandler "^6.0.0" + entities "^8.0.0" + domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== +domelementtype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-3.0.0.tgz#e6c0a24bd39ca5eb7ea67a98d2f699497d7bcc55" + integrity sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg== + domhandler@^5.0.2, domhandler@^5.0.3: version "5.0.3" resolved "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz" @@ -3124,6 +3164,13 @@ domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" +domhandler@^6.0.0, domhandler@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-6.0.1.tgz#75f351a03a6e10c35e08418f9cf0d287e00797cf" + integrity sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg== + dependencies: + domelementtype "^3.0.0" + domutils@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78" @@ -3133,6 +3180,15 @@ domutils@^3.2.2: domelementtype "^2.3.0" domhandler "^5.0.3" +domutils@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-4.0.2.tgz#0c1ac1fdbe8f554a60a6f5eb143ee85630327c94" + integrity sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA== + dependencies: + dom-serializer "^3.0.0" + domelementtype "^3.0.0" + domhandler "^6.0.0" + dot-prop@^5.1.0: version "5.3.0" resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz" @@ -3260,6 +3316,11 @@ entities@^7.0.1: resolved "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz" integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== +entities@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-8.1.0.tgz#9632d69619ebdbe0cf0ef22f53088fe5a2163d86" + integrity sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA== + entities@~3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz" @@ -4342,11 +4403,26 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-escaper@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-3.0.3.tgz#4d336674652beb1dcbc29ef6b6ba7f6be6fdfed6" + integrity sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ== + html-void-elements@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz" integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== +htmlparser2@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-10.1.0.tgz#fe3f2e12c73b6e462d4e10395db9c1119e4d6ae4" + integrity sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.2.2" + entities "^7.0.1" + http-cache-semantics@^4.1.1: version "4.2.0" resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz" @@ -5013,6 +5089,17 @@ lines-and-columns@^1.1.6: resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkedom@0.18.13: + version "0.18.13" + resolved "https://registry.yarnpkg.com/linkedom/-/linkedom-0.18.13.tgz#db4582d42d5606961dd8e596fc273bd8c06f50df" + integrity sha512-ES/o9qotMpzpN2MHs+Iq/JcVoOj8Fa5wiQYrTdFpvAnwXL0g66XHHUc9WUMk6nAlBtGsFQ24ne+SYnvnaQ2FSw== + dependencies: + css-select "^7.0.0" + cssom "^0.5.0" + html-escaper "^3.0.3" + htmlparser2 "^10.1.0" + uhyphen "^0.2.0" + linkify-it@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz" @@ -6120,6 +6207,13 @@ nth-check@^2.1.1: dependencies: boolbase "^1.0.0" +nth-check@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-3.0.1.tgz#a5ede96060f7f0b74d7d3d8425f2a8f0610c5776" + integrity sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ== + dependencies: + boolbase "^2.0.0" + "nx@>=17.1.2 < 21": version "20.8.4" resolved "https://registry.npmjs.org/nx/-/nx-20.8.4.tgz" @@ -7740,6 +7834,11 @@ uglify-js@^3.1.4: resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz" integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== +uhyphen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/uhyphen/-/uhyphen-0.2.0.tgz#8fdf0623314486e020a3c00ee5cc7a12fe722b81" + integrity sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA== + unicorn-magic@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.4.0.tgz#78c6a090fd6d07abd2468b83b385603e00dfdb24"