mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-11 22:06:17 -08:00
fix(danmuku): [PKG-DANMUKU-11] start visible lifetime after Worker placement
This commit is contained in:
1 parent
24b883d862
commit
df6a6fe82e
18 files changed
+3328
-9
No files matched your search
@@ -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(/<d[^>]*?p="(?<p>[^"]+)"[^>]*>(?<text>.*?)<\/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<n.length;a++)i(n[a]);return n}class h{constructor(t){this.owner=t,this.nodes=new Set}acquire(){const t=this.owner.$refs.pop()||document.createElement("div");return t.style.cssText=this.owner.constructor.cssText,t.dataset.mode="",t.dataset.id="",t.className="",this.nodes.add(t),t}get visibles(){const t=[],{clientWidth:e}=this.owner.$player,i=this.owner.getLeft(this.owner.$player);return this.owner.filter("emit",n=>{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=`translLine truncated
|
||||
!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(/<d[^>]*?p="(?<p>[^"]+)"[^>]*>(?<text>.*?)<\/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<n.length;a++)i(n[a]);return n}class h{constructor(t){this.owner=t,this.nodes=new Set}acquire(){const t=this.owner.$refs.pop()||document.createElement("div");return t.style.cssText=this.owner.constructor.cssText,t.dataset.mode="",t.dataset.id="",t.className="",this.nodes.add(t),t}get visibles(){const t=[],{clientWidth:e}=this.owner.$player,i=this.owner.getLeft(this.owner.$player);return this.owner.filter("emit",n=>{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=`translLine truncated
|
||||
@@ -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,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('<d[^>]*?p="(?<p>[^"]+)"[^>]*>(?<text>.*?)<\\/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<n.length;a++)i(n[a]);return n}class ${constructor(t){this.owner=t,this.nodes=new Set}acquire(){const t=this.owner.$refs.pop()||document.createElement("div");return t.style.cssText=this.owner.constructor.cssText,t.dataset.mode="",t.dataset.id="",t.className="",this.nodes.add(t),t}get visibles(){const t=[],{clientWidth:e}=this.owner.$player,i=this.owner.getLeft(this.owner.$player);return this.owner.filter("emit",n=>{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("emitLine truncated
|
||||
!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('<d[^>]*?p="(?<p>[^"]+)"[^>]*>(?<text>.*?)<\\/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<n.length;a++)i(n[a]);return n}class ${constructor(t){this.owner=t,this.nodes=new Set}acquire(){const t=this.owner.$refs.pop()||document.createElement("div");return t.style.cssText=this.owner.constructor.cssText,t.dataset.mode="",t.dataset.id="",t.className="",this.nodes.add(t),t}get visibles(){const t=[],{clientWidth:e}=this.owner.$player,i=this.owner.getLeft(this.owner.$player);return this.owner.filter("emit",n=>{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("emitLine truncated
|
||||
@@ -680,6 +680,7 @@ class Scheduler {
|
||||
if (!this.active(operation) || danmu.$ref !== ref)
|
||||
return;
|
||||
if (top !== void 0) {
|
||||
danmu.$lastStartTime = Date.now();
|
||||
owner.setState(danmu, "emit");
|
||||
owner.renderer.place(danmu, ref, top, distance, clientWidth);
|
||||
operation.ref = null;
|
||||
|
||||
@@ -676,6 +676,7 @@ var artplayerPluginDanmuku = (function() {
|
||||
if (!this.active(operation) || danmu.$ref !== ref)
|
||||
return;
|
||||
if (top !== void 0) {
|
||||
danmu.$lastStartTime = Date.now();
|
||||
owner.setState(danmu, "emit");
|
||||
owner.renderer.place(danmu, ref, top, distance, clientWidth);
|
||||
operation.ref = null;
|
||||
|
||||
@@ -154,6 +154,14 @@ playback enabled, new comments sample speed/playbackRate when allocated; active
|
||||
comments retain their assigned remaining seconds. Do not change these timing
|
||||
rules as incidental cleanup.
|
||||
|
||||
Successful Worker placement resets the visible start timestamp. Hidden measurement
|
||||
and Worker waiting do not consume the CSS-visible duration; recording only before
|
||||
postMessage can recycle a slow reply's comment on its first visible frame. Keep the
|
||||
remaining lifetime and geometry speed assigned during preparation, and change only
|
||||
the visible origin after the cancellation/node-identity checks. The delayed Worker
|
||||
regression in `test/browser/danmuku-lifetime.spec.js` uses real geometry with a
|
||||
controlled request delay; it does not claim natural Worker starvation.
|
||||
|
||||
A rejected `beforeVisible` reports its original error once and excludes that
|
||||
item from further attempts in the current run. Explicit start, reset, an
|
||||
invalidation or replacement callback permits another attempt if the item is
|
||||
|
||||
@@ -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(/<d[^>]*?p="(?<p>[^"]+)"[^>]*>(?<text>.*?)<\/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<n.length;a++)i(n[a]);return n}class h{constructor(t){this.owner=t,this.nodes=new Set}acquire(){const t=this.owner.$refs.pop()||document.createElement("div");return t.style.cssText=this.owner.constructor.cssText,t.dataset.mode="",t.dataset.id="",t.className="",this.nodes.add(t),t}get visibles(){const t=[],{clientWidth:e}=this.owner.$player,i=this.owner.getLeft(this.owner.$player);return this.owner.filter("emit",n=>{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=`translLine truncated
|
||||
!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(/<d[^>]*?p="(?<p>[^"]+)"[^>]*>(?<text>.*?)<\/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<n.length;a++)i(n[a]);return n}class h{constructor(t){this.owner=t,this.nodes=new Set}acquire(){const t=this.owner.$refs.pop()||document.createElement("div");return t.style.cssText=this.owner.constructor.cssText,t.dataset.mode="",t.dataset.id="",t.className="",this.nodes.add(t),t}get visibles(){const t=[],{clientWidth:e}=this.owner.$player,i=this.owner.getLeft(this.owner.$player);return this.owner.filter("emit",n=>{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=`translLine truncated
|
||||
@@ -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,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('<d[^>]*?p="(?<p>[^"]+)"[^>]*>(?<text>.*?)<\\/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<n.length;a++)i(n[a]);return n}class ${constructor(t){this.owner=t,this.nodes=new Set}acquire(){const t=this.owner.$refs.pop()||document.createElement("div");return t.style.cssText=this.owner.constructor.cssText,t.dataset.mode="",t.dataset.id="",t.className="",this.nodes.add(t),t}get visibles(){const t=[],{clientWidth:e}=this.owner.$player,i=this.owner.getLeft(this.owner.$player);return this.owner.filter("emit",n=>{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("emitLine truncated
|
||||
!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('<d[^>]*?p="(?<p>[^"]+)"[^>]*>(?<text>.*?)<\\/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<n.length;a++)i(n[a]);return n}class ${constructor(t){this.owner=t,this.nodes=new Set}acquire(){const t=this.owner.$refs.pop()||document.createElement("div");return t.style.cssText=this.owner.constructor.cssText,t.dataset.mode="",t.dataset.id="",t.className="",this.nodes.add(t),t}get visibles(){const t=[],{clientWidth:e}=this.owner.$player,i=this.owner.getLeft(this.owner.$player);return this.owner.filter("emit",n=>{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("emitLine truncated
|
||||
@@ -680,6 +680,7 @@ class Scheduler {
|
||||
if (!this.active(operation) || danmu.$ref !== ref)
|
||||
return;
|
||||
if (top !== void 0) {
|
||||
danmu.$lastStartTime = Date.now();
|
||||
owner.setState(danmu, "emit");
|
||||
owner.renderer.place(danmu, ref, top, distance, clientWidth);
|
||||
operation.ref = null;
|
||||
|
||||
@@ -175,6 +175,8 @@ export default class Scheduler {
|
||||
if (!this.active(operation) || danmu.$ref !== ref)
|
||||
return
|
||||
if (top !== undefined) {
|
||||
// The hidden Worker wait must not consume the CSS-visible lifetime.
|
||||
danmu.$lastStartTime = Date.now()
|
||||
owner.setState(danmu, 'emit')
|
||||
owner.renderer.place(danmu, ref, top, distance, clientWidth)
|
||||
operation.ref = null
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,43 @@
|
||||
# PKG-DANMUKU-11 Worker 等待与可见寿命
|
||||
|
||||
07稳定性审查发现独立计时问题,拆为本任务修复;07本身仍未完成。
|
||||
配置speed为1秒,Worker回复之前的隐藏等待如果超过1秒,弹幕刚触发visible就会
|
||||
在下一调度帧被回收。开始时间原先在postMessage前设置,而CSS显示在回复后才开始。
|
||||
|
||||
## 兼容范围
|
||||
|
||||
修复仅把成功放置的可见起点与实际放置时间对齐。保留原speed、Worker几何输入、
|
||||
同步/异步门面、事件参数/顺序、状态池、取消与错误行为;不改变公开类型或包入口。
|
||||
空轨道/取消不新增visible;暂停后的剩余寿命、播放速度与旧seek/reset行为仍回归。
|
||||
这是原来隐藏等待吞掉可见时长的缺陷修正,等待较长的弹幕会获得完整显示时间。
|
||||
历史readys的±0.1秒窗口和beforeVisible串行语义不在此任务改变。
|
||||
|
||||
## 验证
|
||||
|
||||
受控红测使用真实Worker算法:mode0/1/2、1秒寿命、等待3秒。三个开始时间断言
|
||||
均得到1000而非4000,明确复现本问题;修复后的绿测继续验证250ms剩余寿命和一次
|
||||
回收断言。原始日志保留在refactor/.cache/danmuku11-unit-red.log。
|
||||
|
||||
原生测试使用实际视频与真实Worker几何,仅通过观察用Worker子类给请求传递加
|
||||
1500ms延迟,记录post/reply/visible/recycle时间。它验证人为注入延迟后的行为,
|
||||
不声称真实用户环境必然发生相同Worker延迟。
|
||||
|
||||
完整源码253项通过,正式main/legacy各57项调度与Worker测试通过(包括共同的
|
||||
内部WorkerClient测试,不能都算独立产物案例)。构建、目标lint和分包严格TS通过。
|
||||
源码浏览器90项:原调度66项加寿命24项,覆盖两核心、新旧插件、mode0/1及三引擎;
|
||||
main/legacy各选12项候选寿命用例,总114项通过,无重试或跳过。以visible事件到
|
||||
节点回收测得生命周期,不把它称为逐帧视觉/自然Worker饥饿或长期吞吐验收。
|
||||
相同1秒配置下,旧版测得0–15ms,修复源码998–1044ms,main1004–1041ms、
|
||||
legacy1005–1027ms;原生逐帧调度有少量回收延后,受控用例精确验证时间扣减。
|
||||
具体环境、字节及时间见[机器证据](../baselines/danmuku-lifetime-validation.json)。
|
||||
|
||||
首次浏览器运行也触发了旧版已登记的play/playing双循环,历史两次请求与单次计时
|
||||
夹具冲突。将插件安装放在原生playing之后并显式启动一次来隔离该已知缺陷,未改变
|
||||
被测时钟/队列或寿命阈值。隔离红测4个旧版缺陷观察通过、4个候选寿命断言失败,
|
||||
再修复生产。第一次失败也留档,没有把夹具故障当成本任务独立红测。
|
||||
独立只读复查确认取消/节点检查在时钟更新之前,几何与剩余寿命仍按准备时速度
|
||||
计算,setState→place→visible顺序不变;包ARCHITECTURE同步维护计时边界。
|
||||
无新依赖、公开类型、版本或分发入口变更;构建产物和docs复制保持相同字节。
|
||||
|
||||
状态以tasks.json为准。回退本任务独立commit
|
||||
会恢复旧计时问题,不撤销06类型迁移或10热力图修复。未推送、未发布。
|
||||
+6
-4
@@ -2,9 +2,9 @@
|
||||
|
||||
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 229 项,范围 22 个包及工作区/示例。
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 230 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 68 / doing 15 / blocked 0 / done 146 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 68 / doing 15 / blocked 0 / done 147 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
| artplayer-plugin-auto-thumbnail | 1.1.0 | PKG-AUTO-THUMB-01, PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-05, PKG-AUTO-THUMB-06 |
|
||||
| artplayer-plugin-chapter | 1.1.0 | PILOT-01, PKG-CHAPTER-01, PKG-CHAPTER-02, PKG-CHAPTER-03, PKG-CHAPTER-04, PKG-CHAPTER-05, PKG-CHAPTER-06 |
|
||||
| artplayer-plugin-chromecast | 1.1.0 | PKG-CAST-01, PKG-CAST-02, PKG-CAST-03, PKG-CAST-04, PKG-CAST-05, PKG-CAST-06 |
|
||||
| artplayer-plugin-danmuku | 5.3.0 | PKG-DANMUKU-01, PKG-DANMUKU-02, PKG-DANMUKU-03, PKG-DANMUKU-04, PKG-DANMUKU-05, PKG-DANMUKU-06, PKG-DANMUKU-07, PKG-DANMUKU-08, PKG-DANMUKU-09, PKG-DANMUKU-10 |
|
||||
| artplayer-plugin-danmuku | 5.3.0 | PKG-DANMUKU-01, PKG-DANMUKU-02, PKG-DANMUKU-03, PKG-DANMUKU-04, PKG-DANMUKU-05, PKG-DANMUKU-06, PKG-DANMUKU-07, PKG-DANMUKU-08, PKG-DANMUKU-09, PKG-DANMUKU-10, PKG-DANMUKU-11 |
|
||||
| artplayer-plugin-danmuku-mask | 1.1.0 | PKG-MASK-01, PKG-MASK-02, PKG-MASK-03, PKG-MASK-04, PKG-MASK-05, PKG-MASK-06 |
|
||||
| artplayer-plugin-dash-control | 1.1.0 | PKG-DASH-01, PKG-DASH-02, PKG-DASH-03, PKG-DASH-04, PKG-DASH-05, PKG-DASH-06 |
|
||||
| artplayer-plugin-document-pip | 1.1.0 | PKG-DPIP-01, PKG-DPIP-02, PKG-DPIP-03, PKG-DPIP-04, PKG-DPIP-05, PKG-DPIP-06 |
|
||||
@@ -315,10 +315,11 @@
|
||||
| PKG-DANMUKU-04 | artplayer-plugin-danmuku<br>整理时钟、队列与轨道调度 | PKG-DANMUKU-03, CORE-10 | danmuku 调度器和确定性时钟测试 | seek/暂停/倍率/长时间运行无顺序和碰撞回归 | H | done |
|
||||
| PKG-DANMUKU-05 | artplayer-plugin-danmuku<br>整理 DOM 渲染、设置、热力图与 worker | PKG-DANMUKU-04, CORE-14, CORE-18 | renderer/setting/heatmap/worker 职责及资源归属 | mount/icons/设置和 worker 协议保持,销毁无后台工作 | H | done |
|
||||
| PKG-DANMUKU-06 | artplayer-plugin-danmuku<br>迁移 TS 与公开声明 | PKG-DANMUKU-05, ENG-04, ENG-06 | 自有模块、worker 消息、option/item/result 类型 | 旧参数、扩展字段和事件消费通过,vendored 边界清楚 | H | done |
|
||||
| PKG-DANMUKU-07 | artplayer-plugin-danmuku<br>弹幕能力稳定性验收 | PKG-DANMUKU-06, PKG-DANMUKU-10 | 负载/渲染/资源对比及 mask 可依赖的稳定边界 | 在试点核心中旧插件 API 与关键帧路径通过 | H | doing |
|
||||
| PKG-DANMUKU-07 | artplayer-plugin-danmuku<br>弹幕能力稳定性验收 | PKG-DANMUKU-06, PKG-DANMUKU-10, PKG-DANMUKU-11 | 负载/渲染/资源对比及 mask 可依赖的稳定边界 | 在试点核心中旧插件 API 与关键帧路径通过 | H | doing |
|
||||
| PKG-DANMUKU-08 | artplayer-plugin-danmuku<br>完成新旧核心与组合验收 | PKG-DANMUKU-07, CORE-22 | 最终核心/旧核心、mask/fullscreen/PiP 组合报告 | 性能无未解释退化,反复装卸无累计资源 | H | todo |
|
||||
| PKG-DANMUKU-09 | artplayer-plugin-danmuku<br>完成分发、示例与文档 | PKG-DANMUKU-08, ENG-07 | danmuku.js、README、声明、worker 与产物 | tarball 和静态 icons 等旧调用通过,有独立回退版本 | H | todo |
|
||||
| PKG-DANMUKU-10 | artplayer-plugin-danmuku<br>修复 issue958 密集热力图过高与曲线裁平 | PKG-DANMUKU-05 | 自动密度缩放与曲线边界、历史自定义坐标兼容及真实浏览器红绿证据 | 16000条三种分布在两核心三引擎和source/main/legacy不遮挡;显式轴和自定义points保持旧解释,有单独commit | M | done |
|
||||
| PKG-DANMUKU-11 | artplayer-plugin-danmuku<br>修复 Worker 隐藏等待吞掉可见寿命 | PKG-DANMUKU-06 | 成功放置时的可见计时与延迟回复回归 | 真实 Worker 延迟下完整显示寿命;保留暂停/回收/取消和公开契约 | M | done |
|
||||
|
||||
## 5 包迁移:artplayer-proxy-canvas
|
||||
|
||||
@@ -589,3 +590,4 @@
|
||||
- PKG-ASR-08: [记录](changes/2026-09-13-PKG-ASR-08-capture-output.md) [记录](baselines/asr-fallback-validation.json)
|
||||
- PKG-ASR-09: [记录](changes/2026-09-13-PKG-ASR-09-explicit-capture.md) [记录](baselines/asr-explicit-capture-validation.json)
|
||||
- PKG-DANMUKU-10: [记录](changes/2026-09-13-PKG-DANMUKU-10-heatmap-density.md) [记录](baselines/danmuku-heatmap-density-validation.json)
|
||||
- PKG-DANMUKU-11: [记录](changes/2026-09-13-PKG-DANMUKU-11-visible-lifetime.md) [记录](baselines/danmuku-lifetime-validation.json)
|
||||
@@ -1,5 +1,17 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-DANMUKU-11 Worker 等待寿命修复
|
||||
|
||||
07负载审查独立发现并复现:隐藏Worker等待超过speed时,刚显示的弹幕下一帧就被
|
||||
回收。成功放置时重置可见起点,保留准备时速度、几何、取消检查与事件顺序。
|
||||
三模式受控红/绿、两核心与三引擎真实延迟Worker验证通过;完整源码253项、
|
||||
main/legacy各57项单测,源码90及两个产物各12共114项浏览器通过。构建/lint/严格
|
||||
分包TS通过,见[变更](changes/2026-09-13-PKG-DANMUKU-11-visible-lifetime.md)和
|
||||
[机器证据](baselines/danmuku-lifetime-validation.json)。
|
||||
这是实际视频/Worker加受控请求延迟,不声称自然饥饿或持续高负载通过。
|
||||
230项:147 done、15 doing、68 todo;07继续,DANMUKU-LOAD-01仍开放。
|
||||
新增11是独立缺陷任务和本地commit;不推送或发布。
|
||||
|
||||
## PKG-DANMUKU-06 自有 TS 与公开声明完成
|
||||
|
||||
20个自有运行模块完成严格TS,新增内部状态/Worker/parser/UI类型边界;公开运行
|
||||
|
||||
@@ -239,3 +239,4 @@
|
||||
| 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 |
|
||||
| DANMUKU-HEATMAP-02 | resolved / 已复现 | Issue958 dense automatic heatmaps exceed the fixed Y domain and flatten across video | PKG-DANMUKU-10 |
|
||||
| DANMUKU-TIMING-01 | resolved / 已复现 | Hidden Worker wait is deducted from visible lifetime and can recycle a comment immediately after visible | PKG-DANMUKU-11 |
|
||||
@@ -5324,6 +5324,28 @@
|
||||
"refactor/baselines/danmuku-heatmap-density-validation.json"
|
||||
],
|
||||
"resolutionRationale": "250 unit tests and 102 native browser cases passed, including source/main/legacy density matrices and main resource regression. No claim of full load or macOS device acceptance."
|
||||
},
|
||||
{
|
||||
"id": "DANMUKU-TIMING-01",
|
||||
"title": "Hidden Worker wait is deducted from visible lifetime and can recycle a comment immediately after visible",
|
||||
"confirmation": "reproduced",
|
||||
"status": "resolved",
|
||||
"owners": [
|
||||
"PKG-DANMUKU-11"
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/changes/2026-09-13-PKG-DANMUKU-11-visible-lifetime.md",
|
||||
"test/danmuku-scheduler.test.js",
|
||||
"test/browser/danmuku-lifetime.spec.js",
|
||||
"refactor/baselines/danmuku-lifetime-validation.json"
|
||||
],
|
||||
"compatibleResolution": "Anchor the lifetime clock to successful placement while preserving speed, Worker geometry, queue ordering and lifecycle cancellation.",
|
||||
"closureCriteria": "Controlled three-mode regression and native delayed Worker runs distinguish historical immediate recycling from full candidate visible lifetime; ordinary scheduling regressions remain green.",
|
||||
"resolutionEvidence": [
|
||||
"refactor/baselines/danmuku-lifetime-validation.json",
|
||||
"refactor/changes/2026-09-13-PKG-DANMUKU-11-visible-lifetime.md"
|
||||
],
|
||||
"resolutionRationale": "Three exact controlled modes and real delayed Worker/native video regression establish full visible lifetime, with the unchanged published defect retained and existing scheduling/pause/cancellation checks green."
|
||||
}
|
||||
]
|
||||
}
|
||||
+21
-1
@@ -3380,7 +3380,8 @@
|
||||
],
|
||||
"dependsOn": [
|
||||
"PKG-DANMUKU-06",
|
||||
"PKG-DANMUKU-10"
|
||||
"PKG-DANMUKU-10",
|
||||
"PKG-DANMUKU-11"
|
||||
],
|
||||
"status": "doing",
|
||||
"risk": "H",
|
||||
@@ -4681,6 +4682,25 @@
|
||||
"changes/2026-09-13-PKG-DANMUKU-10-heatmap-density.md",
|
||||
"baselines/danmuku-heatmap-density-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-DANMUKU-11",
|
||||
"phase": "5 包迁移:artplayer-plugin-danmuku",
|
||||
"title": "修复 Worker 隐藏等待吞掉可见寿命",
|
||||
"scope": [
|
||||
"artplayer-plugin-danmuku"
|
||||
],
|
||||
"dependsOn": [
|
||||
"PKG-DANMUKU-06"
|
||||
],
|
||||
"status": "done",
|
||||
"risk": "M",
|
||||
"deliverable": "成功放置时的可见计时与延迟回复回归",
|
||||
"acceptance": "真实 Worker 延迟下完整显示寿命;保留暂停/回收/取消和公开契约",
|
||||
"evidence": [
|
||||
"changes/2026-09-13-PKG-DANMUKU-11-visible-lifetime.md",
|
||||
"baselines/danmuku-lifetime-validation.json"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import process from 'node:process'
|
||||
import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
|
||||
import { compilePackage } from '../helpers/load.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const implementations = new Map()
|
||||
test.beforeAll(async () => {
|
||||
const release = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/danmuku-release.json', import.meta.url), 'utf8')).release
|
||||
const member = `package/${release.manifest.main.replace(/^\.\//u, '')}`
|
||||
const bytes = readMember(await ensureArchive(release), member)
|
||||
assert.equal(hash(bytes), release.files[member])
|
||||
implementations.set('published', bytes.toString())
|
||||
implementations.set('candidate', process.env.ARTPLAYER_DANMUKU_ARTIFACT
|
||||
? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8')
|
||||
: await compilePackage('artplayer-plugin-danmuku', 'umd'))
|
||||
})
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const implementation of ['published', 'candidate']) {
|
||||
for (const mode of [0, 1]) {
|
||||
test(`${core} core / ${implementation} plugin / mode ${mode}: Worker waiting does not consume visible lifetime`, async ({ page }, testInfo) => {
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
await page.evaluate(() => {
|
||||
const evidence = window.lifetimeEvidence = { posts: [], replies: [], visible: [], recycled: [] }
|
||||
const NativeWorker = window.Worker
|
||||
window.Worker = class DelayedNativeWorker extends NativeWorker {
|
||||
constructor(...args) {
|
||||
super(...args)
|
||||
this.addEventListener('message', event => evidence.replies.push({ id: event.data.id, wall: performance.now() }))
|
||||
}
|
||||
|
||||
postMessage(message, ...args) {
|
||||
evidence.posts.push({ id: message.id, wall: performance.now() })
|
||||
// Delay delivery, then execute the actual native Worker and its real geometry.
|
||||
this.delay = setTimeout(() => super.postMessage(message, ...args), 1500)
|
||||
}
|
||||
|
||||
terminate() {
|
||||
clearTimeout(this.delay)
|
||||
return super.terminate()
|
||||
}
|
||||
}
|
||||
})
|
||||
const code = implementations.get(implementation)
|
||||
await page.addScriptTag({ content: code })
|
||||
await page.evaluate(() => window.createPlayer('/assets/sample/video.mp4'))
|
||||
await page.click('#play')
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
|
||||
// Install after native playing, then start once. The published duplicate
|
||||
// play/playing loop defect is covered elsewhere and is not this timing case.
|
||||
await page.evaluate(async (mode) => {
|
||||
const art = window.art
|
||||
const evidence = window.lifetimeEvidence
|
||||
art.on('artplayerPluginDanmuku:visible', row => evidence.visible.push({ wall: performance.now(), clock: Date.now(), startedAt: row.$lastStartTime, rest: row.$restTime, mode: row.mode, time: art.currentTime, transition: row.$ref.style.transition }))
|
||||
art.plugins.add(window.artplayerPluginDanmuku({ danmuku: [], speed: 1, antiOverlap: false, heatmap: false, emitter: false }))
|
||||
const plugin = art.plugins.artplayerPluginDanmuku
|
||||
const owner = plugin.config({})
|
||||
const makeWait = owner.makeWait
|
||||
owner.makeWait = (row) => {
|
||||
if (row.$state === 'emit')
|
||||
evidence.recycled.push({ wall: performance.now(), rest: row.$restTime, time: art.currentTime })
|
||||
return makeWait.call(owner, row)
|
||||
}
|
||||
await plugin.load([{ id: 'delayed', text: 'Full visible lifetime', time: art.currentTime + 0.4, mode }])
|
||||
owner.start()
|
||||
}, mode)
|
||||
await expect.poll(() => page.evaluate(() => window.lifetimeEvidence.recycled.length)).toBe(1)
|
||||
await page.click('#pause')
|
||||
const evidence = await page.evaluate(() => ({ ...window.lifetimeEvidence, video: { width: window.art.video.videoWidth, time: window.art.currentTime } }))
|
||||
await testInfo.attach('danmuku-lifetime', { contentType: 'application/json', body: JSON.stringify({ core, implementation, mode, sha256: hash(code), evidence, scope: 'Native video and real Worker geometry with controlled 1500ms request delay; does not assert naturally occurring Worker starvation.' }) })
|
||||
expect(evidence.posts).toHaveLength(1)
|
||||
expect(evidence.replies).toHaveLength(1)
|
||||
expect(evidence.visible).toHaveLength(1)
|
||||
expect(evidence.video.width).toBeGreaterThan(0)
|
||||
expect(evidence.replies[0].wall - evidence.posts[0].wall).toBeGreaterThanOrEqual(1400)
|
||||
const lifetime = evidence.recycled[0].wall - evidence.visible[0].wall
|
||||
if (implementation === 'published') {
|
||||
// Preserve the historical defect as explicit evidence, not the candidate contract.
|
||||
expect(lifetime).toBeLessThan(400)
|
||||
expect(evidence.visible[0].clock - evidence.visible[0].startedAt).toBeGreaterThanOrEqual(1400)
|
||||
}
|
||||
else {
|
||||
expect(lifetime).toBeGreaterThanOrEqual(900)
|
||||
expect(evidence.visible[0].rest).toBe(1)
|
||||
}
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -491,6 +491,61 @@ test(`${prefix}: the actual algorithm places three same-time fixed rows on disti
|
||||
env.destroy()
|
||||
})
|
||||
|
||||
for (const mode of [0, 1, 2]) {
|
||||
test(`${prefix}: delayed Worker reply starts visible lifetime at placement for mode ${mode}`, async (t) => {
|
||||
const { env, plugin, owner, worker, layer, reply } = await fixture({ speed: 1 })
|
||||
t.after(() => env.destroy())
|
||||
await plugin.emit({ text: `delayed-${mode}`, time: 10, mode })
|
||||
const row = owner.queue[0]
|
||||
env.art.playing = true
|
||||
owner.start()
|
||||
const pendingFrame = env.frame()
|
||||
await env.flush()
|
||||
assert.equal(worker.messages.length, 1)
|
||||
const ref = row.$ref
|
||||
assert.equal(layer.children.length, 1)
|
||||
assert.equal(output(env, 'visible').length, 0, 'Measurement before the Worker reply is not visible lifetime')
|
||||
assert.notEqual(ref.style.visibility, 'visible')
|
||||
|
||||
// The reply uses the real positioning algorithm; only its delivery and the
|
||||
// wall/media clocks are controlled. No browser animation timing is claimed.
|
||||
env.tick(3000)
|
||||
env.art.currentTime += 3
|
||||
const placedAt = env.context.Date.now()
|
||||
reply(0)
|
||||
await pendingFrame
|
||||
assert.equal(row.$state, 'emit')
|
||||
assert.equal(row.$ref, ref)
|
||||
assert.equal(ref.style.visibility, 'visible')
|
||||
assert.deepEqual(output(env, 'visible').map(event => event.args[0]), [row])
|
||||
assert.equal(row.$restTime, 1)
|
||||
assert.equal(row.$lastStartTime, placedAt, 'Hidden Worker waiting must not shorten the visible duration')
|
||||
if (mode === 0)
|
||||
assert.equal(ref.style.transition, 'transform 1s linear 0s')
|
||||
|
||||
env.tick(250)
|
||||
env.art.currentTime += 0.25
|
||||
await env.frame()
|
||||
assert.equal(row.$state, 'emit')
|
||||
assert.equal(row.$ref, ref)
|
||||
assert.equal(row.$restTime, 0.75, 'Only elapsed time since visible placement consumes lifetime')
|
||||
assert.equal(owner.$refs.length, 0)
|
||||
|
||||
env.tick(750)
|
||||
env.art.currentTime += 0.75
|
||||
await env.frame()
|
||||
assert.equal(row.$state, 'wait')
|
||||
assert.equal(row.$ref, null)
|
||||
assert.equal(ref.style.visibility, 'hidden')
|
||||
assert.deepEqual(Array.from(owner.$refs), [ref], 'The expired node returns to its pool exactly once')
|
||||
await env.frame()
|
||||
assert.deepEqual(Array.from(owner.$refs), [ref])
|
||||
assert.equal(worker.messages.length, 1)
|
||||
assert.equal(output(env, 'visible').length, 1)
|
||||
assert.equal(output(env, 'error').length, 0)
|
||||
})
|
||||
}
|
||||
|
||||
test(`${prefix}: a sampled three-hour simulated clock preserves batch order, pooled nodes, pause and seek state`, async (t) => {
|
||||
// Advance a controlled clock at lifecycle boundaries; this is not a real-time
|
||||
// endurance, frame-rate, CSS animation or browser memory measurement.
|
||||
|
||||
Reference in new issue
Block a user