mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
refactor(ads): [PKG-ADS-03] split TypeScript lifecycle and preserve legacy behavior
This commit is contained in:
1 parent
069ad4aae1
commit
39e2d4cdfb
37 files changed
+3083
-788
No files matched your search
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,l){"object"===typeof exports&&"undefined"!==typeof module?module.exports=l():"function"==typeof define&&define.amd?(t.artplayerPluginAds=l(),define(function(){return t.artplayerPluginAds})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAds=l()}(this,function(){"use strict";function e(l){return n=>{!function(l){const{version:n,utils:{errorHandle:i}}=l.constructor,a=n.split(".").map(Number);i(a[0]+a[1]/100>=5,`Artplayer.js@${n} is not compatible the artplayerPluginAds@${e.version}. Please update it to version Artplayer.js@5.x.x`)}(n);const{template:{$player:i},icons:{volume:a,volumeClose:t,fullscreenOn:r,fullscreenOff:o,loading:d},constructor:{validator:s,utils:{query:p,append:u,setStyle:c}}}=n;l=s({html:"",video:"",url:"",playDuration:5,totalDuration:10,muted:!1,i18n:{close:"\u5173\u95ed\u5e7f\u544a",countdown:"%s\u79d2",detail:"\u67e5\u770b\u8be6\u60c5",canBeClosed:"%s\u79d2\u540e\u53ef\u5173\u95ed\u5e7f\u544a"},...l},{html:"?string",video:"?string",url:"?string",playDuration:"number",totalDuration:"number",muted:"?boolean",i18n:{close:"string",countdown:"string",detail:"string",canBeClosed:"string"}});let y=null,g=null,f=null,m=null,v=null,x=null,h=0,b=null,w=!1,$=!1,k=!1;function D(e,l){return l.replace("%s",e)}function A(){w=!0,n.play(),l.video&&y.pause(),c(n.template.$ads,"display","none"),n.emit("artplayerPluginAds:skip",l)}function C(){w||(b=setTimeout(()=>{h+=1;const e=l.playDuration-h;e>=1?f.innerHTML=D(e,l.i18n.canBeClosed):(f.innerHTML=l.i18n.close,k||(k=!0)),m.innerHTML=D(l.totalDuration-h,l.i18n.countdown),h>=l.totalDuration?A():C()},1e3))}function P(){w||clearTimeout(b)}function T(){$||($=!0,function(){n.template.$ads=u(i,'<div class="artplayer-plugin-ads"></div>'),y=u(n.template.$ads,l.video?`<video class="artplayer-plugin-ads-video" src="${l.video}" loop playsInline></video>`:`<div class="artplayer-plugin-ads-html">${l.html}</div>`),x=u(n.template.$ads,'<div class="artplayer-plugin-ads-loading"></div>'),u(x,d),g=u(n.template.$ads,`<div class="artplayer-plugin-ads-timer">\n <div class="artplayer-plugin-ads-close">${l.playDuration<=0?l.i18n.close:D(l.playDuration,l.i18n.canBeClosed)}</div>\n <div class="artplayer-plugin-ads-countdown">${D(l.totalDuration,l.i18n.countdown)}</div>\n </div>`),f=p(".artplayer-plugin-ads-close",g),m=p(".artplayer-plugin-ads-countdown",g),l.playDuration>=l.totalDuration&&c(f,"display","none"),n.proxy(f,"click",()=>{k&&A()}),v=u(n.template.$ads,`<div class="artplayer-plugin-ads-control">\n <div class="artplayer-plugin-ads-detail">${l.i18n.detail}</div>\n <div class="artplayer-plugin-ads-muted"></div>\n <div class="artplayer-plugin-ads-fullscreen"></div>\n </div>`);const e=p(".artplayer-plugin-ads-detail",v),s=p(".artplayer-plugin-ads-muted",v),h=p(".artplayer-plugin-ads-fullscreen",v);if(l.url?n.proxy(e,"click",()=>{window.open(l.url),n.emit("artplayerPluginAds:click",l)}):c(e,"display","none"),l.video){const e=u(s,a),i=u(s,t);c(i,"display","none"),l.muted&&(y.muted=!0,c(e,"display","none"),c(i,"display","inline-flex")),n.proxy(s,"click",()=>{y.muted=!y.muted,y.muted?(c(e,"display","none"),c(i,"display","inline-flex")):(c(e,"display","inline-flex"),c(i,"display","none"))})}else c(s,"display","none");const b=u(h,r),w=u(h,o);c(w,"display","none"),n.proxy(h,"click",()=>{n.fullscreen=!n.fullscreen,n.fullscreen?(c(b,"display","inline-flex"),c(w,"display","none")):(c(b,"display","none"),c(w,"display","inline-flex"))}),n.proxy(y,"click",()=>{l.url&&window.open(l.url),n.emit("artplayerPluginAds:click",l)})}(),n.pause(),l.video?(n.proxy(y,"error",A),n.proxy(y,"loadedmetadata",()=>{C(),y.play(),c(g,"display","flex"),c(v,"display","flex"),c(x,"display","none")})):(C(),c(g,"display","flex"),c(v,"display","flex"),c(x,"display","none")),n.on("document:visibilitychange",()=>{document.hidden?P():C()}))}return n.on("ready",()=>{n.once("play",T),n.once("video:playing",T)}),{name:"artplayerPluginAds",skip:A,pause:P,play:C}}}if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-ads")){const e=document.createElement("style");e.id="artplayer-plugin-ads",e.textContent=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:none;position:absolute;top:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-timer>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control{display:none;position:absolute;bottom:10px;right:10px}.artplLine truncated
|
||||
!function(e,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(e.artplayerPluginAds=n(),define(function(){return e.artplayerPluginAds})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAds=n()}(this,function(){"use strict";function e(e){console.warn("Artplayer Ads:",e)}function n(n){const t=[];let i=!1;function l(e,n){if(!i){t.push(n);try{e()}finally{i&&n()}}}return{on(e,t){const a=()=>{i||t()};l(()=>{n.on(e,a)},()=>{n.off(e,a)})},dom(e,n,t){const a=()=>{i||t()};l(()=>e.addEventListener(n,a),()=>e.removeEventListener(n,a))},dispose(){if(!i){i=!0;for(const i of t.splice(0).reverse())try{i()}catch(n){e(n)}}}}}const t=(e,n)=>n.replace("%s",String(e));function i(i,l,a,o){const r=n(i),s=n(i),d=i.template;let u,p,c="waiting",y=!1,f=!1,g=!1;const v=function(e,n,t){let i,l=0,a=!1,o=!1,r=!1;function s(){a&&o&&!r&&void 0===i&&(i=setTimeout(()=>{i=void 0,!r&&o&&(l+=1,n(l),r||(l>=e()?t():s()))},1e3))}function d(){o=!1,void 0!==i&&clearTimeout(i),i=void 0}return{start(){a||r||(a=!0,o=!0,s())},play(){a&&!r&&(o=!0,s())},pause:d,stop(){r=!0,d()}}}(()=>l.totalDuration,e=>p?.render(e),b),m=()=>"active"===c&&!i.isDestroy,h=()=>"destroyed"===c||i.isDestroy;function x(n){try{n?.pause()}catch(t){e(t)}}function w(n,t,i){try{Promise.resolve(n()).then(()=>{m()||i?.()},t).catch(e)}catch(l){t(l)}}function b(){if("ended"!==c&&"destroyed"!==c&&!i.isDestroy){if(c="ended",v.stop(),r.dispose(),y){if(w(()=>i.play(),n=>{"destroyed"!==c&&e(n)}),h())return;if(x(p?.video),h())return;p?.hide()}h()||i.emit("artplayerPluginAds:skip",l)}}function D(){const n=p?.video;if(x(n),n)try{n.removeAttribute("src"),n.load()}catch(t){e(t)}try{u?.remove()}catch(t){e(t)}finally{d.$ads===u&&delete d.$ads}}function k(){"destroyed"!==c&&(c="destroyed",v.stop(),r.dispose(),s.dispose(),D())}function A(){if("waiting"===c&&!i.isDestroy){c="active",y=!0;try{if(p=function(e,n,i,l,a){const{append:o,query:r,setStyle:s}=l,d=o(e,'<div class="artplayer-plugin-ads"></div>');a(d);const u=o(d,i.video?'<video class="artplayer-plugin-ads-video" loop playsInline></video>':`<div class="artplayer-plugin-ads-html">${i.html}</div>`),p=i.video?u:null,c=o(d,'<div class="artplayer-plugin-ads-loading"></div>');o(c,n.loading);const y=o(d,'<div class="artplayer-plugin-ads-timer">\n <div class="artplayer-plugin-ads-close"></div>\n <div class="artplayer-plugin-ads-countdown"></div>\n </div>'),f=r(".artplayer-plugin-ads-close",y),g=r(".artplayer-plugin-ads-countdown",y),v=o(d,`<div class="artplayer-plugin-ads-control">\n <div class="artplayer-plugin-ads-detail">${i.i18n.detail}</div>\n <div class="artplayer-plugin-ads-muted"></div>\n <div class="artplayer-plugin-ads-fullscreen"></div>\n </div>`),m=r(".artplayer-plugin-ads-detail",v),h=r(".artplayer-plugin-ads-muted",v),x=r(".artplayer-plugin-ads-fullscreen",v);let w=i.playDuration<=0;function b(){s(n.volume,"display",p?.muted?"none":"inline-flex"),s(n.volumeClose,"display",p?.muted?"inline-flex":"none")}function D(e){const n=i.playDuration-e;w=0===e?i.playDuration<=0:n<1||Number.isNaN(n),f.innerHTML=w?i.i18n.close:t(n,i.i18n.canBeClosed),g.innerHTML=t(i.totalDuration-e,i.i18n.countdown)}return i.playDuration>=i.totalDuration&&s(f,"display","none"),i.url||s(m,"display","none"),p?(o(h,n.volume),o(h,n.volumeClose),p.muted=Boolean(i.muted),b()):s(h,"display","none"),o(x,n.fullscreenOn),o(x,n.fullscreenOff),D(0),{root:d,video:p,render:D,ready(){s(y,"display","flex"),s(v,"display","flex"),s(c,"display","none")},hide(){s(d,"display","none")},fullscreen(e){s(n.fullscreenOn,"display",e?"none":"inline-flex"),s(n.fullscreenOff,"display",e?"inline-flex":"none")},bind(e,n){e.dom(f,"click",()=>{w&&n.skip()}),e.dom(u,"click",n.click),i.url&&e.dom(m,"click",n.click),p&&e.dom(h,"click",()=>{p.muted=!p.muted,b()}),e.dom(x,"click",n.fullscreen)}}}(d.$player,a,l,o,e=>{u=e,d.$ads=e}),!m())return void D();if(i.pause(),!m())return;const n=p,s=u.ownerDocument;n.fullscreen(i.fullscreen),n.bind(r,{skip:b,click(){l.url&&(s.defaultView||window).open(l.url),m()&&i.emit("artplayerPluginAds:click",l)},fullscreen(){i.fullscreen=!i.fullscreen,m()&&n.fullscreen(i.fullscreen)}}),r.on("fullscreen",()=>n.fullscreen(i.fullscreen));const c=()=>s.hidden?v.pause():v.play();r.dom(s,"visibilitychange",c),r.on("document:visibilitychange",c);const y=()=>{if(m()&&!g){if(g=!0,v.start(),s.hidden&&v.pause(),n.video){const t=n.video;w(()=>t.play(),n=>{m()&&(e(n),b())},()=>x(t))}m()&&n.ready()}};n.video?(r.dom(n.video,"error",b),r.dom(n.video,"loadedmetadata",y),n.video.src=l.video):y()}catch(n){throw k(),n}}}try{s.on("destroy",k),r.on("ready",()=>{f||"waiting"!==c||(f=!0,r.on("play",A),r.on("video:playing",A))}),i.isDestroy&&k()}catch(C){throw k(),C}return{name:"artplayerPluginAds",skip:b,pause:v.pause,play:v.play}}if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-ads")){const e=document.createElement("style");e.id="artplayer-plugin-ads",e.textContent=".artplayeLine truncated
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,l){"object"===typeof exports&&"undefined"!==typeof module?module.exports=l():"function"==typeof define&&define.amd?(t.artplayerPluginAds=l(),define(function(){return t.artplayerPluginAds})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAds=l()}(this,function(){"use strict";var e=Object.defineProperty,l=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable,i=(l,n,a)=>n in l?e(l,n,{enumerable:!0,configurable:!0,writable:!0,value:a}):l[n]=a;function t(e){return r=>{!function(e){const{version:l,utils:{errorHandle:n}}=e.constructor,a=l.split(".").map(Number);n(a[0]+a[1]/100>=5,`Artplayer.js@${l} is not compatible the artplayerPluginAds@${t.version}. Please update it to version Artplayer.js@5.x.x`)}(r);const{template:{$player:o},icons:{volume:d,volumeClose:p,fullscreenOn:s,fullscreenOff:u,loading:c},constructor:{validator:y,utils:{query:g,append:f,setStyle:m}}}=r;e=y(((e,t)=>{for(var r in t||(t={}))n.call(t,r)&&i(e,r,t[r]);if(l)for(var r of l(t))a.call(t,r)&&i(e,r,t[r]);return e})({html:"",video:"",url:"",playDuration:5,totalDuration:10,muted:!1,i18n:{close:"\u5173\u95ed\u5e7f\u544a",countdown:"%s\u79d2",detail:"\u67e5\u770b\u8be6\u60c5",canBeClosed:"%s\u79d2\u540e\u53ef\u5173\u95ed\u5e7f\u544a"}},e),{html:"?string",video:"?string",url:"?string",playDuration:"number",totalDuration:"number",muted:"?boolean",i18n:{close:"string",countdown:"string",detail:"string",canBeClosed:"string"}});let v=null,x=null,h=null,b=null,w=null,$=null,k=0,D=null,P=!1,j=!1,A=!1;function O(e,l){return l.replace("%s",e)}function C(){P=!0,r.play(),e.video&&v.pause(),m(r.template.$ads,"display","none"),r.emit("artplayerPluginAds:skip",e)}function T(){P||(D=setTimeout(()=>{k+=1;const l=e.playDuration-k;l>=1?h.innerHTML=O(l,e.i18n.canBeClosed):(h.innerHTML=e.i18n.close,A||(A=!0)),b.innerHTML=O(e.totalDuration-k,e.i18n.countdown),k>=e.totalDuration?C():T()},1e3))}function B(){P||clearTimeout(D)}function H(){j||(j=!0,function(){r.template.$ads=f(o,'<div class="artplayer-plugin-ads"></div>'),v=f(r.template.$ads,e.video?`<video class="artplayer-plugin-ads-video" src="${e.video}" loop playsInline></video>`:`<div class="artplayer-plugin-ads-html">${e.html}</div>`),$=f(r.template.$ads,'<div class="artplayer-plugin-ads-loading"></div>'),f($,c),x=f(r.template.$ads,`<div class="artplayer-plugin-ads-timer">\n <div class="artplayer-plugin-ads-close">${e.playDuration<=0?e.i18n.close:O(e.playDuration,e.i18n.canBeClosed)}</div>\n <div class="artplayer-plugin-ads-countdown">${O(e.totalDuration,e.i18n.countdown)}</div>\n </div>`),h=g(".artplayer-plugin-ads-close",x),b=g(".artplayer-plugin-ads-countdown",x),e.playDuration>=e.totalDuration&&m(h,"display","none"),r.proxy(h,"click",()=>{A&&C()}),w=f(r.template.$ads,`<div class="artplayer-plugin-ads-control">\n <div class="artplayer-plugin-ads-detail">${e.i18n.detail}</div>\n <div class="artplayer-plugin-ads-muted"></div>\n <div class="artplayer-plugin-ads-fullscreen"></div>\n </div>`);const l=g(".artplayer-plugin-ads-detail",w),n=g(".artplayer-plugin-ads-muted",w),a=g(".artplayer-plugin-ads-fullscreen",w);if(e.url?r.proxy(l,"click",()=>{window.open(e.url),r.emit("artplayerPluginAds:click",e)}):m(l,"display","none"),e.video){const l=f(n,d),a=f(n,p);m(a,"display","none"),e.muted&&(v.muted=!0,m(l,"display","none"),m(a,"display","inline-flex")),r.proxy(n,"click",()=>{v.muted=!v.muted,v.muted?(m(l,"display","none"),m(a,"display","inline-flex")):(m(l,"display","inline-flex"),m(a,"display","none"))})}else m(n,"display","none");const i=f(a,s),t=f(a,u);m(t,"display","none"),r.proxy(a,"click",()=>{r.fullscreen=!r.fullscreen,r.fullscreen?(m(i,"display","inline-flex"),m(t,"display","none")):(m(i,"display","none"),m(t,"display","inline-flex"))}),r.proxy(v,"click",()=>{e.url&&window.open(e.url),r.emit("artplayerPluginAds:click",e)})}(),r.pause(),e.video?(r.proxy(v,"error",C),r.proxy(v,"loadedmetadata",()=>{T(),v.play(),m(x,"display","flex"),m(w,"display","flex"),m($,"display","none")})):(T(),m(x,"display","flex"),m(w,"display","flex"),m($,"display","none")),r.on("document:visibilitychange",()=>{document.hidden?B():T()}))}return r.on("ready",()=>{r.once("play",H),r.once("video:playing",H)}),{name:"artplayerPluginAds",skip:C,pause:B,play:T}}}if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-ads")){const e=document.createElement("style");e.id="artplayer-plugin-ads",e.textContent=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:noneLine truncated
|
||||
!function(e,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(e.artplayerPluginAds=n(),define(function(){return e.artplayerPluginAds})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAds=n()}(this,function(){"use strict";var e=Object.defineProperty,n=Object.getOwnPropertySymbols,l=Object.prototype.hasOwnProperty,t=Object.prototype.propertyIsEnumerable,i=(n,l,t)=>l in n?e(n,l,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[l]=t;function r(e,r){return r(((e,r)=>{for(var o in r||(r={}))l.call(r,o)&&i(e,o,r[o]);if(n)for(var o of n(r))t.call(r,o)&&i(e,o,r[o]);return e})({html:"",video:"",url:"",playDuration:5,totalDuration:10,muted:!1,i18n:{close:"\u5173\u95ed\u5e7f\u544a",countdown:"%s\u79d2",detail:"\u67e5\u770b\u8be6\u60c5",canBeClosed:"%s\u79d2\u540e\u53ef\u5173\u95ed\u5e7f\u544a"}},e),{html:"?string",video:"?string",url:"?string",playDuration:"number",totalDuration:"number",muted:"?boolean",i18n:{close:"string",countdown:"string",detail:"string",canBeClosed:"string"}})}function o(e){console.warn("Artplayer Ads:",e)}function a(e){const n=[];let l=!1;function t(e,t){if(!l){n.push(t);try{e()}finally{l&&t()}}}return{on(n,i){const r=()=>{l||i()};t(()=>{e.on(n,r)},()=>{e.off(n,r)})},dom(e,n,i){const r=()=>{l||i()};t(()=>e.addEventListener(n,r),()=>e.removeEventListener(n,r))},dispose(){if(!l){l=!0;for(const l of n.splice(0).reverse())try{l()}catch(e){o(e)}}}}}const s=(e,n)=>n.replace("%s",String(e));function d(e,n,l,t){const i=a(e),r=a(e),d=e.template;let u,p,c="waiting",y=!1,f=!1,g=!1;const v=function(e,n,l){let t,i=0,r=!1,o=!1,a=!1;function s(){r&&o&&!a&&void 0===t&&(t=setTimeout(()=>{t=void 0,!a&&o&&(i+=1,n(i),a||(i>=e()?l():s()))},1e3))}function d(){o=!1,void 0!==t&&clearTimeout(t),t=void 0}return{start(){r||a||(r=!0,o=!0,s())},play(){r&&!a&&(o=!0,s())},pause:d,stop(){a=!0,d()}}}(()=>n.totalDuration,e=>null==p?void 0:p.render(e),w),m=()=>"active"===c&&!e.isDestroy,h=()=>"destroyed"===c||e.isDestroy;function b(e){try{null==e||e.pause()}catch(n){o(n)}}function x(e,n,l){try{Promise.resolve(e()).then(()=>{m()||null==l||l()},n).catch(o)}catch(t){n(t)}}function w(){if("ended"!==c&&"destroyed"!==c&&!e.isDestroy){if(c="ended",v.stop(),i.dispose(),y){if(x(()=>e.play(),e=>{"destroyed"!==c&&o(e)}),h())return;if(b(null==p?void 0:p.video),h())return;null==p||p.hide()}h()||e.emit("artplayerPluginAds:skip",n)}}function D(){const e=null==p?void 0:p.video;if(b(e),e)try{e.removeAttribute("src"),e.load()}catch(n){o(n)}try{null==u||u.remove()}catch(n){o(n)}finally{d.$ads===u&&delete d.$ads}}function k(){"destroyed"!==c&&(c="destroyed",v.stop(),i.dispose(),r.dispose(),D())}function O(){if("waiting"===c&&!e.isDestroy){c="active",y=!0;try{if(p=function(e,n,l,t,i){const{append:r,query:o,setStyle:a}=t,d=r(e,'<div class="artplayer-plugin-ads"></div>');i(d);const u=r(d,l.video?'<video class="artplayer-plugin-ads-video" loop playsInline></video>':`<div class="artplayer-plugin-ads-html">${l.html}</div>`),p=l.video?u:null,c=r(d,'<div class="artplayer-plugin-ads-loading"></div>');r(c,n.loading);const y=r(d,'<div class="artplayer-plugin-ads-timer">\n <div class="artplayer-plugin-ads-close"></div>\n <div class="artplayer-plugin-ads-countdown"></div>\n </div>'),f=o(".artplayer-plugin-ads-close",y),g=o(".artplayer-plugin-ads-countdown",y),v=r(d,`<div class="artplayer-plugin-ads-control">\n <div class="artplayer-plugin-ads-detail">${l.i18n.detail}</div>\n <div class="artplayer-plugin-ads-muted"></div>\n <div class="artplayer-plugin-ads-fullscreen"></div>\n </div>`),m=o(".artplayer-plugin-ads-detail",v),h=o(".artplayer-plugin-ads-muted",v),b=o(".artplayer-plugin-ads-fullscreen",v);let x=l.playDuration<=0;function w(){a(n.volume,"display",(null==p?void 0:p.muted)?"none":"inline-flex"),a(n.volumeClose,"display",(null==p?void 0:p.muted)?"inline-flex":"none")}function D(e){const n=l.playDuration-e;x=0===e?l.playDuration<=0:n<1||Number.isNaN(n),f.innerHTML=x?l.i18n.close:s(n,l.i18n.canBeClosed),g.innerHTML=s(l.totalDuration-e,l.i18n.countdown)}return l.playDuration>=l.totalDuration&&a(f,"display","none"),l.url||a(m,"display","none"),p?(r(h,n.volume),r(h,n.volumeClose),p.muted=Boolean(l.muted),w()):a(h,"display","none"),r(b,n.fullscreenOn),r(b,n.fullscreenOff),D(0),{root:d,video:p,render:D,ready(){a(y,"display","flex"),a(v,"display","flex"),a(c,"display","none")},hide(){a(d,"display","none")},fullscreen(e){a(n.fullscreenOn,"display",e?"none":"inline-flex"),a(n.fullscreenOff,"display",e?"inline-flex":"none")},bind(e,n){e.dom(f,"click",()=>{x&&n.skip()}),e.dom(u,"click",n.click),l.url&&e.dom(m,"click",n.click),p&&e.dom(h,"click",()=>{p.muted=!p.muted,w()}),e.dom(b,"click",n.fullscreen)}}}(d.$player,l,n,t,e=>{u=e,d.$ads=e}),!m())return void D();if(e.pause(),!m())return;const r=p,a=u.ownerDocument;r.fullscreen(e.fullscreen),r.bind(i,{skip:w,click(){n.url&&(a.defaultView||window).open(n.url),m()&&e.emit("artplayerPluginAds:click",n)},fullscreenLine truncated
|
||||
@@ -4,247 +4,397 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
const style = ".artplayer-plugin-ads {\n position: absolute;\n z-index: 150;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n overflow: hidden;\n font-size: 13px;\n line-height: 1;\n color: #fff;\n background-color: #000;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-html {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-video {\n width: 100%;\n height: 100%;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-timer {\n display: none;\n position: absolute;\n top: 10px;\n right: 10px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-timer > div {\n display: flex;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.5);\n border-radius: 15px;\n margin-left: 5px;\n padding: 5px 10px;\n cursor: pointer;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control {\n display: none;\n position: absolute;\n bottom: 10px;\n right: 10px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control > div {\n display: flex;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.5);\n border-radius: 15px;\n margin-left: 5px;\n padding: 5px 10px;\n cursor: pointer;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg {\n width: 20px;\n height: 20px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n}\n";
|
||||
function checkVersion(art) {
|
||||
const {
|
||||
version,
|
||||
utils: { errorHandle }
|
||||
} = art.constructor;
|
||||
const arr = version.split(".").map(Number);
|
||||
const major = arr[0];
|
||||
const minor = arr[1] / 100;
|
||||
errorHandle(
|
||||
major + minor >= 5,
|
||||
`Artplayer.js@${version} is not compatible the artplayerPluginAds@${artplayerPluginAds.version}. Please update it to version Artplayer.js@5.x.x`
|
||||
);
|
||||
function normalizeOptions(input, validate) {
|
||||
return validate({
|
||||
html: "",
|
||||
video: "",
|
||||
url: "",
|
||||
playDuration: 5,
|
||||
totalDuration: 10,
|
||||
muted: false,
|
||||
i18n: {
|
||||
close: "关闭广告",
|
||||
countdown: "%s秒",
|
||||
detail: "查看详情",
|
||||
canBeClosed: "%s秒后可关闭广告"
|
||||
},
|
||||
...input
|
||||
}, {
|
||||
html: "?string",
|
||||
video: "?string",
|
||||
url: "?string",
|
||||
playDuration: "number",
|
||||
totalDuration: "number",
|
||||
muted: "?boolean",
|
||||
i18n: { close: "string", countdown: "string", detail: "string", canBeClosed: "string" }
|
||||
});
|
||||
}
|
||||
function artplayerPluginAds(option) {
|
||||
return (art) => {
|
||||
checkVersion(art);
|
||||
const {
|
||||
template: { $player },
|
||||
icons: { volume, volumeClose, fullscreenOn, fullscreenOff, loading },
|
||||
constructor: {
|
||||
validator,
|
||||
utils: { query, append, setStyle }
|
||||
}
|
||||
} = art;
|
||||
option = validator(
|
||||
{
|
||||
html: "",
|
||||
video: "",
|
||||
url: "",
|
||||
playDuration: 5,
|
||||
totalDuration: 10,
|
||||
muted: false,
|
||||
i18n: {
|
||||
close: "关闭广告",
|
||||
countdown: "%s秒",
|
||||
detail: "查看详情",
|
||||
canBeClosed: "%s秒后可关闭广告"
|
||||
},
|
||||
...option
|
||||
},
|
||||
{
|
||||
html: "?string",
|
||||
video: "?string",
|
||||
url: "?string",
|
||||
playDuration: "number",
|
||||
totalDuration: "number",
|
||||
muted: "?boolean",
|
||||
i18n: {
|
||||
close: "string",
|
||||
countdown: "string",
|
||||
detail: "string",
|
||||
canBeClosed: "string"
|
||||
}
|
||||
}
|
||||
);
|
||||
let $ads = null;
|
||||
let $timer = null;
|
||||
let $close = null;
|
||||
let $countdown = null;
|
||||
let $control = null;
|
||||
let $loading = null;
|
||||
let time = 0;
|
||||
let timer = null;
|
||||
let isEnd = false;
|
||||
let isInit = false;
|
||||
let isCanClose = false;
|
||||
function getI18n(val, str) {
|
||||
return str.replace("%s", val);
|
||||
}
|
||||
function skip() {
|
||||
isEnd = true;
|
||||
art.play();
|
||||
if (option.video)
|
||||
$ads.pause();
|
||||
setStyle(art.template.$ads, "display", "none");
|
||||
art.emit("artplayerPluginAds:skip", option);
|
||||
}
|
||||
function play() {
|
||||
if (isEnd)
|
||||
function createCountdown(total, render, complete) {
|
||||
let elapsed = 0;
|
||||
let ready = false;
|
||||
let running = false;
|
||||
let closed = false;
|
||||
let timer;
|
||||
function schedule() {
|
||||
if (!ready || !running || closed || timer !== void 0)
|
||||
return;
|
||||
timer = setTimeout(() => {
|
||||
timer = void 0;
|
||||
if (closed || !running)
|
||||
return;
|
||||
timer = setTimeout(() => {
|
||||
time += 1;
|
||||
const playDuration = option.playDuration - time;
|
||||
if (playDuration >= 1) {
|
||||
$close.innerHTML = getI18n(playDuration, option.i18n.canBeClosed);
|
||||
} else {
|
||||
$close.innerHTML = option.i18n.close;
|
||||
if (!isCanClose) {
|
||||
isCanClose = true;
|
||||
}
|
||||
}
|
||||
$countdown.innerHTML = getI18n(option.totalDuration - time, option.i18n.countdown);
|
||||
if (time >= option.totalDuration) {
|
||||
skip();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
}, 1e3);
|
||||
}
|
||||
function pause() {
|
||||
if (isEnd)
|
||||
elapsed += 1;
|
||||
render(elapsed);
|
||||
if (closed)
|
||||
return;
|
||||
if (elapsed >= total())
|
||||
complete();
|
||||
else
|
||||
schedule();
|
||||
}, 1e3);
|
||||
}
|
||||
function pause() {
|
||||
running = false;
|
||||
if (timer !== void 0)
|
||||
clearTimeout(timer);
|
||||
}
|
||||
function show() {
|
||||
art.template.$ads = append($player, '<div class="artplayer-plugin-ads"></div>');
|
||||
$ads = append(
|
||||
art.template.$ads,
|
||||
option.video ? `<video class="artplayer-plugin-ads-video" src="${option.video}" loop playsInline></video>` : `<div class="artplayer-plugin-ads-html">${option.html}</div>`
|
||||
);
|
||||
$loading = append(art.template.$ads, '<div class="artplayer-plugin-ads-loading"></div>');
|
||||
append($loading, loading);
|
||||
$timer = append(
|
||||
art.template.$ads,
|
||||
`<div class="artplayer-plugin-ads-timer">
|
||||
<div class="artplayer-plugin-ads-close">${option.playDuration <= 0 ? option.i18n.close : getI18n(option.playDuration, option.i18n.canBeClosed)}</div>
|
||||
<div class="artplayer-plugin-ads-countdown">${getI18n(
|
||||
option.totalDuration,
|
||||
option.i18n.countdown
|
||||
)}</div>
|
||||
</div>`
|
||||
);
|
||||
$close = query(".artplayer-plugin-ads-close", $timer);
|
||||
$countdown = query(".artplayer-plugin-ads-countdown", $timer);
|
||||
if (option.playDuration >= option.totalDuration) {
|
||||
setStyle($close, "display", "none");
|
||||
}
|
||||
art.proxy($close, "click", () => {
|
||||
if (isCanClose) {
|
||||
skip();
|
||||
}
|
||||
});
|
||||
$control = append(
|
||||
art.template.$ads,
|
||||
`<div class="artplayer-plugin-ads-control">
|
||||
<div class="artplayer-plugin-ads-detail">${option.i18n.detail}</div>
|
||||
<div class="artplayer-plugin-ads-muted"></div>
|
||||
<div class="artplayer-plugin-ads-fullscreen"></div>
|
||||
</div>`
|
||||
);
|
||||
const $detail = query(".artplayer-plugin-ads-detail", $control);
|
||||
const $muted = query(".artplayer-plugin-ads-muted", $control);
|
||||
const $fullscreen = query(".artplayer-plugin-ads-fullscreen", $control);
|
||||
if (option.url) {
|
||||
art.proxy($detail, "click", () => {
|
||||
window.open(option.url);
|
||||
art.emit("artplayerPluginAds:click", option);
|
||||
});
|
||||
} else {
|
||||
setStyle($detail, "display", "none");
|
||||
}
|
||||
if (option.video) {
|
||||
const $volume = append($muted, volume);
|
||||
const $volumeClose = append($muted, volumeClose);
|
||||
setStyle($volumeClose, "display", "none");
|
||||
if (option.muted) {
|
||||
$ads.muted = true;
|
||||
setStyle($volume, "display", "none");
|
||||
setStyle($volumeClose, "display", "inline-flex");
|
||||
}
|
||||
art.proxy($muted, "click", () => {
|
||||
$ads.muted = !$ads.muted;
|
||||
if ($ads.muted) {
|
||||
setStyle($volume, "display", "none");
|
||||
setStyle($volumeClose, "display", "inline-flex");
|
||||
} else {
|
||||
setStyle($volume, "display", "inline-flex");
|
||||
setStyle($volumeClose, "display", "none");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setStyle($muted, "display", "none");
|
||||
}
|
||||
const $fullscreenOn = append($fullscreen, fullscreenOn);
|
||||
const $fullscreenOff = append($fullscreen, fullscreenOff);
|
||||
setStyle($fullscreenOff, "display", "none");
|
||||
art.proxy($fullscreen, "click", () => {
|
||||
art.fullscreen = !art.fullscreen;
|
||||
if (art.fullscreen) {
|
||||
setStyle($fullscreenOn, "display", "inline-flex");
|
||||
setStyle($fullscreenOff, "display", "none");
|
||||
} else {
|
||||
setStyle($fullscreenOn, "display", "none");
|
||||
setStyle($fullscreenOff, "display", "inline-flex");
|
||||
}
|
||||
});
|
||||
art.proxy($ads, "click", () => {
|
||||
if (option.url)
|
||||
window.open(option.url);
|
||||
art.emit("artplayerPluginAds:click", option);
|
||||
});
|
||||
}
|
||||
function init() {
|
||||
if (isInit)
|
||||
timer = void 0;
|
||||
}
|
||||
return {
|
||||
start() {
|
||||
if (ready || closed)
|
||||
return;
|
||||
isInit = true;
|
||||
show();
|
||||
art.pause();
|
||||
if (option.video) {
|
||||
art.proxy($ads, "error", skip);
|
||||
art.proxy($ads, "loadedmetadata", () => {
|
||||
play();
|
||||
$ads.play();
|
||||
setStyle($timer, "display", "flex");
|
||||
setStyle($control, "display", "flex");
|
||||
setStyle($loading, "display", "none");
|
||||
});
|
||||
} else {
|
||||
play();
|
||||
setStyle($timer, "display", "flex");
|
||||
setStyle($control, "display", "flex");
|
||||
setStyle($loading, "display", "none");
|
||||
}
|
||||
art.on("document:visibilitychange", () => {
|
||||
if (document.hidden) {
|
||||
pause();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
});
|
||||
ready = true;
|
||||
running = true;
|
||||
schedule();
|
||||
},
|
||||
play() {
|
||||
if (!ready || closed)
|
||||
return;
|
||||
running = true;
|
||||
schedule();
|
||||
},
|
||||
pause,
|
||||
stop() {
|
||||
closed = true;
|
||||
pause();
|
||||
}
|
||||
art.on("ready", () => {
|
||||
art.once("play", init);
|
||||
art.once("video:playing", init);
|
||||
});
|
||||
return {
|
||||
name: "artplayerPluginAds",
|
||||
skip,
|
||||
pause,
|
||||
play
|
||||
};
|
||||
};
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
if (!document.getElementById("artplayer-plugin-ads")) {
|
||||
const $style = document.createElement("style");
|
||||
$style.id = "artplayer-plugin-ads";
|
||||
$style.textContent = style;
|
||||
document.head.appendChild($style);
|
||||
function report(error) {
|
||||
console.warn("Artplayer Ads:", error);
|
||||
}
|
||||
function createResources(host) {
|
||||
const cleaners = [];
|
||||
let closed = false;
|
||||
function install(add, remove) {
|
||||
if (closed)
|
||||
return;
|
||||
cleaners.push(remove);
|
||||
try {
|
||||
add();
|
||||
} finally {
|
||||
if (closed)
|
||||
remove();
|
||||
}
|
||||
}
|
||||
return {
|
||||
on(name, callback) {
|
||||
const listener = () => {
|
||||
if (!closed)
|
||||
callback();
|
||||
};
|
||||
install(() => {
|
||||
host.on(name, listener);
|
||||
}, () => {
|
||||
host.off(name, listener);
|
||||
});
|
||||
},
|
||||
dom(target, name, callback) {
|
||||
const listener = () => {
|
||||
if (!closed)
|
||||
callback();
|
||||
};
|
||||
install(() => target.addEventListener(name, listener), () => target.removeEventListener(name, listener));
|
||||
},
|
||||
dispose() {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
for (const clean of cleaners.splice(0).reverse()) {
|
||||
try {
|
||||
clean();
|
||||
} catch (error) {
|
||||
report(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
const translate = (value, text) => text.replace("%s", String(value));
|
||||
function createView(parent, icons, option, utils, own) {
|
||||
const { append, query, setStyle } = utils;
|
||||
const root = append(parent, '<div class="artplayer-plugin-ads"></div>');
|
||||
own(root);
|
||||
const content = append(root, option.video ? '<video class="artplayer-plugin-ads-video" loop playsInline></video>' : `<div class="artplayer-plugin-ads-html">${option.html}</div>`);
|
||||
const video = option.video ? content : null;
|
||||
const loading = append(root, '<div class="artplayer-plugin-ads-loading"></div>');
|
||||
append(loading, icons.loading);
|
||||
const timer = append(root, `<div class="artplayer-plugin-ads-timer">
|
||||
<div class="artplayer-plugin-ads-close"></div>
|
||||
<div class="artplayer-plugin-ads-countdown"></div>
|
||||
</div>`);
|
||||
const close = query(".artplayer-plugin-ads-close", timer);
|
||||
const countdown = query(".artplayer-plugin-ads-countdown", timer);
|
||||
const control = append(root, `<div class="artplayer-plugin-ads-control">
|
||||
<div class="artplayer-plugin-ads-detail">${option.i18n.detail}</div>
|
||||
<div class="artplayer-plugin-ads-muted"></div>
|
||||
<div class="artplayer-plugin-ads-fullscreen"></div>
|
||||
</div>`);
|
||||
const detail = query(".artplayer-plugin-ads-detail", control);
|
||||
const muted = query(".artplayer-plugin-ads-muted", control);
|
||||
const fullscreen = query(".artplayer-plugin-ads-fullscreen", control);
|
||||
let canClose = option.playDuration <= 0;
|
||||
if (option.playDuration >= option.totalDuration)
|
||||
setStyle(close, "display", "none");
|
||||
if (!option.url)
|
||||
setStyle(detail, "display", "none");
|
||||
if (video) {
|
||||
append(muted, icons.volume);
|
||||
append(muted, icons.volumeClose);
|
||||
video.muted = Boolean(option.muted);
|
||||
syncMuted();
|
||||
} else {
|
||||
setStyle(muted, "display", "none");
|
||||
}
|
||||
append(fullscreen, icons.fullscreenOn);
|
||||
append(fullscreen, icons.fullscreenOff);
|
||||
function syncMuted() {
|
||||
setStyle(icons.volume, "display", video?.muted ? "none" : "inline-flex");
|
||||
setStyle(icons.volumeClose, "display", video?.muted ? "inline-flex" : "none");
|
||||
}
|
||||
function render(elapsed) {
|
||||
const remaining = option.playDuration - elapsed;
|
||||
canClose = elapsed === 0 ? option.playDuration <= 0 : remaining < 1 || Number.isNaN(remaining);
|
||||
close.innerHTML = canClose ? option.i18n.close : translate(remaining, option.i18n.canBeClosed);
|
||||
countdown.innerHTML = translate(option.totalDuration - elapsed, option.i18n.countdown);
|
||||
}
|
||||
render(0);
|
||||
return {
|
||||
root,
|
||||
video,
|
||||
render,
|
||||
ready() {
|
||||
setStyle(timer, "display", "flex");
|
||||
setStyle(control, "display", "flex");
|
||||
setStyle(loading, "display", "none");
|
||||
},
|
||||
hide() {
|
||||
setStyle(root, "display", "none");
|
||||
},
|
||||
fullscreen(active) {
|
||||
setStyle(icons.fullscreenOn, "display", active ? "none" : "inline-flex");
|
||||
setStyle(icons.fullscreenOff, "display", active ? "inline-flex" : "none");
|
||||
},
|
||||
bind(events, callbacks) {
|
||||
events.dom(close, "click", () => {
|
||||
if (canClose)
|
||||
callbacks.skip();
|
||||
});
|
||||
events.dom(content, "click", callbacks.click);
|
||||
if (option.url)
|
||||
events.dom(detail, "click", callbacks.click);
|
||||
if (video) {
|
||||
events.dom(muted, "click", () => {
|
||||
video.muted = !video.muted;
|
||||
syncMuted();
|
||||
});
|
||||
}
|
||||
events.dom(fullscreen, "click", callbacks.fullscreen);
|
||||
}
|
||||
};
|
||||
}
|
||||
function createSession(art, option, icons, utils) {
|
||||
const events = createResources(art);
|
||||
const lifetime = createResources(art);
|
||||
const template = art.template;
|
||||
let state = "waiting";
|
||||
let initialized = false;
|
||||
let armed = false;
|
||||
let mediaReady = false;
|
||||
let root;
|
||||
let view;
|
||||
const clock = createCountdown(() => option.totalDuration, (time) => view?.render(time), skip);
|
||||
const active = () => state === "active" && !art.isDestroy;
|
||||
const destroyed = () => state === "destroyed" || art.isDestroy;
|
||||
function pauseVideo(video) {
|
||||
try {
|
||||
video?.pause();
|
||||
} catch (error) {
|
||||
report(error);
|
||||
}
|
||||
}
|
||||
function requestPlay(start, rejected, late) {
|
||||
try {
|
||||
Promise.resolve(start()).then(() => {
|
||||
if (!active())
|
||||
late?.();
|
||||
}, rejected).catch(report);
|
||||
} catch (error) {
|
||||
rejected(error);
|
||||
}
|
||||
}
|
||||
function skip() {
|
||||
if (state === "ended" || state === "destroyed" || art.isDestroy)
|
||||
return;
|
||||
state = "ended";
|
||||
clock.stop();
|
||||
events.dispose();
|
||||
if (initialized) {
|
||||
requestPlay(() => art.play(), (error) => {
|
||||
if (state !== "destroyed")
|
||||
report(error);
|
||||
});
|
||||
if (destroyed())
|
||||
return;
|
||||
pauseVideo(view?.video);
|
||||
if (destroyed())
|
||||
return;
|
||||
view?.hide();
|
||||
}
|
||||
if (!destroyed())
|
||||
art.emit("artplayerPluginAds:skip", option);
|
||||
}
|
||||
function disposeView() {
|
||||
const video = view?.video;
|
||||
pauseVideo(video);
|
||||
if (video) {
|
||||
try {
|
||||
video.removeAttribute("src");
|
||||
video.load();
|
||||
} catch (error) {
|
||||
report(error);
|
||||
}
|
||||
}
|
||||
try {
|
||||
root?.remove();
|
||||
} catch (error) {
|
||||
report(error);
|
||||
} finally {
|
||||
if (template.$ads === root)
|
||||
delete template.$ads;
|
||||
}
|
||||
}
|
||||
function destroy() {
|
||||
if (state === "destroyed")
|
||||
return;
|
||||
state = "destroyed";
|
||||
clock.stop();
|
||||
events.dispose();
|
||||
lifetime.dispose();
|
||||
disposeView();
|
||||
}
|
||||
function init() {
|
||||
if (state !== "waiting" || art.isDestroy)
|
||||
return;
|
||||
state = "active";
|
||||
initialized = true;
|
||||
try {
|
||||
view = createView(template.$player, icons, option, utils, (node) => {
|
||||
root = node;
|
||||
template.$ads = node;
|
||||
});
|
||||
if (!active()) {
|
||||
disposeView();
|
||||
return;
|
||||
}
|
||||
art.pause();
|
||||
if (!active())
|
||||
return;
|
||||
const current = view;
|
||||
const document2 = root.ownerDocument;
|
||||
current.fullscreen(art.fullscreen);
|
||||
current.bind(events, {
|
||||
skip,
|
||||
click() {
|
||||
if (option.url)
|
||||
(document2.defaultView || window).open(option.url);
|
||||
if (active())
|
||||
art.emit("artplayerPluginAds:click", option);
|
||||
},
|
||||
fullscreen() {
|
||||
art.fullscreen = !art.fullscreen;
|
||||
if (active())
|
||||
current.fullscreen(art.fullscreen);
|
||||
}
|
||||
});
|
||||
events.on("fullscreen", () => current.fullscreen(art.fullscreen));
|
||||
const visibility = () => document2.hidden ? clock.pause() : clock.play();
|
||||
events.dom(document2, "visibilitychange", visibility);
|
||||
events.on("document:visibilitychange", visibility);
|
||||
const ready = () => {
|
||||
if (!active() || mediaReady)
|
||||
return;
|
||||
mediaReady = true;
|
||||
clock.start();
|
||||
if (document2.hidden)
|
||||
clock.pause();
|
||||
if (current.video) {
|
||||
const video = current.video;
|
||||
requestPlay(() => video.play(), (error) => {
|
||||
if (active()) {
|
||||
report(error);
|
||||
skip();
|
||||
}
|
||||
}, () => pauseVideo(video));
|
||||
}
|
||||
if (active())
|
||||
current.ready();
|
||||
};
|
||||
if (current.video) {
|
||||
events.dom(current.video, "error", skip);
|
||||
events.dom(current.video, "loadedmetadata", ready);
|
||||
current.video.src = option.video;
|
||||
} else {
|
||||
ready();
|
||||
}
|
||||
} catch (error) {
|
||||
destroy();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
try {
|
||||
lifetime.on("destroy", destroy);
|
||||
events.on("ready", () => {
|
||||
if (armed || state !== "waiting")
|
||||
return;
|
||||
armed = true;
|
||||
events.on("play", init);
|
||||
events.on("video:playing", init);
|
||||
});
|
||||
if (art.isDestroy)
|
||||
destroy();
|
||||
} catch (error) {
|
||||
destroy();
|
||||
throw error;
|
||||
}
|
||||
return { name: "artplayerPluginAds", skip, pause: clock.pause, play: clock.play };
|
||||
}
|
||||
const style = ".artplayer-plugin-ads {\n position: absolute;\n z-index: 150;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n overflow: hidden;\n font-size: 13px;\n line-height: 1;\n color: #fff;\n background-color: #000;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-html {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-video {\n width: 100%;\n height: 100%;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-timer {\n display: none;\n position: absolute;\n top: 10px;\n right: 10px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-timer > div {\n display: flex;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.5);\n border-radius: 15px;\n margin-left: 5px;\n padding: 5px 10px;\n cursor: pointer;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control {\n display: none;\n position: absolute;\n bottom: 10px;\n right: 10px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control > div {\n display: flex;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.5);\n border-radius: 15px;\n margin-left: 5px;\n padding: 5px 10px;\n cursor: pointer;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg {\n width: 20px;\n height: 20px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n}\n";
|
||||
function artplayerPluginAds(input) {
|
||||
return (art) => {
|
||||
const constructor = art.constructor;
|
||||
if (typeof constructor.validator !== "function" || !["append", "query", "setStyle"].every((name) => typeof Reflect.get(constructor.utils || {}, name) === "function")) {
|
||||
throw new Error("Artplayer Ads requires the core validator and DOM utilities");
|
||||
}
|
||||
const option = normalizeOptions(input, constructor.validator);
|
||||
const { volume, volumeClose, fullscreenOn, fullscreenOff, loading } = art.icons;
|
||||
return createSession(art, option, { volume, volumeClose, fullscreenOn, fullscreenOff, loading }, constructor.utils);
|
||||
};
|
||||
}
|
||||
if (typeof document !== "undefined" && !document.getElementById("artplayer-plugin-ads")) {
|
||||
const element = document.createElement("style");
|
||||
element.id = "artplayer-plugin-ads";
|
||||
element.textContent = style;
|
||||
document.head.appendChild(element);
|
||||
}
|
||||
export {
|
||||
artplayerPluginAds as default
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@
|
||||
"build:i18n": "node ./scripts/build-i18n.js",
|
||||
"build:test": "node ./scripts/build-test.js",
|
||||
"test:playback": "node --test test/playback.test.js",
|
||||
"test:ads": "node --test test/ads.test.js",
|
||||
"test:ads": "node --test test/ads.test.js test/ads-lifecycle.test.js",
|
||||
"trans:docs": "node ./scripts/trans-docs.js",
|
||||
"create:plugin": "node ./scripts/plugin/create.js",
|
||||
"test:dash-control": "node --test test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js",
|
||||
@@ -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",
|
||||
"typecheck": "node scripts/typecheck.mjs",
|
||||
"test:unit": "node --test test/ads.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:unit": "node --test 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:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
|
||||
"test": "yarn test:node && yarn test:baseline",
|
||||
"test:browser": "playwright test",
|
||||
|
||||
@@ -2,4 +2,5 @@ node_modules
|
||||
.DS_Store
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
src
|
||||
src
|
||||
tsconfig.json
|
||||
@@ -0,0 +1,83 @@
|
||||
# Ads implementation and maintenance
|
||||
|
||||
Ads is a single preroll attached by `artplayerPluginAds(option)(art)`. The factory and
|
||||
returned `{ name, skip, pause, play }` are synchronous. Package public declarations are
|
||||
still scheduled for PKG-ADS-04; do not infer that their current source/type fields are
|
||||
implemented runtime aliases. Runtime input remains html/video/url plus timing, mute and i18n.
|
||||
|
||||
## Module boundaries
|
||||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| src/index.ts | Public factory, checked legacy constructor capabilities, import-time style injection |
|
||||
| src/options.ts | Per-attachment defaults and the existing shallow validator schema |
|
||||
| src/types.ts | Internal options, result and narrow core/DOM dependencies; public types remain separate for now |
|
||||
| src/countdown.ts | One owned timeout, whole-second counting, pause/resume and terminal cancellation; no core or DOM dependency |
|
||||
| src/view.ts | Existing HTML/CSS hooks, labels, ad element, mute and fullscreen icon rendering |
|
||||
| src/resources.ts | Instance-owned core/DOM subscriptions, guarded callbacks and disposal that continues after cleanup failures |
|
||||
| src/session.ts | Ready/first-play lifecycle, ad readiness, playback requests, completion, destruction and cross-module ownership |
|
||||
|
||||
The entry calls options/session. Session composes view/countdown/resources. View receives
|
||||
only a parent, icons, options, three DOM utilities and callbacks, rather than the whole player.
|
||||
Style remains in src/style.less. There are no runtime imports from the ArtPlayer core package;
|
||||
all imports of its declarations are erased. Old cores do not need new lifecycle helpers.
|
||||
|
||||
## State and ordering
|
||||
|
||||
`waiting -> active -> ended`, with any state able to become `destroyed`. Ready arms the two
|
||||
existing first-play signals; only one can initialize. Repeated ready/play/metadata cannot
|
||||
create another view or timer. The normalized option belongs to this attachment even when a
|
||||
caller reuses the factory. Emitted options retain their identity and remain live: for example,
|
||||
changing totalDuration in an ad-click listener affects subsequent ticks.
|
||||
|
||||
For video, the element is created without a source. Error and loadedmetadata listeners are
|
||||
installed before assigning src. Metadata starts the timer and requests ad playback. HTML
|
||||
starts immediately. The ad video loops independently from the totalDuration countdown.
|
||||
Public pause/play still control only the countdown; they do not pause/resume the ad video.
|
||||
Native document visibility and the newer core visibility bridge both control that same timer;
|
||||
idempotent resume prevents duplicate timer chains. Initially hidden documents pause counting.
|
||||
|
||||
Skip first closes the timer and active subscriptions. For an initialized ad it requests main
|
||||
content playback, pauses the ad, hides the overlay and synchronously emits
|
||||
artplayerPluginAds:skip with the normalized option. This keeps the historical successful
|
||||
ordering. Reentrant destruction prevents subsequent dispatch. Repeated completion is inert.
|
||||
Before initialization, play/pause are inert; skip cancels the pending preroll and emits once
|
||||
without starting main playback or creating DOM. These early-call semantics repair historical
|
||||
exceptions and are covered separately from normal old behavior.
|
||||
|
||||
Internally requested play Promises are observed. An ad play rejection warns with the original
|
||||
error and completes the ad like a media load failure. Rejected main-content restoration also
|
||||
warns, while completion remains visible and synchronous. Direct consumer calls to art.play
|
||||
are not wrapped or changed. If an ad play settles after its owner ended/died, its own video is
|
||||
paused again; stale rejections cannot restart content.
|
||||
|
||||
## Resource ownership
|
||||
|
||||
The session owns one countdown, active core/native listeners, a separate destroy subscription,
|
||||
and the overlay. Finishing releases active listeners and pauses video, but keeps the hidden
|
||||
overlay and source/error state available until destroy. Destroy cancels the timer, disposes
|
||||
all owned subscriptions, pauses/releases the ad src via load, and removes the overlay even
|
||||
when the core keeps its HTML. Only an owned template.$ads value is removed. Foreign DOM
|
||||
listeners and caller-owned replacement template references are not removed.
|
||||
|
||||
Native DOM listeners are locally owned; the plugin does not mutate the core event registry
|
||||
or depend on new resource APIs. Cleanup guards stale callbacks and handles partial view
|
||||
construction/registration failure. A failure while cleaning one subscription does not stop
|
||||
the remaining cleanup. Root ownership is recorded as soon as it is appended.
|
||||
|
||||
Keep all artplayer-plugin-ads class names and the template.$ads hook. The full screen button
|
||||
uses the core fullscreen property; its icons reflect both the initial value and the core
|
||||
fullscreen event. Actual device/native fullscreen acceptance remains part of PKG-ADS-05.
|
||||
|
||||
## Verification and remaining work
|
||||
|
||||
Use pinned Yarn/Node. `yarn test:ads` covers source/frozen releases and candidate lifecycle
|
||||
regressions; `yarn test:browser test/browser/ads.spec.js` covers real local media and old/new
|
||||
cores. `yarn typecheck` checks the strict package configuration. `yarn build artplayer-plugin-ads`
|
||||
generates main/legacy/ESM through the repository build. Do not hand-edit dist or copied docs assets.
|
||||
|
||||
Tests distinguish actual media decoding from injected errors/controlled clocks. Baseline and
|
||||
open release gates live in [Ads validation](../../refactor/ads-validation.md). Public declaration
|
||||
reconciliation, old CommonJS default compatibility, isolated package consumption and demo
|
||||
validation remain PKG-ADS-04/06. Real hidden-page/device behavior and complete media-resource
|
||||
acceptance remain PKG-ADS-05. No claim of compatibility with every historical 4.x core is made.
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
Ads plugin for ArtPlayer
|
||||
|
||||
Implementation, resource ownership and regression commands:
|
||||
[maintenance guide](ARCHITECTURE.md).
|
||||
|
||||
## Demo
|
||||
|
||||
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-ads/index.js&example=ads)
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,l){"object"===typeof exports&&"undefined"!==typeof module?module.exports=l():"function"==typeof define&&define.amd?(t.artplayerPluginAds=l(),define(function(){return t.artplayerPluginAds})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAds=l()}(this,function(){"use strict";function e(l){return n=>{!function(l){const{version:n,utils:{errorHandle:i}}=l.constructor,a=n.split(".").map(Number);i(a[0]+a[1]/100>=5,`Artplayer.js@${n} is not compatible the artplayerPluginAds@${e.version}. Please update it to version Artplayer.js@5.x.x`)}(n);const{template:{$player:i},icons:{volume:a,volumeClose:t,fullscreenOn:r,fullscreenOff:o,loading:d},constructor:{validator:s,utils:{query:p,append:u,setStyle:c}}}=n;l=s({html:"",video:"",url:"",playDuration:5,totalDuration:10,muted:!1,i18n:{close:"\u5173\u95ed\u5e7f\u544a",countdown:"%s\u79d2",detail:"\u67e5\u770b\u8be6\u60c5",canBeClosed:"%s\u79d2\u540e\u53ef\u5173\u95ed\u5e7f\u544a"},...l},{html:"?string",video:"?string",url:"?string",playDuration:"number",totalDuration:"number",muted:"?boolean",i18n:{close:"string",countdown:"string",detail:"string",canBeClosed:"string"}});let y=null,g=null,f=null,m=null,v=null,x=null,h=0,b=null,w=!1,$=!1,k=!1;function D(e,l){return l.replace("%s",e)}function A(){w=!0,n.play(),l.video&&y.pause(),c(n.template.$ads,"display","none"),n.emit("artplayerPluginAds:skip",l)}function C(){w||(b=setTimeout(()=>{h+=1;const e=l.playDuration-h;e>=1?f.innerHTML=D(e,l.i18n.canBeClosed):(f.innerHTML=l.i18n.close,k||(k=!0)),m.innerHTML=D(l.totalDuration-h,l.i18n.countdown),h>=l.totalDuration?A():C()},1e3))}function P(){w||clearTimeout(b)}function T(){$||($=!0,function(){n.template.$ads=u(i,'<div class="artplayer-plugin-ads"></div>'),y=u(n.template.$ads,l.video?`<video class="artplayer-plugin-ads-video" src="${l.video}" loop playsInline></video>`:`<div class="artplayer-plugin-ads-html">${l.html}</div>`),x=u(n.template.$ads,'<div class="artplayer-plugin-ads-loading"></div>'),u(x,d),g=u(n.template.$ads,`<div class="artplayer-plugin-ads-timer">\n <div class="artplayer-plugin-ads-close">${l.playDuration<=0?l.i18n.close:D(l.playDuration,l.i18n.canBeClosed)}</div>\n <div class="artplayer-plugin-ads-countdown">${D(l.totalDuration,l.i18n.countdown)}</div>\n </div>`),f=p(".artplayer-plugin-ads-close",g),m=p(".artplayer-plugin-ads-countdown",g),l.playDuration>=l.totalDuration&&c(f,"display","none"),n.proxy(f,"click",()=>{k&&A()}),v=u(n.template.$ads,`<div class="artplayer-plugin-ads-control">\n <div class="artplayer-plugin-ads-detail">${l.i18n.detail}</div>\n <div class="artplayer-plugin-ads-muted"></div>\n <div class="artplayer-plugin-ads-fullscreen"></div>\n </div>`);const e=p(".artplayer-plugin-ads-detail",v),s=p(".artplayer-plugin-ads-muted",v),h=p(".artplayer-plugin-ads-fullscreen",v);if(l.url?n.proxy(e,"click",()=>{window.open(l.url),n.emit("artplayerPluginAds:click",l)}):c(e,"display","none"),l.video){const e=u(s,a),i=u(s,t);c(i,"display","none"),l.muted&&(y.muted=!0,c(e,"display","none"),c(i,"display","inline-flex")),n.proxy(s,"click",()=>{y.muted=!y.muted,y.muted?(c(e,"display","none"),c(i,"display","inline-flex")):(c(e,"display","inline-flex"),c(i,"display","none"))})}else c(s,"display","none");const b=u(h,r),w=u(h,o);c(w,"display","none"),n.proxy(h,"click",()=>{n.fullscreen=!n.fullscreen,n.fullscreen?(c(b,"display","inline-flex"),c(w,"display","none")):(c(b,"display","none"),c(w,"display","inline-flex"))}),n.proxy(y,"click",()=>{l.url&&window.open(l.url),n.emit("artplayerPluginAds:click",l)})}(),n.pause(),l.video?(n.proxy(y,"error",A),n.proxy(y,"loadedmetadata",()=>{C(),y.play(),c(g,"display","flex"),c(v,"display","flex"),c(x,"display","none")})):(C(),c(g,"display","flex"),c(v,"display","flex"),c(x,"display","none")),n.on("document:visibilitychange",()=>{document.hidden?P():C()}))}return n.on("ready",()=>{n.once("play",T),n.once("video:playing",T)}),{name:"artplayerPluginAds",skip:A,pause:P,play:C}}}if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-ads")){const e=document.createElement("style");e.id="artplayer-plugin-ads",e.textContent=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:none;position:absolute;top:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-timer>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control{display:none;position:absolute;bottom:10px;right:10px}.artplLine truncated
|
||||
!function(e,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(e.artplayerPluginAds=n(),define(function(){return e.artplayerPluginAds})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAds=n()}(this,function(){"use strict";function e(e){console.warn("Artplayer Ads:",e)}function n(n){const t=[];let i=!1;function l(e,n){if(!i){t.push(n);try{e()}finally{i&&n()}}}return{on(e,t){const a=()=>{i||t()};l(()=>{n.on(e,a)},()=>{n.off(e,a)})},dom(e,n,t){const a=()=>{i||t()};l(()=>e.addEventListener(n,a),()=>e.removeEventListener(n,a))},dispose(){if(!i){i=!0;for(const i of t.splice(0).reverse())try{i()}catch(n){e(n)}}}}}const t=(e,n)=>n.replace("%s",String(e));function i(i,l,a,o){const r=n(i),s=n(i),d=i.template;let u,p,c="waiting",y=!1,f=!1,g=!1;const v=function(e,n,t){let i,l=0,a=!1,o=!1,r=!1;function s(){a&&o&&!r&&void 0===i&&(i=setTimeout(()=>{i=void 0,!r&&o&&(l+=1,n(l),r||(l>=e()?t():s()))},1e3))}function d(){o=!1,void 0!==i&&clearTimeout(i),i=void 0}return{start(){a||r||(a=!0,o=!0,s())},play(){a&&!r&&(o=!0,s())},pause:d,stop(){r=!0,d()}}}(()=>l.totalDuration,e=>p?.render(e),b),m=()=>"active"===c&&!i.isDestroy,h=()=>"destroyed"===c||i.isDestroy;function x(n){try{n?.pause()}catch(t){e(t)}}function w(n,t,i){try{Promise.resolve(n()).then(()=>{m()||i?.()},t).catch(e)}catch(l){t(l)}}function b(){if("ended"!==c&&"destroyed"!==c&&!i.isDestroy){if(c="ended",v.stop(),r.dispose(),y){if(w(()=>i.play(),n=>{"destroyed"!==c&&e(n)}),h())return;if(x(p?.video),h())return;p?.hide()}h()||i.emit("artplayerPluginAds:skip",l)}}function D(){const n=p?.video;if(x(n),n)try{n.removeAttribute("src"),n.load()}catch(t){e(t)}try{u?.remove()}catch(t){e(t)}finally{d.$ads===u&&delete d.$ads}}function k(){"destroyed"!==c&&(c="destroyed",v.stop(),r.dispose(),s.dispose(),D())}function A(){if("waiting"===c&&!i.isDestroy){c="active",y=!0;try{if(p=function(e,n,i,l,a){const{append:o,query:r,setStyle:s}=l,d=o(e,'<div class="artplayer-plugin-ads"></div>');a(d);const u=o(d,i.video?'<video class="artplayer-plugin-ads-video" loop playsInline></video>':`<div class="artplayer-plugin-ads-html">${i.html}</div>`),p=i.video?u:null,c=o(d,'<div class="artplayer-plugin-ads-loading"></div>');o(c,n.loading);const y=o(d,'<div class="artplayer-plugin-ads-timer">\n <div class="artplayer-plugin-ads-close"></div>\n <div class="artplayer-plugin-ads-countdown"></div>\n </div>'),f=r(".artplayer-plugin-ads-close",y),g=r(".artplayer-plugin-ads-countdown",y),v=o(d,`<div class="artplayer-plugin-ads-control">\n <div class="artplayer-plugin-ads-detail">${i.i18n.detail}</div>\n <div class="artplayer-plugin-ads-muted"></div>\n <div class="artplayer-plugin-ads-fullscreen"></div>\n </div>`),m=r(".artplayer-plugin-ads-detail",v),h=r(".artplayer-plugin-ads-muted",v),x=r(".artplayer-plugin-ads-fullscreen",v);let w=i.playDuration<=0;function b(){s(n.volume,"display",p?.muted?"none":"inline-flex"),s(n.volumeClose,"display",p?.muted?"inline-flex":"none")}function D(e){const n=i.playDuration-e;w=0===e?i.playDuration<=0:n<1||Number.isNaN(n),f.innerHTML=w?i.i18n.close:t(n,i.i18n.canBeClosed),g.innerHTML=t(i.totalDuration-e,i.i18n.countdown)}return i.playDuration>=i.totalDuration&&s(f,"display","none"),i.url||s(m,"display","none"),p?(o(h,n.volume),o(h,n.volumeClose),p.muted=Boolean(i.muted),b()):s(h,"display","none"),o(x,n.fullscreenOn),o(x,n.fullscreenOff),D(0),{root:d,video:p,render:D,ready(){s(y,"display","flex"),s(v,"display","flex"),s(c,"display","none")},hide(){s(d,"display","none")},fullscreen(e){s(n.fullscreenOn,"display",e?"none":"inline-flex"),s(n.fullscreenOff,"display",e?"inline-flex":"none")},bind(e,n){e.dom(f,"click",()=>{w&&n.skip()}),e.dom(u,"click",n.click),i.url&&e.dom(m,"click",n.click),p&&e.dom(h,"click",()=>{p.muted=!p.muted,b()}),e.dom(x,"click",n.fullscreen)}}}(d.$player,a,l,o,e=>{u=e,d.$ads=e}),!m())return void D();if(i.pause(),!m())return;const n=p,s=u.ownerDocument;n.fullscreen(i.fullscreen),n.bind(r,{skip:b,click(){l.url&&(s.defaultView||window).open(l.url),m()&&i.emit("artplayerPluginAds:click",l)},fullscreen(){i.fullscreen=!i.fullscreen,m()&&n.fullscreen(i.fullscreen)}}),r.on("fullscreen",()=>n.fullscreen(i.fullscreen));const c=()=>s.hidden?v.pause():v.play();r.dom(s,"visibilitychange",c),r.on("document:visibilitychange",c);const y=()=>{if(m()&&!g){if(g=!0,v.start(),s.hidden&&v.pause(),n.video){const t=n.video;w(()=>t.play(),n=>{m()&&(e(n),b())},()=>x(t))}m()&&n.ready()}};n.video?(r.dom(n.video,"error",b),r.dom(n.video,"loadedmetadata",y),n.video.src=l.video):y()}catch(n){throw k(),n}}}try{s.on("destroy",k),r.on("ready",()=>{f||"waiting"!==c||(f=!0,r.on("play",A),r.on("video:playing",A))}),i.isDestroy&&k()}catch(C){throw k(),C}return{name:"artplayerPluginAds",skip:b,pause:v.pause,play:v.play}}if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-ads")){const e=document.createElement("style");e.id="artplayer-plugin-ads",e.textContent=".artplayeLine truncated
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,l){"object"===typeof exports&&"undefined"!==typeof module?module.exports=l():"function"==typeof define&&define.amd?(t.artplayerPluginAds=l(),define(function(){return t.artplayerPluginAds})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAds=l()}(this,function(){"use strict";var e=Object.defineProperty,l=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable,i=(l,n,a)=>n in l?e(l,n,{enumerable:!0,configurable:!0,writable:!0,value:a}):l[n]=a;function t(e){return r=>{!function(e){const{version:l,utils:{errorHandle:n}}=e.constructor,a=l.split(".").map(Number);n(a[0]+a[1]/100>=5,`Artplayer.js@${l} is not compatible the artplayerPluginAds@${t.version}. Please update it to version Artplayer.js@5.x.x`)}(r);const{template:{$player:o},icons:{volume:d,volumeClose:p,fullscreenOn:s,fullscreenOff:u,loading:c},constructor:{validator:y,utils:{query:g,append:f,setStyle:m}}}=r;e=y(((e,t)=>{for(var r in t||(t={}))n.call(t,r)&&i(e,r,t[r]);if(l)for(var r of l(t))a.call(t,r)&&i(e,r,t[r]);return e})({html:"",video:"",url:"",playDuration:5,totalDuration:10,muted:!1,i18n:{close:"\u5173\u95ed\u5e7f\u544a",countdown:"%s\u79d2",detail:"\u67e5\u770b\u8be6\u60c5",canBeClosed:"%s\u79d2\u540e\u53ef\u5173\u95ed\u5e7f\u544a"}},e),{html:"?string",video:"?string",url:"?string",playDuration:"number",totalDuration:"number",muted:"?boolean",i18n:{close:"string",countdown:"string",detail:"string",canBeClosed:"string"}});let v=null,x=null,h=null,b=null,w=null,$=null,k=0,D=null,P=!1,j=!1,A=!1;function O(e,l){return l.replace("%s",e)}function C(){P=!0,r.play(),e.video&&v.pause(),m(r.template.$ads,"display","none"),r.emit("artplayerPluginAds:skip",e)}function T(){P||(D=setTimeout(()=>{k+=1;const l=e.playDuration-k;l>=1?h.innerHTML=O(l,e.i18n.canBeClosed):(h.innerHTML=e.i18n.close,A||(A=!0)),b.innerHTML=O(e.totalDuration-k,e.i18n.countdown),k>=e.totalDuration?C():T()},1e3))}function B(){P||clearTimeout(D)}function H(){j||(j=!0,function(){r.template.$ads=f(o,'<div class="artplayer-plugin-ads"></div>'),v=f(r.template.$ads,e.video?`<video class="artplayer-plugin-ads-video" src="${e.video}" loop playsInline></video>`:`<div class="artplayer-plugin-ads-html">${e.html}</div>`),$=f(r.template.$ads,'<div class="artplayer-plugin-ads-loading"></div>'),f($,c),x=f(r.template.$ads,`<div class="artplayer-plugin-ads-timer">\n <div class="artplayer-plugin-ads-close">${e.playDuration<=0?e.i18n.close:O(e.playDuration,e.i18n.canBeClosed)}</div>\n <div class="artplayer-plugin-ads-countdown">${O(e.totalDuration,e.i18n.countdown)}</div>\n </div>`),h=g(".artplayer-plugin-ads-close",x),b=g(".artplayer-plugin-ads-countdown",x),e.playDuration>=e.totalDuration&&m(h,"display","none"),r.proxy(h,"click",()=>{A&&C()}),w=f(r.template.$ads,`<div class="artplayer-plugin-ads-control">\n <div class="artplayer-plugin-ads-detail">${e.i18n.detail}</div>\n <div class="artplayer-plugin-ads-muted"></div>\n <div class="artplayer-plugin-ads-fullscreen"></div>\n </div>`);const l=g(".artplayer-plugin-ads-detail",w),n=g(".artplayer-plugin-ads-muted",w),a=g(".artplayer-plugin-ads-fullscreen",w);if(e.url?r.proxy(l,"click",()=>{window.open(e.url),r.emit("artplayerPluginAds:click",e)}):m(l,"display","none"),e.video){const l=f(n,d),a=f(n,p);m(a,"display","none"),e.muted&&(v.muted=!0,m(l,"display","none"),m(a,"display","inline-flex")),r.proxy(n,"click",()=>{v.muted=!v.muted,v.muted?(m(l,"display","none"),m(a,"display","inline-flex")):(m(l,"display","inline-flex"),m(a,"display","none"))})}else m(n,"display","none");const i=f(a,s),t=f(a,u);m(t,"display","none"),r.proxy(a,"click",()=>{r.fullscreen=!r.fullscreen,r.fullscreen?(m(i,"display","inline-flex"),m(t,"display","none")):(m(i,"display","none"),m(t,"display","inline-flex"))}),r.proxy(v,"click",()=>{e.url&&window.open(e.url),r.emit("artplayerPluginAds:click",e)})}(),r.pause(),e.video?(r.proxy(v,"error",C),r.proxy(v,"loadedmetadata",()=>{T(),v.play(),m(x,"display","flex"),m(w,"display","flex"),m($,"display","none")})):(T(),m(x,"display","flex"),m(w,"display","flex"),m($,"display","none")),r.on("document:visibilitychange",()=>{document.hidden?B():T()}))}return r.on("ready",()=>{r.once("play",H),r.once("video:playing",H)}),{name:"artplayerPluginAds",skip:C,pause:B,play:T}}}if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-ads")){const e=document.createElement("style");e.id="artplayer-plugin-ads",e.textContent=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:noneLine truncated
|
||||
!function(e,n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"==typeof define&&define.amd?(e.artplayerPluginAds=n(),define(function(){return e.artplayerPluginAds})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginAds=n()}(this,function(){"use strict";var e=Object.defineProperty,n=Object.getOwnPropertySymbols,l=Object.prototype.hasOwnProperty,t=Object.prototype.propertyIsEnumerable,i=(n,l,t)=>l in n?e(n,l,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[l]=t;function r(e,r){return r(((e,r)=>{for(var o in r||(r={}))l.call(r,o)&&i(e,o,r[o]);if(n)for(var o of n(r))t.call(r,o)&&i(e,o,r[o]);return e})({html:"",video:"",url:"",playDuration:5,totalDuration:10,muted:!1,i18n:{close:"\u5173\u95ed\u5e7f\u544a",countdown:"%s\u79d2",detail:"\u67e5\u770b\u8be6\u60c5",canBeClosed:"%s\u79d2\u540e\u53ef\u5173\u95ed\u5e7f\u544a"}},e),{html:"?string",video:"?string",url:"?string",playDuration:"number",totalDuration:"number",muted:"?boolean",i18n:{close:"string",countdown:"string",detail:"string",canBeClosed:"string"}})}function o(e){console.warn("Artplayer Ads:",e)}function a(e){const n=[];let l=!1;function t(e,t){if(!l){n.push(t);try{e()}finally{l&&t()}}}return{on(n,i){const r=()=>{l||i()};t(()=>{e.on(n,r)},()=>{e.off(n,r)})},dom(e,n,i){const r=()=>{l||i()};t(()=>e.addEventListener(n,r),()=>e.removeEventListener(n,r))},dispose(){if(!l){l=!0;for(const l of n.splice(0).reverse())try{l()}catch(e){o(e)}}}}}const s=(e,n)=>n.replace("%s",String(e));function d(e,n,l,t){const i=a(e),r=a(e),d=e.template;let u,p,c="waiting",y=!1,f=!1,g=!1;const v=function(e,n,l){let t,i=0,r=!1,o=!1,a=!1;function s(){r&&o&&!a&&void 0===t&&(t=setTimeout(()=>{t=void 0,!a&&o&&(i+=1,n(i),a||(i>=e()?l():s()))},1e3))}function d(){o=!1,void 0!==t&&clearTimeout(t),t=void 0}return{start(){r||a||(r=!0,o=!0,s())},play(){r&&!a&&(o=!0,s())},pause:d,stop(){a=!0,d()}}}(()=>n.totalDuration,e=>null==p?void 0:p.render(e),w),m=()=>"active"===c&&!e.isDestroy,h=()=>"destroyed"===c||e.isDestroy;function b(e){try{null==e||e.pause()}catch(n){o(n)}}function x(e,n,l){try{Promise.resolve(e()).then(()=>{m()||null==l||l()},n).catch(o)}catch(t){n(t)}}function w(){if("ended"!==c&&"destroyed"!==c&&!e.isDestroy){if(c="ended",v.stop(),i.dispose(),y){if(x(()=>e.play(),e=>{"destroyed"!==c&&o(e)}),h())return;if(b(null==p?void 0:p.video),h())return;null==p||p.hide()}h()||e.emit("artplayerPluginAds:skip",n)}}function D(){const e=null==p?void 0:p.video;if(b(e),e)try{e.removeAttribute("src"),e.load()}catch(n){o(n)}try{null==u||u.remove()}catch(n){o(n)}finally{d.$ads===u&&delete d.$ads}}function k(){"destroyed"!==c&&(c="destroyed",v.stop(),i.dispose(),r.dispose(),D())}function O(){if("waiting"===c&&!e.isDestroy){c="active",y=!0;try{if(p=function(e,n,l,t,i){const{append:r,query:o,setStyle:a}=t,d=r(e,'<div class="artplayer-plugin-ads"></div>');i(d);const u=r(d,l.video?'<video class="artplayer-plugin-ads-video" loop playsInline></video>':`<div class="artplayer-plugin-ads-html">${l.html}</div>`),p=l.video?u:null,c=r(d,'<div class="artplayer-plugin-ads-loading"></div>');r(c,n.loading);const y=r(d,'<div class="artplayer-plugin-ads-timer">\n <div class="artplayer-plugin-ads-close"></div>\n <div class="artplayer-plugin-ads-countdown"></div>\n </div>'),f=o(".artplayer-plugin-ads-close",y),g=o(".artplayer-plugin-ads-countdown",y),v=r(d,`<div class="artplayer-plugin-ads-control">\n <div class="artplayer-plugin-ads-detail">${l.i18n.detail}</div>\n <div class="artplayer-plugin-ads-muted"></div>\n <div class="artplayer-plugin-ads-fullscreen"></div>\n </div>`),m=o(".artplayer-plugin-ads-detail",v),h=o(".artplayer-plugin-ads-muted",v),b=o(".artplayer-plugin-ads-fullscreen",v);let x=l.playDuration<=0;function w(){a(n.volume,"display",(null==p?void 0:p.muted)?"none":"inline-flex"),a(n.volumeClose,"display",(null==p?void 0:p.muted)?"inline-flex":"none")}function D(e){const n=l.playDuration-e;x=0===e?l.playDuration<=0:n<1||Number.isNaN(n),f.innerHTML=x?l.i18n.close:s(n,l.i18n.canBeClosed),g.innerHTML=s(l.totalDuration-e,l.i18n.countdown)}return l.playDuration>=l.totalDuration&&a(f,"display","none"),l.url||a(m,"display","none"),p?(r(h,n.volume),r(h,n.volumeClose),p.muted=Boolean(l.muted),w()):a(h,"display","none"),r(b,n.fullscreenOn),r(b,n.fullscreenOff),D(0),{root:d,video:p,render:D,ready(){a(y,"display","flex"),a(v,"display","flex"),a(c,"display","none")},hide(){a(d,"display","none")},fullscreen(e){a(n.fullscreenOn,"display",e?"none":"inline-flex"),a(n.fullscreenOff,"display",e?"inline-flex":"none")},bind(e,n){e.dom(f,"click",()=>{x&&n.skip()}),e.dom(u,"click",n.click),l.url&&e.dom(m,"click",n.click),p&&e.dom(h,"click",()=>{p.muted=!p.muted,w()}),e.dom(b,"click",n.fullscreen)}}}(d.$player,l,n,t,e=>{u=e,d.$ads=e}),!m())return void D();if(e.pause(),!m())return;const r=p,a=u.ownerDocument;r.fullscreen(e.fullscreen),r.bind(i,{skip:w,click(){n.url&&(a.defaultView||window).open(n.url),m()&&e.emit("artplayerPluginAds:click",n)},fullscreenLine truncated
|
||||
+382
-232
@@ -4,247 +4,397 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
const style = ".artplayer-plugin-ads {\n position: absolute;\n z-index: 150;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n overflow: hidden;\n font-size: 13px;\n line-height: 1;\n color: #fff;\n background-color: #000;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-html {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-video {\n width: 100%;\n height: 100%;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-timer {\n display: none;\n position: absolute;\n top: 10px;\n right: 10px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-timer > div {\n display: flex;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.5);\n border-radius: 15px;\n margin-left: 5px;\n padding: 5px 10px;\n cursor: pointer;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control {\n display: none;\n position: absolute;\n bottom: 10px;\n right: 10px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control > div {\n display: flex;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.5);\n border-radius: 15px;\n margin-left: 5px;\n padding: 5px 10px;\n cursor: pointer;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg {\n width: 20px;\n height: 20px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n}\n";
|
||||
function checkVersion(art) {
|
||||
const {
|
||||
version,
|
||||
utils: { errorHandle }
|
||||
} = art.constructor;
|
||||
const arr = version.split(".").map(Number);
|
||||
const major = arr[0];
|
||||
const minor = arr[1] / 100;
|
||||
errorHandle(
|
||||
major + minor >= 5,
|
||||
`Artplayer.js@${version} is not compatible the artplayerPluginAds@${artplayerPluginAds.version}. Please update it to version Artplayer.js@5.x.x`
|
||||
);
|
||||
function normalizeOptions(input, validate) {
|
||||
return validate({
|
||||
html: "",
|
||||
video: "",
|
||||
url: "",
|
||||
playDuration: 5,
|
||||
totalDuration: 10,
|
||||
muted: false,
|
||||
i18n: {
|
||||
close: "关闭广告",
|
||||
countdown: "%s秒",
|
||||
detail: "查看详情",
|
||||
canBeClosed: "%s秒后可关闭广告"
|
||||
},
|
||||
...input
|
||||
}, {
|
||||
html: "?string",
|
||||
video: "?string",
|
||||
url: "?string",
|
||||
playDuration: "number",
|
||||
totalDuration: "number",
|
||||
muted: "?boolean",
|
||||
i18n: { close: "string", countdown: "string", detail: "string", canBeClosed: "string" }
|
||||
});
|
||||
}
|
||||
function artplayerPluginAds(option) {
|
||||
return (art) => {
|
||||
checkVersion(art);
|
||||
const {
|
||||
template: { $player },
|
||||
icons: { volume, volumeClose, fullscreenOn, fullscreenOff, loading },
|
||||
constructor: {
|
||||
validator,
|
||||
utils: { query, append, setStyle }
|
||||
}
|
||||
} = art;
|
||||
option = validator(
|
||||
{
|
||||
html: "",
|
||||
video: "",
|
||||
url: "",
|
||||
playDuration: 5,
|
||||
totalDuration: 10,
|
||||
muted: false,
|
||||
i18n: {
|
||||
close: "关闭广告",
|
||||
countdown: "%s秒",
|
||||
detail: "查看详情",
|
||||
canBeClosed: "%s秒后可关闭广告"
|
||||
},
|
||||
...option
|
||||
},
|
||||
{
|
||||
html: "?string",
|
||||
video: "?string",
|
||||
url: "?string",
|
||||
playDuration: "number",
|
||||
totalDuration: "number",
|
||||
muted: "?boolean",
|
||||
i18n: {
|
||||
close: "string",
|
||||
countdown: "string",
|
||||
detail: "string",
|
||||
canBeClosed: "string"
|
||||
}
|
||||
}
|
||||
);
|
||||
let $ads = null;
|
||||
let $timer = null;
|
||||
let $close = null;
|
||||
let $countdown = null;
|
||||
let $control = null;
|
||||
let $loading = null;
|
||||
let time = 0;
|
||||
let timer = null;
|
||||
let isEnd = false;
|
||||
let isInit = false;
|
||||
let isCanClose = false;
|
||||
function getI18n(val, str) {
|
||||
return str.replace("%s", val);
|
||||
}
|
||||
function skip() {
|
||||
isEnd = true;
|
||||
art.play();
|
||||
if (option.video)
|
||||
$ads.pause();
|
||||
setStyle(art.template.$ads, "display", "none");
|
||||
art.emit("artplayerPluginAds:skip", option);
|
||||
}
|
||||
function play() {
|
||||
if (isEnd)
|
||||
function createCountdown(total, render, complete) {
|
||||
let elapsed = 0;
|
||||
let ready = false;
|
||||
let running = false;
|
||||
let closed = false;
|
||||
let timer;
|
||||
function schedule() {
|
||||
if (!ready || !running || closed || timer !== void 0)
|
||||
return;
|
||||
timer = setTimeout(() => {
|
||||
timer = void 0;
|
||||
if (closed || !running)
|
||||
return;
|
||||
timer = setTimeout(() => {
|
||||
time += 1;
|
||||
const playDuration = option.playDuration - time;
|
||||
if (playDuration >= 1) {
|
||||
$close.innerHTML = getI18n(playDuration, option.i18n.canBeClosed);
|
||||
} else {
|
||||
$close.innerHTML = option.i18n.close;
|
||||
if (!isCanClose) {
|
||||
isCanClose = true;
|
||||
}
|
||||
}
|
||||
$countdown.innerHTML = getI18n(option.totalDuration - time, option.i18n.countdown);
|
||||
if (time >= option.totalDuration) {
|
||||
skip();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
}, 1e3);
|
||||
}
|
||||
function pause() {
|
||||
if (isEnd)
|
||||
elapsed += 1;
|
||||
render(elapsed);
|
||||
if (closed)
|
||||
return;
|
||||
if (elapsed >= total())
|
||||
complete();
|
||||
else
|
||||
schedule();
|
||||
}, 1e3);
|
||||
}
|
||||
function pause() {
|
||||
running = false;
|
||||
if (timer !== void 0)
|
||||
clearTimeout(timer);
|
||||
}
|
||||
function show() {
|
||||
art.template.$ads = append($player, '<div class="artplayer-plugin-ads"></div>');
|
||||
$ads = append(
|
||||
art.template.$ads,
|
||||
option.video ? `<video class="artplayer-plugin-ads-video" src="${option.video}" loop playsInline></video>` : `<div class="artplayer-plugin-ads-html">${option.html}</div>`
|
||||
);
|
||||
$loading = append(art.template.$ads, '<div class="artplayer-plugin-ads-loading"></div>');
|
||||
append($loading, loading);
|
||||
$timer = append(
|
||||
art.template.$ads,
|
||||
`<div class="artplayer-plugin-ads-timer">
|
||||
<div class="artplayer-plugin-ads-close">${option.playDuration <= 0 ? option.i18n.close : getI18n(option.playDuration, option.i18n.canBeClosed)}</div>
|
||||
<div class="artplayer-plugin-ads-countdown">${getI18n(
|
||||
option.totalDuration,
|
||||
option.i18n.countdown
|
||||
)}</div>
|
||||
</div>`
|
||||
);
|
||||
$close = query(".artplayer-plugin-ads-close", $timer);
|
||||
$countdown = query(".artplayer-plugin-ads-countdown", $timer);
|
||||
if (option.playDuration >= option.totalDuration) {
|
||||
setStyle($close, "display", "none");
|
||||
}
|
||||
art.proxy($close, "click", () => {
|
||||
if (isCanClose) {
|
||||
skip();
|
||||
}
|
||||
});
|
||||
$control = append(
|
||||
art.template.$ads,
|
||||
`<div class="artplayer-plugin-ads-control">
|
||||
<div class="artplayer-plugin-ads-detail">${option.i18n.detail}</div>
|
||||
<div class="artplayer-plugin-ads-muted"></div>
|
||||
<div class="artplayer-plugin-ads-fullscreen"></div>
|
||||
</div>`
|
||||
);
|
||||
const $detail = query(".artplayer-plugin-ads-detail", $control);
|
||||
const $muted = query(".artplayer-plugin-ads-muted", $control);
|
||||
const $fullscreen = query(".artplayer-plugin-ads-fullscreen", $control);
|
||||
if (option.url) {
|
||||
art.proxy($detail, "click", () => {
|
||||
window.open(option.url);
|
||||
art.emit("artplayerPluginAds:click", option);
|
||||
});
|
||||
} else {
|
||||
setStyle($detail, "display", "none");
|
||||
}
|
||||
if (option.video) {
|
||||
const $volume = append($muted, volume);
|
||||
const $volumeClose = append($muted, volumeClose);
|
||||
setStyle($volumeClose, "display", "none");
|
||||
if (option.muted) {
|
||||
$ads.muted = true;
|
||||
setStyle($volume, "display", "none");
|
||||
setStyle($volumeClose, "display", "inline-flex");
|
||||
}
|
||||
art.proxy($muted, "click", () => {
|
||||
$ads.muted = !$ads.muted;
|
||||
if ($ads.muted) {
|
||||
setStyle($volume, "display", "none");
|
||||
setStyle($volumeClose, "display", "inline-flex");
|
||||
} else {
|
||||
setStyle($volume, "display", "inline-flex");
|
||||
setStyle($volumeClose, "display", "none");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setStyle($muted, "display", "none");
|
||||
}
|
||||
const $fullscreenOn = append($fullscreen, fullscreenOn);
|
||||
const $fullscreenOff = append($fullscreen, fullscreenOff);
|
||||
setStyle($fullscreenOff, "display", "none");
|
||||
art.proxy($fullscreen, "click", () => {
|
||||
art.fullscreen = !art.fullscreen;
|
||||
if (art.fullscreen) {
|
||||
setStyle($fullscreenOn, "display", "inline-flex");
|
||||
setStyle($fullscreenOff, "display", "none");
|
||||
} else {
|
||||
setStyle($fullscreenOn, "display", "none");
|
||||
setStyle($fullscreenOff, "display", "inline-flex");
|
||||
}
|
||||
});
|
||||
art.proxy($ads, "click", () => {
|
||||
if (option.url)
|
||||
window.open(option.url);
|
||||
art.emit("artplayerPluginAds:click", option);
|
||||
});
|
||||
}
|
||||
function init() {
|
||||
if (isInit)
|
||||
timer = void 0;
|
||||
}
|
||||
return {
|
||||
start() {
|
||||
if (ready || closed)
|
||||
return;
|
||||
isInit = true;
|
||||
show();
|
||||
art.pause();
|
||||
if (option.video) {
|
||||
art.proxy($ads, "error", skip);
|
||||
art.proxy($ads, "loadedmetadata", () => {
|
||||
play();
|
||||
$ads.play();
|
||||
setStyle($timer, "display", "flex");
|
||||
setStyle($control, "display", "flex");
|
||||
setStyle($loading, "display", "none");
|
||||
});
|
||||
} else {
|
||||
play();
|
||||
setStyle($timer, "display", "flex");
|
||||
setStyle($control, "display", "flex");
|
||||
setStyle($loading, "display", "none");
|
||||
}
|
||||
art.on("document:visibilitychange", () => {
|
||||
if (document.hidden) {
|
||||
pause();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
});
|
||||
ready = true;
|
||||
running = true;
|
||||
schedule();
|
||||
},
|
||||
play() {
|
||||
if (!ready || closed)
|
||||
return;
|
||||
running = true;
|
||||
schedule();
|
||||
},
|
||||
pause,
|
||||
stop() {
|
||||
closed = true;
|
||||
pause();
|
||||
}
|
||||
art.on("ready", () => {
|
||||
art.once("play", init);
|
||||
art.once("video:playing", init);
|
||||
});
|
||||
return {
|
||||
name: "artplayerPluginAds",
|
||||
skip,
|
||||
pause,
|
||||
play
|
||||
};
|
||||
};
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
if (!document.getElementById("artplayer-plugin-ads")) {
|
||||
const $style = document.createElement("style");
|
||||
$style.id = "artplayer-plugin-ads";
|
||||
$style.textContent = style;
|
||||
document.head.appendChild($style);
|
||||
function report(error) {
|
||||
console.warn("Artplayer Ads:", error);
|
||||
}
|
||||
function createResources(host) {
|
||||
const cleaners = [];
|
||||
let closed = false;
|
||||
function install(add, remove) {
|
||||
if (closed)
|
||||
return;
|
||||
cleaners.push(remove);
|
||||
try {
|
||||
add();
|
||||
} finally {
|
||||
if (closed)
|
||||
remove();
|
||||
}
|
||||
}
|
||||
return {
|
||||
on(name, callback) {
|
||||
const listener = () => {
|
||||
if (!closed)
|
||||
callback();
|
||||
};
|
||||
install(() => {
|
||||
host.on(name, listener);
|
||||
}, () => {
|
||||
host.off(name, listener);
|
||||
});
|
||||
},
|
||||
dom(target, name, callback) {
|
||||
const listener = () => {
|
||||
if (!closed)
|
||||
callback();
|
||||
};
|
||||
install(() => target.addEventListener(name, listener), () => target.removeEventListener(name, listener));
|
||||
},
|
||||
dispose() {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
for (const clean of cleaners.splice(0).reverse()) {
|
||||
try {
|
||||
clean();
|
||||
} catch (error) {
|
||||
report(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
const translate = (value, text) => text.replace("%s", String(value));
|
||||
function createView(parent, icons, option, utils, own) {
|
||||
const { append, query, setStyle } = utils;
|
||||
const root = append(parent, '<div class="artplayer-plugin-ads"></div>');
|
||||
own(root);
|
||||
const content = append(root, option.video ? '<video class="artplayer-plugin-ads-video" loop playsInline></video>' : `<div class="artplayer-plugin-ads-html">${option.html}</div>`);
|
||||
const video = option.video ? content : null;
|
||||
const loading = append(root, '<div class="artplayer-plugin-ads-loading"></div>');
|
||||
append(loading, icons.loading);
|
||||
const timer = append(root, `<div class="artplayer-plugin-ads-timer">
|
||||
<div class="artplayer-plugin-ads-close"></div>
|
||||
<div class="artplayer-plugin-ads-countdown"></div>
|
||||
</div>`);
|
||||
const close = query(".artplayer-plugin-ads-close", timer);
|
||||
const countdown = query(".artplayer-plugin-ads-countdown", timer);
|
||||
const control = append(root, `<div class="artplayer-plugin-ads-control">
|
||||
<div class="artplayer-plugin-ads-detail">${option.i18n.detail}</div>
|
||||
<div class="artplayer-plugin-ads-muted"></div>
|
||||
<div class="artplayer-plugin-ads-fullscreen"></div>
|
||||
</div>`);
|
||||
const detail = query(".artplayer-plugin-ads-detail", control);
|
||||
const muted = query(".artplayer-plugin-ads-muted", control);
|
||||
const fullscreen = query(".artplayer-plugin-ads-fullscreen", control);
|
||||
let canClose = option.playDuration <= 0;
|
||||
if (option.playDuration >= option.totalDuration)
|
||||
setStyle(close, "display", "none");
|
||||
if (!option.url)
|
||||
setStyle(detail, "display", "none");
|
||||
if (video) {
|
||||
append(muted, icons.volume);
|
||||
append(muted, icons.volumeClose);
|
||||
video.muted = Boolean(option.muted);
|
||||
syncMuted();
|
||||
} else {
|
||||
setStyle(muted, "display", "none");
|
||||
}
|
||||
append(fullscreen, icons.fullscreenOn);
|
||||
append(fullscreen, icons.fullscreenOff);
|
||||
function syncMuted() {
|
||||
setStyle(icons.volume, "display", video?.muted ? "none" : "inline-flex");
|
||||
setStyle(icons.volumeClose, "display", video?.muted ? "inline-flex" : "none");
|
||||
}
|
||||
function render(elapsed) {
|
||||
const remaining = option.playDuration - elapsed;
|
||||
canClose = elapsed === 0 ? option.playDuration <= 0 : remaining < 1 || Number.isNaN(remaining);
|
||||
close.innerHTML = canClose ? option.i18n.close : translate(remaining, option.i18n.canBeClosed);
|
||||
countdown.innerHTML = translate(option.totalDuration - elapsed, option.i18n.countdown);
|
||||
}
|
||||
render(0);
|
||||
return {
|
||||
root,
|
||||
video,
|
||||
render,
|
||||
ready() {
|
||||
setStyle(timer, "display", "flex");
|
||||
setStyle(control, "display", "flex");
|
||||
setStyle(loading, "display", "none");
|
||||
},
|
||||
hide() {
|
||||
setStyle(root, "display", "none");
|
||||
},
|
||||
fullscreen(active) {
|
||||
setStyle(icons.fullscreenOn, "display", active ? "none" : "inline-flex");
|
||||
setStyle(icons.fullscreenOff, "display", active ? "inline-flex" : "none");
|
||||
},
|
||||
bind(events, callbacks) {
|
||||
events.dom(close, "click", () => {
|
||||
if (canClose)
|
||||
callbacks.skip();
|
||||
});
|
||||
events.dom(content, "click", callbacks.click);
|
||||
if (option.url)
|
||||
events.dom(detail, "click", callbacks.click);
|
||||
if (video) {
|
||||
events.dom(muted, "click", () => {
|
||||
video.muted = !video.muted;
|
||||
syncMuted();
|
||||
});
|
||||
}
|
||||
events.dom(fullscreen, "click", callbacks.fullscreen);
|
||||
}
|
||||
};
|
||||
}
|
||||
function createSession(art, option, icons, utils) {
|
||||
const events = createResources(art);
|
||||
const lifetime = createResources(art);
|
||||
const template = art.template;
|
||||
let state = "waiting";
|
||||
let initialized = false;
|
||||
let armed = false;
|
||||
let mediaReady = false;
|
||||
let root;
|
||||
let view;
|
||||
const clock = createCountdown(() => option.totalDuration, (time) => view?.render(time), skip);
|
||||
const active = () => state === "active" && !art.isDestroy;
|
||||
const destroyed = () => state === "destroyed" || art.isDestroy;
|
||||
function pauseVideo(video) {
|
||||
try {
|
||||
video?.pause();
|
||||
} catch (error) {
|
||||
report(error);
|
||||
}
|
||||
}
|
||||
function requestPlay(start, rejected, late) {
|
||||
try {
|
||||
Promise.resolve(start()).then(() => {
|
||||
if (!active())
|
||||
late?.();
|
||||
}, rejected).catch(report);
|
||||
} catch (error) {
|
||||
rejected(error);
|
||||
}
|
||||
}
|
||||
function skip() {
|
||||
if (state === "ended" || state === "destroyed" || art.isDestroy)
|
||||
return;
|
||||
state = "ended";
|
||||
clock.stop();
|
||||
events.dispose();
|
||||
if (initialized) {
|
||||
requestPlay(() => art.play(), (error) => {
|
||||
if (state !== "destroyed")
|
||||
report(error);
|
||||
});
|
||||
if (destroyed())
|
||||
return;
|
||||
pauseVideo(view?.video);
|
||||
if (destroyed())
|
||||
return;
|
||||
view?.hide();
|
||||
}
|
||||
if (!destroyed())
|
||||
art.emit("artplayerPluginAds:skip", option);
|
||||
}
|
||||
function disposeView() {
|
||||
const video = view?.video;
|
||||
pauseVideo(video);
|
||||
if (video) {
|
||||
try {
|
||||
video.removeAttribute("src");
|
||||
video.load();
|
||||
} catch (error) {
|
||||
report(error);
|
||||
}
|
||||
}
|
||||
try {
|
||||
root?.remove();
|
||||
} catch (error) {
|
||||
report(error);
|
||||
} finally {
|
||||
if (template.$ads === root)
|
||||
delete template.$ads;
|
||||
}
|
||||
}
|
||||
function destroy() {
|
||||
if (state === "destroyed")
|
||||
return;
|
||||
state = "destroyed";
|
||||
clock.stop();
|
||||
events.dispose();
|
||||
lifetime.dispose();
|
||||
disposeView();
|
||||
}
|
||||
function init() {
|
||||
if (state !== "waiting" || art.isDestroy)
|
||||
return;
|
||||
state = "active";
|
||||
initialized = true;
|
||||
try {
|
||||
view = createView(template.$player, icons, option, utils, (node) => {
|
||||
root = node;
|
||||
template.$ads = node;
|
||||
});
|
||||
if (!active()) {
|
||||
disposeView();
|
||||
return;
|
||||
}
|
||||
art.pause();
|
||||
if (!active())
|
||||
return;
|
||||
const current = view;
|
||||
const document2 = root.ownerDocument;
|
||||
current.fullscreen(art.fullscreen);
|
||||
current.bind(events, {
|
||||
skip,
|
||||
click() {
|
||||
if (option.url)
|
||||
(document2.defaultView || window).open(option.url);
|
||||
if (active())
|
||||
art.emit("artplayerPluginAds:click", option);
|
||||
},
|
||||
fullscreen() {
|
||||
art.fullscreen = !art.fullscreen;
|
||||
if (active())
|
||||
current.fullscreen(art.fullscreen);
|
||||
}
|
||||
});
|
||||
events.on("fullscreen", () => current.fullscreen(art.fullscreen));
|
||||
const visibility = () => document2.hidden ? clock.pause() : clock.play();
|
||||
events.dom(document2, "visibilitychange", visibility);
|
||||
events.on("document:visibilitychange", visibility);
|
||||
const ready = () => {
|
||||
if (!active() || mediaReady)
|
||||
return;
|
||||
mediaReady = true;
|
||||
clock.start();
|
||||
if (document2.hidden)
|
||||
clock.pause();
|
||||
if (current.video) {
|
||||
const video = current.video;
|
||||
requestPlay(() => video.play(), (error) => {
|
||||
if (active()) {
|
||||
report(error);
|
||||
skip();
|
||||
}
|
||||
}, () => pauseVideo(video));
|
||||
}
|
||||
if (active())
|
||||
current.ready();
|
||||
};
|
||||
if (current.video) {
|
||||
events.dom(current.video, "error", skip);
|
||||
events.dom(current.video, "loadedmetadata", ready);
|
||||
current.video.src = option.video;
|
||||
} else {
|
||||
ready();
|
||||
}
|
||||
} catch (error) {
|
||||
destroy();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
try {
|
||||
lifetime.on("destroy", destroy);
|
||||
events.on("ready", () => {
|
||||
if (armed || state !== "waiting")
|
||||
return;
|
||||
armed = true;
|
||||
events.on("play", init);
|
||||
events.on("video:playing", init);
|
||||
});
|
||||
if (art.isDestroy)
|
||||
destroy();
|
||||
} catch (error) {
|
||||
destroy();
|
||||
throw error;
|
||||
}
|
||||
return { name: "artplayerPluginAds", skip, pause: clock.pause, play: clock.play };
|
||||
}
|
||||
const style = ".artplayer-plugin-ads {\n position: absolute;\n z-index: 150;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n overflow: hidden;\n font-size: 13px;\n line-height: 1;\n color: #fff;\n background-color: #000;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-html {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-video {\n width: 100%;\n height: 100%;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-timer {\n display: none;\n position: absolute;\n top: 10px;\n right: 10px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-timer > div {\n display: flex;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.5);\n border-radius: 15px;\n margin-left: 5px;\n padding: 5px 10px;\n cursor: pointer;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control {\n display: none;\n position: absolute;\n bottom: 10px;\n right: 10px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control > div {\n display: flex;\n align-items: center;\n background-color: rgba(0, 0, 0, 0.5);\n border-radius: 15px;\n margin-left: 5px;\n padding: 5px 10px;\n cursor: pointer;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg {\n width: 20px;\n height: 20px;\n}\n.artplayer-plugin-ads .artplayer-plugin-ads-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n}\n";
|
||||
function artplayerPluginAds(input) {
|
||||
return (art) => {
|
||||
const constructor = art.constructor;
|
||||
if (typeof constructor.validator !== "function" || !["append", "query", "setStyle"].every((name) => typeof Reflect.get(constructor.utils || {}, name) === "function")) {
|
||||
throw new Error("Artplayer Ads requires the core validator and DOM utilities");
|
||||
}
|
||||
const option = normalizeOptions(input, constructor.validator);
|
||||
const { volume, volumeClose, fullscreenOn, fullscreenOff, loading } = art.icons;
|
||||
return createSession(art, option, { volume, volumeClose, fullscreenOn, fullscreenOff, loading }, constructor.utils);
|
||||
};
|
||||
}
|
||||
if (typeof document !== "undefined" && !document.getElementById("artplayer-plugin-ads")) {
|
||||
const element = document.createElement("style");
|
||||
element.id = "artplayer-plugin-ads";
|
||||
element.textContent = style;
|
||||
document.head.appendChild(element);
|
||||
}
|
||||
export {
|
||||
artplayerPluginAds as default
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { Cleanup } from './types'
|
||||
|
||||
export function createCountdown(total: () => number, render: (seconds: number) => void, complete: Cleanup) {
|
||||
let elapsed = 0
|
||||
let ready = false
|
||||
let running = false
|
||||
let closed = false
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
function schedule(): void {
|
||||
if (!ready || !running || closed || timer !== undefined)
|
||||
return
|
||||
timer = setTimeout(() => {
|
||||
timer = undefined
|
||||
if (closed || !running)
|
||||
return
|
||||
elapsed += 1
|
||||
render(elapsed)
|
||||
if (closed)
|
||||
return
|
||||
if (elapsed >= total())
|
||||
complete()
|
||||
else
|
||||
schedule()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function pause(): void {
|
||||
running = false
|
||||
if (timer !== undefined)
|
||||
clearTimeout(timer)
|
||||
timer = undefined
|
||||
}
|
||||
|
||||
return {
|
||||
start() {
|
||||
if (ready || closed)
|
||||
return
|
||||
ready = true
|
||||
running = true
|
||||
schedule()
|
||||
},
|
||||
play() {
|
||||
if (!ready || closed)
|
||||
return
|
||||
running = true
|
||||
schedule()
|
||||
},
|
||||
pause,
|
||||
stop() {
|
||||
closed = true
|
||||
pause()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
import style from './style.less?inline'
|
||||
|
||||
function checkVersion(art) {
|
||||
const {
|
||||
version,
|
||||
utils: { errorHandle },
|
||||
} = art.constructor
|
||||
const arr = version.split('.').map(Number)
|
||||
const major = arr[0]
|
||||
const minor = arr[1] / 100
|
||||
errorHandle(
|
||||
major + minor >= 5,
|
||||
`Artplayer.js@${version} is not compatible the artplayerPluginAds@${artplayerPluginAds.version}. Please update it to version Artplayer.js@5.x.x`,
|
||||
)
|
||||
}
|
||||
|
||||
export default function artplayerPluginAds(option) {
|
||||
return (art) => {
|
||||
checkVersion(art)
|
||||
|
||||
const {
|
||||
template: { $player },
|
||||
icons: { volume, volumeClose, fullscreenOn, fullscreenOff, loading },
|
||||
constructor: {
|
||||
validator,
|
||||
utils: { query, append, setStyle },
|
||||
},
|
||||
} = art
|
||||
|
||||
option = validator(
|
||||
{
|
||||
html: '',
|
||||
video: '',
|
||||
url: '',
|
||||
playDuration: 5,
|
||||
totalDuration: 10,
|
||||
muted: false,
|
||||
i18n: {
|
||||
close: '关闭广告',
|
||||
countdown: '%s秒',
|
||||
detail: '查看详情',
|
||||
canBeClosed: '%s秒后可关闭广告',
|
||||
},
|
||||
...option,
|
||||
},
|
||||
{
|
||||
html: '?string',
|
||||
video: '?string',
|
||||
url: '?string',
|
||||
playDuration: 'number',
|
||||
totalDuration: 'number',
|
||||
muted: '?boolean',
|
||||
i18n: {
|
||||
close: 'string',
|
||||
countdown: 'string',
|
||||
detail: 'string',
|
||||
canBeClosed: 'string',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
let $ads = null
|
||||
let $timer = null
|
||||
let $close = null
|
||||
let $countdown = null
|
||||
let $control = null
|
||||
let $loading = null
|
||||
|
||||
let time = 0
|
||||
let timer = null
|
||||
let isEnd = false
|
||||
let isInit = false
|
||||
let isCanClose = false
|
||||
|
||||
function getI18n(val, str) {
|
||||
return str.replace('%s', val)
|
||||
}
|
||||
|
||||
function skip() {
|
||||
isEnd = true
|
||||
art.play()
|
||||
if (option.video)
|
||||
$ads.pause()
|
||||
setStyle(art.template.$ads, 'display', 'none')
|
||||
art.emit('artplayerPluginAds:skip', option)
|
||||
}
|
||||
|
||||
function play() {
|
||||
if (isEnd)
|
||||
return
|
||||
|
||||
timer = setTimeout(() => {
|
||||
time += 1
|
||||
|
||||
const playDuration = option.playDuration - time
|
||||
if (playDuration >= 1) {
|
||||
$close.innerHTML = getI18n(playDuration, option.i18n.canBeClosed)
|
||||
}
|
||||
else {
|
||||
$close.innerHTML = option.i18n.close
|
||||
if (!isCanClose) {
|
||||
isCanClose = true
|
||||
}
|
||||
}
|
||||
|
||||
$countdown.innerHTML = getI18n(option.totalDuration - time, option.i18n.countdown)
|
||||
|
||||
if (time >= option.totalDuration) {
|
||||
skip()
|
||||
}
|
||||
else {
|
||||
play()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function pause() {
|
||||
if (isEnd)
|
||||
return
|
||||
clearTimeout(timer)
|
||||
}
|
||||
|
||||
function show() {
|
||||
art.template.$ads = append($player, '<div class="artplayer-plugin-ads"></div>')
|
||||
|
||||
$ads = append(
|
||||
art.template.$ads,
|
||||
option.video
|
||||
? `<video class="artplayer-plugin-ads-video" src="${option.video}" loop playsInline></video>`
|
||||
: `<div class="artplayer-plugin-ads-html">${option.html}</div>`,
|
||||
)
|
||||
|
||||
$loading = append(art.template.$ads, '<div class="artplayer-plugin-ads-loading"></div>')
|
||||
append($loading, loading)
|
||||
|
||||
$timer = append(
|
||||
art.template.$ads,
|
||||
`<div class="artplayer-plugin-ads-timer">
|
||||
<div class="artplayer-plugin-ads-close">${
|
||||
option.playDuration <= 0
|
||||
? option.i18n.close
|
||||
: getI18n(option.playDuration, option.i18n.canBeClosed)
|
||||
}</div>
|
||||
<div class="artplayer-plugin-ads-countdown">${getI18n(
|
||||
option.totalDuration,
|
||||
option.i18n.countdown,
|
||||
)}</div>
|
||||
</div>`,
|
||||
)
|
||||
|
||||
$close = query('.artplayer-plugin-ads-close', $timer)
|
||||
$countdown = query('.artplayer-plugin-ads-countdown', $timer)
|
||||
|
||||
if (option.playDuration >= option.totalDuration) {
|
||||
setStyle($close, 'display', 'none')
|
||||
}
|
||||
|
||||
art.proxy($close, 'click', () => {
|
||||
if (isCanClose) {
|
||||
skip()
|
||||
}
|
||||
})
|
||||
|
||||
$control = append(
|
||||
art.template.$ads,
|
||||
`<div class="artplayer-plugin-ads-control">
|
||||
<div class="artplayer-plugin-ads-detail">${option.i18n.detail}</div>
|
||||
<div class="artplayer-plugin-ads-muted"></div>
|
||||
<div class="artplayer-plugin-ads-fullscreen"></div>
|
||||
</div>`,
|
||||
)
|
||||
|
||||
const $detail = query('.artplayer-plugin-ads-detail', $control)
|
||||
const $muted = query('.artplayer-plugin-ads-muted', $control)
|
||||
const $fullscreen = query('.artplayer-plugin-ads-fullscreen', $control)
|
||||
|
||||
if (option.url) {
|
||||
art.proxy($detail, 'click', () => {
|
||||
window.open(option.url)
|
||||
art.emit('artplayerPluginAds:click', option)
|
||||
})
|
||||
}
|
||||
else {
|
||||
setStyle($detail, 'display', 'none')
|
||||
}
|
||||
|
||||
if (option.video) {
|
||||
const $volume = append($muted, volume)
|
||||
const $volumeClose = append($muted, volumeClose)
|
||||
setStyle($volumeClose, 'display', 'none')
|
||||
|
||||
// If the ad was set to muted initially, match the icon
|
||||
if (option.muted) {
|
||||
$ads.muted = true
|
||||
setStyle($volume, 'display', 'none')
|
||||
setStyle($volumeClose, 'display', 'inline-flex')
|
||||
}
|
||||
|
||||
art.proxy($muted, 'click', () => {
|
||||
$ads.muted = !$ads.muted
|
||||
if ($ads.muted) {
|
||||
setStyle($volume, 'display', 'none')
|
||||
setStyle($volumeClose, 'display', 'inline-flex')
|
||||
}
|
||||
else {
|
||||
setStyle($volume, 'display', 'inline-flex')
|
||||
setStyle($volumeClose, 'display', 'none')
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
setStyle($muted, 'display', 'none')
|
||||
}
|
||||
|
||||
const $fullscreenOn = append($fullscreen, fullscreenOn)
|
||||
const $fullscreenOff = append($fullscreen, fullscreenOff)
|
||||
setStyle($fullscreenOff, 'display', 'none')
|
||||
|
||||
art.proxy($fullscreen, 'click', () => {
|
||||
art.fullscreen = !art.fullscreen
|
||||
if (art.fullscreen) {
|
||||
setStyle($fullscreenOn, 'display', 'inline-flex')
|
||||
setStyle($fullscreenOff, 'display', 'none')
|
||||
}
|
||||
else {
|
||||
setStyle($fullscreenOn, 'display', 'none')
|
||||
setStyle($fullscreenOff, 'display', 'inline-flex')
|
||||
}
|
||||
})
|
||||
|
||||
art.proxy($ads, 'click', () => {
|
||||
if (option.url)
|
||||
window.open(option.url)
|
||||
art.emit('artplayerPluginAds:click', option)
|
||||
})
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (isInit)
|
||||
return
|
||||
isInit = true
|
||||
|
||||
show()
|
||||
art.pause()
|
||||
|
||||
if (option.video) {
|
||||
art.proxy($ads, 'error', skip)
|
||||
art.proxy($ads, 'loadedmetadata', () => {
|
||||
play()
|
||||
$ads.play()
|
||||
setStyle($timer, 'display', 'flex')
|
||||
setStyle($control, 'display', 'flex')
|
||||
setStyle($loading, 'display', 'none')
|
||||
})
|
||||
}
|
||||
else {
|
||||
play()
|
||||
setStyle($timer, 'display', 'flex')
|
||||
setStyle($control, 'display', 'flex')
|
||||
setStyle($loading, 'display', 'none')
|
||||
}
|
||||
|
||||
art.on('document:visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
pause()
|
||||
}
|
||||
else {
|
||||
play()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
art.on('ready', () => {
|
||||
art.once('play', init)
|
||||
art.once('video:playing', init)
|
||||
})
|
||||
|
||||
return {
|
||||
name: 'artplayerPluginAds',
|
||||
skip,
|
||||
pause,
|
||||
play,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
if (!document.getElementById('artplayer-plugin-ads')) {
|
||||
const $style = document.createElement('style')
|
||||
$style.id = 'artplayer-plugin-ads'
|
||||
$style.textContent = style
|
||||
document.head.appendChild($style)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Input, Result } from './types'
|
||||
import { normalizeOptions } from './options'
|
||||
import { createSession } from './session'
|
||||
import style from './style.less?inline'
|
||||
|
||||
export default function artplayerPluginAds(input?: Input) {
|
||||
return (art: Artplayer): Result => {
|
||||
// Legacy core declarations type instance.constructor as Function. Check the
|
||||
// actual capabilities before using its existing static validator/utilities.
|
||||
const constructor = art.constructor as typeof Artplayer
|
||||
if (typeof constructor.validator !== 'function'
|
||||
|| !['append', 'query', 'setStyle'].every(name => typeof Reflect.get(constructor.utils || {}, name) === 'function')) { throw new Error('Artplayer Ads requires the core validator and DOM utilities') }
|
||||
const option = normalizeOptions(input, constructor.validator)
|
||||
const { volume, volumeClose, fullscreenOn, fullscreenOff, loading } = art.icons
|
||||
return createSession(art, option, { volume, volumeClose, fullscreenOn, fullscreenOff, loading }, constructor.utils)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined' && !document.getElementById('artplayer-plugin-ads')) {
|
||||
const element = document.createElement('style')
|
||||
element.id = 'artplayer-plugin-ads'
|
||||
element.textContent = style
|
||||
document.head.appendChild(element)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Input, Options } from './types'
|
||||
|
||||
export function normalizeOptions(input: Input | undefined, validate: typeof Artplayer.validator): Options {
|
||||
return validate({
|
||||
html: '',
|
||||
video: '',
|
||||
url: '',
|
||||
playDuration: 5,
|
||||
totalDuration: 10,
|
||||
muted: false,
|
||||
i18n: {
|
||||
close: '关闭广告',
|
||||
countdown: '%s秒',
|
||||
detail: '查看详情',
|
||||
canBeClosed: '%s秒后可关闭广告',
|
||||
},
|
||||
...input,
|
||||
}, {
|
||||
html: '?string',
|
||||
video: '?string',
|
||||
url: '?string',
|
||||
playDuration: 'number',
|
||||
totalDuration: 'number',
|
||||
muted: '?boolean',
|
||||
i18n: { close: 'string', countdown: 'string', detail: 'string', canBeClosed: 'string' },
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { Cleanup, Host } from './types'
|
||||
|
||||
export function report(error: unknown): void {
|
||||
console.warn('Artplayer Ads:', error)
|
||||
}
|
||||
|
||||
export function createResources(host: Pick<Host, 'on' | 'off'>) {
|
||||
const cleaners: Cleanup[] = []
|
||||
let closed = false
|
||||
|
||||
function install(add: Cleanup, remove: Cleanup): void {
|
||||
if (closed)
|
||||
return
|
||||
cleaners.push(remove)
|
||||
try {
|
||||
add()
|
||||
}
|
||||
finally {
|
||||
// A custom host/target can destroy the owner while registration is executing.
|
||||
if (closed)
|
||||
remove()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
on(name: string, callback: Cleanup) {
|
||||
const listener = () => {
|
||||
if (!closed)
|
||||
callback()
|
||||
}
|
||||
install(() => {
|
||||
host.on(name, listener)
|
||||
}, () => {
|
||||
host.off(name, listener)
|
||||
})
|
||||
},
|
||||
dom(target: EventTarget, name: string, callback: Cleanup) {
|
||||
const listener = () => {
|
||||
if (!closed)
|
||||
callback()
|
||||
}
|
||||
install(() => target.addEventListener(name, listener), () => target.removeEventListener(name, listener))
|
||||
},
|
||||
dispose() {
|
||||
if (closed)
|
||||
return
|
||||
closed = true
|
||||
for (const clean of cleaners.splice(0).reverse()) {
|
||||
try {
|
||||
clean()
|
||||
}
|
||||
catch (error) { report(error) }
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export type Resources = ReturnType<typeof createResources>
|
||||
@@ -0,0 +1,182 @@
|
||||
import type { Host, Icons, Options, Result, Utilities } from './types'
|
||||
import type { View } from './view'
|
||||
import { createCountdown } from './countdown'
|
||||
import { createResources, report } from './resources'
|
||||
import { createView } from './view'
|
||||
|
||||
export function createSession(art: Host, option: Options, icons: Icons, utils: Utilities): Result {
|
||||
const events = createResources(art)
|
||||
const lifetime = createResources(art)
|
||||
const template = art.template as Host['template'] & { $ads?: HTMLElement }
|
||||
let state: 'waiting' | 'active' | 'ended' | 'destroyed' = 'waiting'
|
||||
let initialized = false
|
||||
let armed = false
|
||||
let mediaReady = false
|
||||
let root: HTMLElement | undefined
|
||||
let view: View | undefined
|
||||
const clock = createCountdown(() => option.totalDuration, time => view?.render(time), skip)
|
||||
const active = () => state === 'active' && !art.isDestroy
|
||||
const destroyed = () => state === 'destroyed' || art.isDestroy
|
||||
|
||||
function pauseVideo(video: HTMLVideoElement | null | undefined): void {
|
||||
try {
|
||||
video?.pause()
|
||||
}
|
||||
catch (error) { report(error) }
|
||||
}
|
||||
|
||||
function requestPlay(start: () => unknown, rejected: (error: unknown) => void, late?: () => void): void {
|
||||
try {
|
||||
Promise.resolve(start()).then(() => {
|
||||
if (!active())
|
||||
late?.()
|
||||
}, rejected).catch(report)
|
||||
}
|
||||
catch (error) { rejected(error) }
|
||||
}
|
||||
|
||||
function skip(): void {
|
||||
if (state === 'ended' || state === 'destroyed' || art.isDestroy)
|
||||
return
|
||||
state = 'ended'
|
||||
clock.stop()
|
||||
events.dispose()
|
||||
if (initialized) {
|
||||
requestPlay(() => art.play(), (error) => {
|
||||
if (state !== 'destroyed')
|
||||
report(error)
|
||||
})
|
||||
if (destroyed())
|
||||
return
|
||||
pauseVideo(view?.video)
|
||||
if (destroyed())
|
||||
return
|
||||
view?.hide()
|
||||
}
|
||||
if (!destroyed())
|
||||
art.emit('artplayerPluginAds:skip', option)
|
||||
}
|
||||
|
||||
function disposeView(): void {
|
||||
const video = view?.video
|
||||
pauseVideo(video)
|
||||
if (video) {
|
||||
try {
|
||||
video.removeAttribute('src')
|
||||
video.load()
|
||||
}
|
||||
catch (error) { report(error) }
|
||||
}
|
||||
try {
|
||||
root?.remove()
|
||||
}
|
||||
catch (error) { report(error) }
|
||||
finally {
|
||||
if (template.$ads === root)
|
||||
delete template.$ads
|
||||
}
|
||||
}
|
||||
|
||||
function destroy(): void {
|
||||
if (state === 'destroyed')
|
||||
return
|
||||
state = 'destroyed'
|
||||
clock.stop()
|
||||
events.dispose()
|
||||
lifetime.dispose()
|
||||
disposeView()
|
||||
}
|
||||
|
||||
function init(): void {
|
||||
if (state !== 'waiting' || art.isDestroy)
|
||||
return
|
||||
state = 'active'
|
||||
initialized = true
|
||||
try {
|
||||
view = createView(template.$player, icons, option, utils, (node) => {
|
||||
root = node
|
||||
template.$ads = node
|
||||
})
|
||||
if (!active()) {
|
||||
disposeView()
|
||||
return
|
||||
}
|
||||
art.pause()
|
||||
if (!active())
|
||||
return
|
||||
const current = view
|
||||
const document = root!.ownerDocument
|
||||
current.fullscreen(art.fullscreen)
|
||||
current.bind(events, {
|
||||
skip,
|
||||
click() {
|
||||
if (option.url)
|
||||
(document.defaultView || window).open(option.url)
|
||||
if (active())
|
||||
art.emit('artplayerPluginAds:click', option)
|
||||
},
|
||||
fullscreen() {
|
||||
art.fullscreen = !art.fullscreen
|
||||
if (active())
|
||||
current.fullscreen(art.fullscreen)
|
||||
},
|
||||
})
|
||||
events.on('fullscreen', () => current.fullscreen(art.fullscreen))
|
||||
const visibility = () => document.hidden ? clock.pause() : clock.play()
|
||||
events.dom(document, 'visibilitychange', visibility)
|
||||
// Support the newer core event bridge as well; the clock deduplicates resume.
|
||||
events.on('document:visibilitychange', visibility)
|
||||
const ready = () => {
|
||||
if (!active() || mediaReady)
|
||||
return
|
||||
mediaReady = true
|
||||
clock.start()
|
||||
if (document.hidden)
|
||||
clock.pause()
|
||||
if (current.video) {
|
||||
const video = current.video
|
||||
requestPlay(() => video.play(), (error) => {
|
||||
if (active()) {
|
||||
report(error)
|
||||
skip()
|
||||
}
|
||||
}, () => pauseVideo(video))
|
||||
}
|
||||
if (active())
|
||||
current.ready()
|
||||
}
|
||||
if (current.video) {
|
||||
events.dom(current.video, 'error', skip)
|
||||
events.dom(current.video, 'loadedmetadata', ready)
|
||||
// Observe readiness/failure before initiating a potentially cached request.
|
||||
current.video.src = option.video!
|
||||
}
|
||||
else {
|
||||
ready()
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
destroy()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
lifetime.on('destroy', destroy)
|
||||
events.on('ready', () => {
|
||||
if (armed || state !== 'waiting')
|
||||
return
|
||||
armed = true
|
||||
events.on('play', init)
|
||||
events.on('video:playing', init)
|
||||
})
|
||||
if (art.isDestroy)
|
||||
destroy()
|
||||
}
|
||||
catch (error) {
|
||||
destroy()
|
||||
throw error
|
||||
}
|
||||
|
||||
return { name: 'artplayerPluginAds', skip, pause: clock.pause, play: clock.play }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
|
||||
export interface Translations {
|
||||
close: string
|
||||
countdown: string
|
||||
detail: string
|
||||
canBeClosed: string
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
html?: string
|
||||
video?: string
|
||||
url?: string
|
||||
playDuration: number
|
||||
totalDuration: number
|
||||
muted?: boolean
|
||||
i18n: Translations
|
||||
}
|
||||
|
||||
export type Input = Partial<Options>
|
||||
export type Host = Pick<Artplayer, 'on' | 'off' | 'emit' | 'play' | 'pause' | 'template' | 'icons' | 'fullscreen' | 'isDestroy'>
|
||||
export type Utilities = Pick<typeof Artplayer.utils, 'append' | 'query' | 'setStyle'>
|
||||
export type Icons = Pick<Artplayer['icons'], 'volume' | 'volumeClose' | 'fullscreenOn' | 'fullscreenOff' | 'loading'>
|
||||
export type Cleanup = () => void
|
||||
|
||||
export interface Result {
|
||||
name: 'artplayerPluginAds'
|
||||
skip: Cleanup
|
||||
pause: Cleanup
|
||||
play: Cleanup
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import type { Resources } from './resources'
|
||||
import type { Cleanup, Icons, Options, Utilities } from './types'
|
||||
|
||||
const translate = (value: number, text: string) => text.replace('%s', String(value))
|
||||
|
||||
export function createView(parent: HTMLElement, icons: Icons, option: Options, utils: Utilities, own: (root: HTMLElement) => void) {
|
||||
const { append, query, setStyle } = utils
|
||||
// The private markup below fixes the element kind; public append/query retain their broad types.
|
||||
const root = append(parent, '<div class="artplayer-plugin-ads"></div>') as HTMLElement
|
||||
own(root)
|
||||
const content = append(root, option.video
|
||||
? '<video class="artplayer-plugin-ads-video" loop playsInline></video>'
|
||||
: `<div class="artplayer-plugin-ads-html">${option.html}</div>`) as HTMLElement
|
||||
const video = option.video ? content as HTMLVideoElement : null
|
||||
const loading = append(root, '<div class="artplayer-plugin-ads-loading"></div>') as HTMLElement
|
||||
append(loading, icons.loading)
|
||||
const timer = append(root, `<div class="artplayer-plugin-ads-timer">
|
||||
<div class="artplayer-plugin-ads-close"></div>
|
||||
<div class="artplayer-plugin-ads-countdown"></div>
|
||||
</div>`) as HTMLElement
|
||||
const close = query('.artplayer-plugin-ads-close', timer) as HTMLElement
|
||||
const countdown = query('.artplayer-plugin-ads-countdown', timer) as HTMLElement
|
||||
const control = append(root, `<div class="artplayer-plugin-ads-control">
|
||||
<div class="artplayer-plugin-ads-detail">${option.i18n.detail}</div>
|
||||
<div class="artplayer-plugin-ads-muted"></div>
|
||||
<div class="artplayer-plugin-ads-fullscreen"></div>
|
||||
</div>`) as HTMLElement
|
||||
const detail = query('.artplayer-plugin-ads-detail', control) as HTMLElement
|
||||
const muted = query('.artplayer-plugin-ads-muted', control) as HTMLElement
|
||||
const fullscreen = query('.artplayer-plugin-ads-fullscreen', control) as HTMLElement
|
||||
let canClose = option.playDuration <= 0
|
||||
|
||||
if (option.playDuration >= option.totalDuration)
|
||||
setStyle(close, 'display', 'none')
|
||||
if (!option.url)
|
||||
setStyle(detail, 'display', 'none')
|
||||
if (video) {
|
||||
append(muted, icons.volume)
|
||||
append(muted, icons.volumeClose)
|
||||
video.muted = Boolean(option.muted)
|
||||
syncMuted()
|
||||
}
|
||||
else {
|
||||
setStyle(muted, 'display', 'none')
|
||||
}
|
||||
append(fullscreen, icons.fullscreenOn)
|
||||
append(fullscreen, icons.fullscreenOff)
|
||||
|
||||
function syncMuted(): void {
|
||||
setStyle(icons.volume, 'display', video?.muted ? 'none' : 'inline-flex')
|
||||
setStyle(icons.volumeClose, 'display', video?.muted ? 'inline-flex' : 'none')
|
||||
}
|
||||
|
||||
function render(elapsed: number): void {
|
||||
const remaining = option.playDuration - elapsed
|
||||
canClose = elapsed === 0 ? option.playDuration <= 0 : remaining < 1 || Number.isNaN(remaining)
|
||||
close.innerHTML = canClose ? option.i18n.close : translate(remaining, option.i18n.canBeClosed)
|
||||
countdown.innerHTML = translate(option.totalDuration - elapsed, option.i18n.countdown)
|
||||
}
|
||||
render(0)
|
||||
|
||||
return {
|
||||
root,
|
||||
video,
|
||||
render,
|
||||
ready() {
|
||||
setStyle(timer, 'display', 'flex')
|
||||
setStyle(control, 'display', 'flex')
|
||||
setStyle(loading, 'display', 'none')
|
||||
},
|
||||
hide() { setStyle(root, 'display', 'none') },
|
||||
fullscreen(active: boolean) {
|
||||
setStyle(icons.fullscreenOn, 'display', active ? 'none' : 'inline-flex')
|
||||
setStyle(icons.fullscreenOff, 'display', active ? 'inline-flex' : 'none')
|
||||
},
|
||||
bind(events: Resources, callbacks: { skip: Cleanup, click: Cleanup, fullscreen: Cleanup }) {
|
||||
events.dom(close, 'click', () => {
|
||||
if (canClose)
|
||||
callbacks.skip()
|
||||
})
|
||||
events.dom(content, 'click', callbacks.click)
|
||||
if (option.url)
|
||||
events.dom(detail, 'click', callbacks.click)
|
||||
if (video) {
|
||||
events.dom(muted, 'click', () => {
|
||||
video.muted = !video.muted
|
||||
syncMuted()
|
||||
})
|
||||
}
|
||||
events.dom(fullscreen, 'click', callbacks.fullscreen)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export type View = ReturnType<typeof createView>
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": { "allowJs": false },
|
||||
"include": ["src/**/*.ts", "types/**/*.d.ts", "../../types/assets.d.ts"]
|
||||
}
|
||||
@@ -1,5 +1,20 @@
|
||||
# Ads 回归测试与维护入口
|
||||
|
||||
## 03 源码改造补充
|
||||
|
||||
当前源码已按 [包内架构](../packages/artplayer-plugin-ads/ARCHITECTURE.md) 拆为 7 个严格 TS
|
||||
模块。新增 test/ads-lifecycle.test.js,仅候选实现运行修复断言;test/ads.test.js 的旧
|
||||
行为与冻结缺陷观察保留,新增动态修改事件配置的兼容检查。三个正式格式也可通过
|
||||
ARTPLAYER_TEST_ADS 加入同一运行。02 的原始输入哈希/结果不覆盖更新。
|
||||
|
||||
浏览器加入实际 4.5.5 核心映射;候选用例覆盖提前 skip、零阈值幂等、真实 destroy/src
|
||||
释放与两类内部播放拒绝。拒绝用例透明记录 console.warn 的实际参数,仍调用原方法,
|
||||
不依赖 Firefox 等引擎不同的 console 文本渲染。成功播放仍检查真实像素和时钟。
|
||||
结构/缺陷差异及验证过程见 [03 记录](changes/2026-09-12-PKG-ADS-03-lifecycle.md)。
|
||||
03 最终正式三格式 Node 186 项通过;main/legacy 三引擎各 144 项通过;完整 CI 830 项
|
||||
通过、269 生产 TS 严格检查。对应 [新执行证据](baselines/ads-lifecycle-validation.json)
|
||||
与以下 02 历史结果分别保留,不能把任何一轮当作全部包的发布证明。
|
||||
|
||||
Ads 的实际发布行为以 [1.0.6 归档](baselines/ads-release.json) 为基线;工作区 2.1.0
|
||||
未发布,其旧 bundle 单独从冻结 Git 提交加载。不要将当前声明里的 source/type 当成
|
||||
发布 JS 支持的输入;正常广告仍使用 html/video/url,图片通过 HTML img 提供。
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"task": "PKG-ADS-03",
|
||||
"sourceCommit": "069ad4aa",
|
||||
"release": {
|
||||
"name": "artplayer",
|
||||
"version": "4.5.5",
|
||||
"tarball": "https://registry.npmjs.org/artplayer/-/artplayer-4.5.5.tgz",
|
||||
"integrity": "sha512-HeAKbe3od9ZgFQVSBCg5FlixJsefDEBC8txVG19yZgvzlcJuiurxPlasap+GHuex/ftG+jmSXnTRtuNrTki2Kw==",
|
||||
"sha256": "cf867dc73b6dfca9a9e2313ea8e13ed35e5e3adb37b7046fd9e0316bf0ebdb35",
|
||||
"manifest": {
|
||||
"license": "MIT",
|
||||
"name": "artplayer",
|
||||
"version": "4.5.5",
|
||||
"main": "dist/artplayer.js",
|
||||
"types": "types/artplayer.d.ts",
|
||||
"homepage": "https://artplayer.org",
|
||||
"author": "Harvey Zack <laozhaochaguan@gmail.com>",
|
||||
"browserslist": "> 0.5%, last 2 versions, not dead",
|
||||
"description": "ArtPlayer.js is a modern and full featured HTML5 video player",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"html5",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"dependencies": {
|
||||
"option-validator": "^2.0.6",
|
||||
"screenfull": "^6.0.2"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"package/package.json": "d559f1c2a6b308842517f025553b38b29e7948241458a93fc573533856224eeb",
|
||||
"package/dist/artplayer.js": "3467e626ca29d2f7884e75eb75b8b90de74a25761ffa2e272576c9b5a98fa0a4",
|
||||
"package/src/index.js": "8593d5bc9e99bcc53ab8cf8300f8e9d7767b765beaa2ef5c0443cabb42595a58",
|
||||
"package/src/events/index.js": "d0a68560c887f2beb0ac5b3ff4ff8ef68e5564e3b106c33c4819b1b272cd1493",
|
||||
"package/src/utils/dom.js": "d82466f14e9f70b6e1e8d153505087e97be51c64c9e6d55d6bd65ded19a6ead4"
|
||||
}
|
||||
},
|
||||
"archiveMembers": [
|
||||
"package/README.md",
|
||||
"package/dist/artplayer.js",
|
||||
"package/examples/react/Artplayer.js",
|
||||
"package/examples/vue/Artplayer.vue",
|
||||
"package/package.json",
|
||||
"package/src/config/index.js",
|
||||
"package/src/contextmenu/aspectRatio.js",
|
||||
"package/src/contextmenu/close.js",
|
||||
"package/src/contextmenu/flip.js",
|
||||
"package/src/contextmenu/index.js",
|
||||
"package/src/contextmenu/info.js",
|
||||
"package/src/contextmenu/playbackRate.js",
|
||||
"package/src/contextmenu/version.js",
|
||||
"package/src/control/airplay.js",
|
||||
"package/src/control/fullscreen.js",
|
||||
"package/src/control/fullscreenWeb.js",
|
||||
"package/src/control/index.js",
|
||||
"package/src/control/loop.js",
|
||||
"package/src/control/pip.js",
|
||||
"package/src/control/playAndPause.js",
|
||||
"package/src/control/progress.js",
|
||||
"package/src/control/quality.js",
|
||||
"package/src/control/screenshot.js",
|
||||
"package/src/control/setting.js",
|
||||
"package/src/control/thumbnails.js",
|
||||
"package/src/control/time.js",
|
||||
"package/src/control/volume.js",
|
||||
"package/src/events/clickInit.js",
|
||||
"package/src/events/gestureInit.js",
|
||||
"package/src/events/hoverInit.js",
|
||||
"package/src/events/index.js",
|
||||
"package/src/events/mousemoveInit.js",
|
||||
"package/src/events/resizeInit.js",
|
||||
"package/src/events/viewInit.js",
|
||||
"package/src/hotkey.js",
|
||||
"package/src/i18n/cs.json",
|
||||
"package/src/i18n/es.json",
|
||||
"package/src/i18n/index.js",
|
||||
"package/src/i18n/pl.json",
|
||||
"package/src/i18n/zh-cn.json",
|
||||
"package/src/i18n/zh-tw.json",
|
||||
"package/src/icons/airplay.svg",
|
||||
"package/src/icons/arrow-left.svg",
|
||||
"package/src/icons/arrow-right.svg",
|
||||
"package/src/icons/aspect-ratio.svg",
|
||||
"package/src/icons/check.svg",
|
||||
"package/src/icons/close.svg",
|
||||
"package/src/icons/config.svg",
|
||||
"package/src/icons/error.svg",
|
||||
"package/src/icons/fast-forward.svg",
|
||||
"package/src/icons/flip.svg",
|
||||
"package/src/icons/fullscreen-off.svg",
|
||||
"package/src/icons/fullscreen-on.svg",
|
||||
"package/src/icons/fullscreen-web-off.svg",
|
||||
"package/src/icons/fullscreen-web-on.svg",
|
||||
"package/src/icons/index.js",
|
||||
"package/src/icons/loading.svg",
|
||||
"package/src/icons/lock.svg",
|
||||
"package/src/icons/pause.svg",
|
||||
"package/src/icons/pip.svg",
|
||||
"package/src/icons/play.svg",
|
||||
"package/src/icons/playback-rate.svg",
|
||||
"package/src/icons/screenshot.svg",
|
||||
"package/src/icons/setting.svg",
|
||||
"package/src/icons/state.svg",
|
||||
"package/src/icons/switch-off.svg",
|
||||
"package/src/icons/switch-on.svg",
|
||||
"package/src/icons/unlock.svg",
|
||||
"package/src/icons/volume-close.svg",
|
||||
"package/src/icons/volume.svg",
|
||||
"package/src/index.js",
|
||||
"package/src/info.js",
|
||||
"package/src/layer.js",
|
||||
"package/src/libs/screenfull.js",
|
||||
"package/src/loading.js",
|
||||
"package/src/mask.js",
|
||||
"package/src/mobile.js",
|
||||
"package/src/notice.js",
|
||||
"package/src/player/airplayMix.js",
|
||||
"package/src/player/aspectRatioMix.js",
|
||||
"package/src/player/attrMix.js",
|
||||
"package/src/player/autoHeightMix.js",
|
||||
"package/src/player/autoSizeMix.js",
|
||||
"package/src/player/currentTimeMix.js",
|
||||
"package/src/player/durationMix.js",
|
||||
"package/src/player/eventInit.js",
|
||||
"package/src/player/flipMix.js",
|
||||
"package/src/player/fullscreenMix.js",
|
||||
"package/src/player/fullscreenWebMix.js",
|
||||
"package/src/player/index.js",
|
||||
"package/src/player/loadedMix.js",
|
||||
"package/src/player/loopMix.js",
|
||||
"package/src/player/miniMix.js",
|
||||
"package/src/player/normalSizeMix.js",
|
||||
"package/src/player/optionInit.js",
|
||||
"package/src/player/pauseMix.js",
|
||||
"package/src/player/pipMix.js",
|
||||
"package/src/player/playMix.js",
|
||||
"package/src/player/playbackRateMix.js",
|
||||
"package/src/player/playedMix.js",
|
||||
"package/src/player/playingMix.js",
|
||||
"package/src/player/posterMix.js",
|
||||
"package/src/player/rectMix.js",
|
||||
"package/src/player/screenshotMix.js",
|
||||
"package/src/player/seekMix.js",
|
||||
"package/src/player/subtitleOffsetMix.js",
|
||||
"package/src/player/switchMix.js",
|
||||
"package/src/player/themeMix.js",
|
||||
"package/src/player/titleMix.js",
|
||||
"package/src/player/toggleMix.js",
|
||||
"package/src/player/urlMix.js",
|
||||
"package/src/player/volumeMix.js",
|
||||
"package/src/plugins/autoOrientation.js",
|
||||
"package/src/plugins/autoPlayback.js",
|
||||
"package/src/plugins/fastForward.js",
|
||||
"package/src/plugins/index.js",
|
||||
"package/src/plugins/lock.js",
|
||||
"package/src/plugins/miniProgressBar.js",
|
||||
"package/src/scheme/index.js",
|
||||
"package/src/setting/aspectRatio.js",
|
||||
"package/src/setting/flip.js",
|
||||
"package/src/setting/index.js",
|
||||
"package/src/setting/playbackRate.js",
|
||||
"package/src/setting/subtitleOffset.js",
|
||||
"package/src/storage.js",
|
||||
"package/src/style/artplayer.less",
|
||||
"package/src/style/aspectRatio.less",
|
||||
"package/src/style/autoSize.less",
|
||||
"package/src/style/bottom.less",
|
||||
"package/src/style/contextmenu.less",
|
||||
"package/src/style/danmuku.less",
|
||||
"package/src/style/flip.less",
|
||||
"package/src/style/fullscreenWeb.less",
|
||||
"package/src/style/hideCursor.less",
|
||||
"package/src/style/hint.less",
|
||||
"package/src/style/index.less",
|
||||
"package/src/style/info.less",
|
||||
"package/src/style/layers.less",
|
||||
"package/src/style/loading.less",
|
||||
"package/src/style/mask.less",
|
||||
"package/src/style/mini.less",
|
||||
"package/src/style/notice.less",
|
||||
"package/src/style/plugins.less",
|
||||
"package/src/style/poster.less",
|
||||
"package/src/style/selector.less",
|
||||
"package/src/style/setting.less",
|
||||
"package/src/style/subtitle.less",
|
||||
"package/src/style/video.less",
|
||||
"package/src/subtitle.js",
|
||||
"package/src/template.js",
|
||||
"package/src/utils/compatibility.js",
|
||||
"package/src/utils/component.js",
|
||||
"package/src/utils/dom.js",
|
||||
"package/src/utils/emitter.js",
|
||||
"package/src/utils/error.js",
|
||||
"package/src/utils/file.js",
|
||||
"package/src/utils/format.js",
|
||||
"package/src/utils/index.js",
|
||||
"package/src/utils/property.js",
|
||||
"package/src/utils/subtitle.js",
|
||||
"package/src/utils/time.js",
|
||||
"package/src/whitelist.js",
|
||||
"package/tsconfig.json",
|
||||
"package/types/artplayer.d.ts"
|
||||
],
|
||||
"note": "Actual core associated with Ads 1.0.6 source. Full archive is integrity-checked; selected runtime/manifest/source members are separately fingerprinted. No inference of all 4.x support."
|
||||
}
|
||||
@@ -0,0 +1,893 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"task": "PKG-ADS-03",
|
||||
"sourceCommit": "069ad4aa",
|
||||
"production": [
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/src/countdown.ts",
|
||||
"sha256LF": "7dab796a3d8fb757da7f87e4396403f0687af52d0876cf74ebb2b8db7c2e752a"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/src/index.ts",
|
||||
"sha256LF": "f4fd3eaa7ee0ba638b4bd324305cc496dbb0a5ecd7bf3fac7745f6fcf2ac2978"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/src/options.ts",
|
||||
"sha256LF": "fc49c6a73c1563c05067566e243679b42f8cec3e5365fde9873126b1e16af964"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/src/resources.ts",
|
||||
"sha256LF": "ba0d5b5f25d79929a39a9cda6b80b9e60a2b746e20312aa658d84fbb9be6562f"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/src/session.ts",
|
||||
"sha256LF": "59540b169b7bb348ddd1d773368717cbd6c4d3b1b28305c1c759fea6aef8d827"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/src/style.less",
|
||||
"sha256LF": "0eeed3f29ccafc6836f5bc4338f58f784edd114d2b100fda7fc395329031a406"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/src/types.ts",
|
||||
"sha256LF": "87fab15723a3eb596d12a22d737d4127d06dd4c45604f9888efe7557fb64f6e0"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/src/view.ts",
|
||||
"sha256LF": "7d2cba7a3e20ce02288627723ebc9256d61cee313c3a7d00a31373b7f250eb91"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"sha256LF": "135e26a113b36ae6b0144abd969649884dd1e2e171125c5958d25eb8b618764e"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/tsconfig.json",
|
||||
"sha256LF": "5b53a2bb158e81c5bfd3e2b50e750f7f6e20d0e9aaa6733facec036c15f28ebe"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/.npmignore",
|
||||
"sha256LF": "145c5fa0341195e21cf13df3423a057282f8ea374e9a1b3065e2280e1ce1c5c1"
|
||||
},
|
||||
{
|
||||
"file": "test/ads.test.js",
|
||||
"sha256LF": "918e2c743142af281adb86f55084a1a6421aa3bdbc60e851e14859e3f8b48f50"
|
||||
},
|
||||
{
|
||||
"file": "test/ads-lifecycle.test.js",
|
||||
"sha256LF": "992b8b786b68735a9472ae26ad0c27306b00292cba28d8d744de9318279107de"
|
||||
},
|
||||
{
|
||||
"file": "test/helpers/ads.js",
|
||||
"sha256LF": "89f6a65006810813be7b3d60d35837241c2076470131f29d2291ba0a29fae387"
|
||||
},
|
||||
{
|
||||
"file": "test/browser/ads.spec.js",
|
||||
"sha256LF": "f0a52956e5d7b8a81f3cfef6b52d6efb01f8fe78f6c63563c596500b2bb5dd80"
|
||||
},
|
||||
{
|
||||
"file": "test/browser/server.mjs",
|
||||
"sha256LF": "d475bef6e13682f1f2445cdd3b1a01e6e27c469c5c1f21de0286969d38cd1a26"
|
||||
},
|
||||
{
|
||||
"file": "refactor/baselines/ads-core.json",
|
||||
"sha256LF": "5f678676e2e6307bf829912880ae112eff5e46b8a67b7731f0c5a77929f883a9"
|
||||
},
|
||||
{
|
||||
"file": "refactor/scripts/ads-core.test.mjs",
|
||||
"sha256LF": "514b446c5dd42dc3c7b7230699fde45915a64bc5b8d2fc4799f1fc6b66f14516"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.js",
|
||||
"sha256": "dd36ba43be92b156f6a0476be7924300151f21542f493b18f4dd1ca7eaa01446",
|
||||
"bytes": 7236,
|
||||
"copy": {
|
||||
"file": "docs/compiled/artplayer-plugin-ads.js",
|
||||
"sha256": "dd36ba43be92b156f6a0476be7924300151f21542f493b18f4dd1ca7eaa01446",
|
||||
"bytes": 7236
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.legacy.js",
|
||||
"sha256": "1d27be200f522ab80db9562672e531306fbc9f6de4b4a6f311824cf6c4af4cfe",
|
||||
"bytes": 7675,
|
||||
"copy": {
|
||||
"file": "docs/compiled/artplayer-plugin-ads.legacy.js",
|
||||
"sha256": "1d27be200f522ab80db9562672e531306fbc9f6de4b4a6f311824cf6c4af4cfe",
|
||||
"bytes": 7675
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.mjs",
|
||||
"sha256": "0e3cc7f1cd527e268a50a7a2126df37a334d26f180fd6e5561f7f7595cfbd0e7",
|
||||
"bytes": 12990,
|
||||
"copy": {
|
||||
"file": "docs/compiled/artplayer-plugin-ads.mjs",
|
||||
"sha256": "0e3cc7f1cd527e268a50a7a2126df37a334d26f180fd6e5561f7f7595cfbd0e7",
|
||||
"bytes": 12990
|
||||
}
|
||||
}
|
||||
],
|
||||
"checks": [
|
||||
{
|
||||
"file": "refactor/.cache/ads03-before.log",
|
||||
"sha256": "814f35b7ce294893bb1fa0240fbf45cf6ce32b2db9facab20ba7002df2b9134f",
|
||||
"bytes": 36247,
|
||||
"passed": 0,
|
||||
"failed": 10,
|
||||
"note": "First ten candidate regressions on pre-refactor source."
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-first.log",
|
||||
"sha256": "b7ead3b69d388ee384baf12c7371caf6ec462761568e13bc0d68fb813929432e",
|
||||
"bytes": 41374,
|
||||
"passed": 67,
|
||||
"failed": 1,
|
||||
"note": "Initial source: controlled Node fixture lacked native remove; strict TS also required a callback-aware state reader."
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-reentry-before.log",
|
||||
"sha256": "6587b1ec86b2b6238fb0d30f57703ab382a26948c09cf315fe691644bb587b1f",
|
||||
"bytes": 2967,
|
||||
"passed": 19,
|
||||
"failed": 1,
|
||||
"note": "New synchronous destroy during ad pause regression; repaired before final production build."
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-artifacts-node-first.log",
|
||||
"sha256": "c0e310174cabe60ecb1be75ea9804c70dd039927504cc5b96c912d97f9dea66b",
|
||||
"bytes": 23947,
|
||||
"passed": 182,
|
||||
"failed": 4,
|
||||
"note": "VM cross-realm Promise assimilation took more than two microtasks; changed only the test drain."
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-artifacts-node.log",
|
||||
"sha256": "a1f1d696a3b6f0c6ee255f02536254ba49786dd6e0a879c037bb6f37634af602",
|
||||
"bytes": 20903,
|
||||
"passed": 186,
|
||||
"failed": 0,
|
||||
"note": "Source plus all three final artifacts and both frozen implementations; public contracts and candidate lifecycle."
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-build.log",
|
||||
"sha256": "d036d8139d6d4607994a3adc75a90eb9b3602400db91d661eca5467ee952f00f",
|
||||
"bytes": 1438
|
||||
}
|
||||
],
|
||||
"browser": [
|
||||
{
|
||||
"name": "shared",
|
||||
"note": "Historical 4.5.5 fixture incorrectly used the newer art.video alias; all reports retained.",
|
||||
"stats": {
|
||||
"startTime": "2026-09-11T23:37:45.302Z",
|
||||
"duration": 146339.709,
|
||||
"expected": 66,
|
||||
"skipped": 0,
|
||||
"unexpected": 33,
|
||||
"flaky": 0
|
||||
},
|
||||
"report": {
|
||||
"file": "refactor/.cache/ads03-browser-shared-report.json",
|
||||
"sha256": "4ffd4196b6173f78eaf80625096207f4fc9ad47e321c2adad114dd9422feddfd",
|
||||
"bytes": 7714476
|
||||
},
|
||||
"log": {
|
||||
"file": "refactor/.cache/ads03-browser-shared.log",
|
||||
"sha256": "aa0b3989a8887a9c37a732cc704e9be7fbdb8fba18cd19688f3a96b0e3e3200c",
|
||||
"bytes": 108330
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/.last-run.json",
|
||||
"sha256": "90dc2c1cf9cf3f75bf81622bdd7faff122fe2aeb834fa26bced10cae2219ea57",
|
||||
"bytes": 1664
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-1e3a2-acement-does-not-replay-ads-chromium/error-context.md",
|
||||
"sha256": "ef5244b38edf71430c0b90e6ff1c743e299a54201880682060230d40812aba35",
|
||||
"bytes": 776
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-1e3a2-acement-does-not-replay-ads-chromium/test-failed-1.png",
|
||||
"sha256": "4b6d3264c2a48cce81c5687dbd0a4171be5e71927f153a29f063f64502a3a39a",
|
||||
"bytes": 79563
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-1e3a2-acement-does-not-replay-ads-chromium/trace.zip",
|
||||
"sha256": "9be3d8ee1155550d26b65647e8cc32ca47b8cb5611f8bc0d92a014bb0494066b",
|
||||
"bytes": 996043
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-1e3a2-acement-does-not-replay-ads-firefox/error-context.md",
|
||||
"sha256": "7871833d9a264ef0dcd4ffbbe6078d69d604170240b85590331f555b2159f28e",
|
||||
"bytes": 736
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-1e3a2-acement-does-not-replay-ads-firefox/test-failed-1.png",
|
||||
"sha256": "5838a1ed3f9ebfc277a128ffd72f94f817c05654f5c9d01fd35e97cacd52339c",
|
||||
"bytes": 110398
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-1e3a2-acement-does-not-replay-ads-firefox/trace.zip",
|
||||
"sha256": "017fb7b8361fc907916dede7eeaaa0076a94cc54668998e54a46dfb8e2dc061a",
|
||||
"bytes": 986398
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-1e3a2-acement-does-not-replay-ads-webkit/error-context.md",
|
||||
"sha256": "2dd0ad6dcb2b02fdf0bc7b0c208adffcfc62bf1eaca1bf0a8ed0f8588cdbac9b",
|
||||
"bytes": 9111
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-1e3a2-acement-does-not-replay-ads-webkit/test-failed-1.png",
|
||||
"sha256": "0dbfa9bf58a269c1b5cbc322ad359afa65f0bdb1846de179a2d7fa5f84ce09fa",
|
||||
"bytes": 274748
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-1e3a2-acement-does-not-replay-ads-webkit/trace.zip",
|
||||
"sha256": "e2b71a13c82da7ea3f3321c0802366f6173f40305091499ea01b1b08a6b5ef6b",
|
||||
"bytes": 591985
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-2b017-reshold-and-resumed-content-chromium/error-context.md",
|
||||
"sha256": "d8e09a89765ee3943dd824072fbccb5e92df69f549673b5aa110591730390f94",
|
||||
"bytes": 1586
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-2b017-reshold-and-resumed-content-chromium/test-failed-1.png",
|
||||
"sha256": "84b9477647c08e7834233e683b9388fabbf12cd8a6bf11429a24e87e46315c82",
|
||||
"bytes": 39173
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-2b017-reshold-and-resumed-content-chromium/trace.zip",
|
||||
"sha256": "8dfd1f75d6120adf3421203bdf66f182dee685c3d34d060cccc0771c2c4ea4dc",
|
||||
"bytes": 227009
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-2b017-reshold-and-resumed-content-firefox/error-context.md",
|
||||
"sha256": "559a1b90954d5c99407c33726c353517e0d0955ff179849d4f5b05ffad044f63",
|
||||
"bytes": 1506
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-2b017-reshold-and-resumed-content-firefox/test-failed-1.png",
|
||||
"sha256": "1305c8f416bdc660c918a6eb2b7e1aa62f9aa3d0571bec5489d417d30c564cec",
|
||||
"bytes": 44317
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-2b017-reshold-and-resumed-content-firefox/trace.zip",
|
||||
"sha256": "d9e3d9eb69bd5a41bee575803df36ae2879761a31d627cf03ab13d400188e352",
|
||||
"bytes": 563304
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-2b017-reshold-and-resumed-content-webkit/error-context.md",
|
||||
"sha256": "a5ec00c127ea6ed0e601659c17468a75cf98616e7b02a21eebec569f21b6001e",
|
||||
"bytes": 9770
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-2b017-reshold-and-resumed-content-webkit/test-failed-1.png",
|
||||
"sha256": "ed9d1018d409dc75f3513bddc1f2156bc05b595fb504204626547a5a7d216900",
|
||||
"bytes": 102016
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-2b017-reshold-and-resumed-content-webkit/trace.zip",
|
||||
"sha256": "b447f96893c5fadf3ad40851c4a626d6398c34e103d992118e3bb31e62b7b743",
|
||||
"bytes": 201624
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-c88d1-ls-open-to-playable-content-chromium/error-context.md",
|
||||
"sha256": "e20426126d5cb3b7df68debd78d40cf410031e35498a1174177d53bd1ccbe06a",
|
||||
"bytes": 1041
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-c88d1-ls-open-to-playable-content-chromium/test-failed-1.png",
|
||||
"sha256": "24e7c007f06bd62468312b3febc7fd03392176d20472de6d693b0648b9c46b1b",
|
||||
"bytes": 83698
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-c88d1-ls-open-to-playable-content-chromium/trace.zip",
|
||||
"sha256": "4a7ca4e36c068c744f419de1f2f467152b89670348eb92d24f871297a9eaa1b7",
|
||||
"bytes": 449038
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-c88d1-ls-open-to-playable-content-firefox/error-context.md",
|
||||
"sha256": "ae4199a32dadc92e6a762efe1716302f37dbed76debe803ae0e33ad959b312e1",
|
||||
"bytes": 1001
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-c88d1-ls-open-to-playable-content-firefox/test-failed-1.png",
|
||||
"sha256": "d483027341c8b3eea0b43f28265f8b6ec7c519af01766e6043f3a75f7b7297b5",
|
||||
"bytes": 117412
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-c88d1-ls-open-to-playable-content-firefox/trace.zip",
|
||||
"sha256": "e974471baaf967acc8479f1e58c5ef6dc1ebce34e920a5f0bd5511c9ae78a4dc",
|
||||
"bytes": 730455
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-c88d1-ls-open-to-playable-content-webkit/error-context.md",
|
||||
"sha256": "59951fa0c234973d34933462389273e87ab868c542d445c3f3b98dddc2045207",
|
||||
"bytes": 9376
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-c88d1-ls-open-to-playable-content-webkit/test-failed-1.png",
|
||||
"sha256": "e9bb3ca915c9305e670be1a4401e661150471985002805fc9f5d0eaad5ed74b5",
|
||||
"bytes": 296764
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-c88d1-ls-open-to-playable-content-webkit/trace.zip",
|
||||
"sha256": "09689fcea6be8ebfbef20f510a083257f89e77bb5a8acd18b24863b0960c2353",
|
||||
"bytes": 428187
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-da607-completion-restores-content-chromium/error-context.md",
|
||||
"sha256": "725ae57c92da4c558cd8cb42a555accd2af68c03f1d34178424ec36bb38656a7",
|
||||
"bytes": 1060
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-da607-completion-restores-content-chromium/test-failed-1.png",
|
||||
"sha256": "7f3e5854a32a1fcf19a6435355021033914b455a81682b11e064ef84e00991ae",
|
||||
"bytes": 83249
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-da607-completion-restores-content-chromium/trace.zip",
|
||||
"sha256": "c65d0620423417e248f24fa16785e6f566d1514fb8953a58e0e71b2ab9e360fb",
|
||||
"bytes": 990100
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-da607-completion-restores-content-firefox/error-context.md",
|
||||
"sha256": "c3309d51f1b0e08bf38f54db1c440af62c6940b53e478f9b0098e0ee4bd122e4",
|
||||
"bytes": 1020
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-da607-completion-restores-content-firefox/test-failed-1.png",
|
||||
"sha256": "863546a34e733534f5b2078d698e246b52bb3133823b20ef75f630a318d0c030",
|
||||
"bytes": 116926
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-da607-completion-restores-content-firefox/trace.zip",
|
||||
"sha256": "c939ae28160d6827c628cfacc41e6ff3800e4b70337795897a0d8b7281149abf",
|
||||
"bytes": 1433757
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-da607-completion-restores-content-webkit/error-context.md",
|
||||
"sha256": "7e6c7f3eea4cc00f147bfa708014f131c8ff5c27886643b681d84877109e9da5",
|
||||
"bytes": 9395
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-da607-completion-restores-content-webkit/test-failed-1.png",
|
||||
"sha256": "53467d0e5479a3079bdb133cc84203070a2700cf3c7a57222fc9243f42ae4724",
|
||||
"bytes": 292303
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-c-da607-completion-restores-content-webkit/trace.zip",
|
||||
"sha256": "5f4497f4616d0cc64176428ac08f1f6beb57674199c3a133bc68555c4f1778b0",
|
||||
"bytes": 1144671
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-03f26-reshold-and-resumed-content-chromium/error-context.md",
|
||||
"sha256": "474373de10ecfe0c612ba4e340975429cb211c7c7496092fc155df9723e0d952",
|
||||
"bytes": 1586
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-03f26-reshold-and-resumed-content-chromium/test-failed-1.png",
|
||||
"sha256": "84b9477647c08e7834233e683b9388fabbf12cd8a6bf11429a24e87e46315c82",
|
||||
"bytes": 39173
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-03f26-reshold-and-resumed-content-chromium/trace.zip",
|
||||
"sha256": "f3114ce8c2a303d44eda29ba4bbb95b6b6e47a9c58faea2926ac9cad18df71f2",
|
||||
"bytes": 261124
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-03f26-reshold-and-resumed-content-firefox/error-context.md",
|
||||
"sha256": "91442b03b1b1c327ead14fa6ba4ede1b79a9a52fa272286e8a0aadc52728560e",
|
||||
"bytes": 1506
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-03f26-reshold-and-resumed-content-firefox/test-failed-1.png",
|
||||
"sha256": "1305c8f416bdc660c918a6eb2b7e1aa62f9aa3d0571bec5489d417d30c564cec",
|
||||
"bytes": 44317
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-03f26-reshold-and-resumed-content-firefox/trace.zip",
|
||||
"sha256": "066e77efd29e85eec24517b2a0d4cc6cb514bed4f680e4617c0e87aad12771f4",
|
||||
"bytes": 632240
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-03f26-reshold-and-resumed-content-webkit/error-context.md",
|
||||
"sha256": "25b31129cf5a72dcad9427021cf836a7c488b7b10cdeeffa002659017ac83367",
|
||||
"bytes": 9770
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-03f26-reshold-and-resumed-content-webkit/test-failed-1.png",
|
||||
"sha256": "ed9d1018d409dc75f3513bddc1f2156bc05b595fb504204626547a5a7d216900",
|
||||
"bytes": 102016
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-03f26-reshold-and-resumed-content-webkit/trace.zip",
|
||||
"sha256": "b401ff38930495c734204df145504d703c34e09edd5ce296907321935d59df5c",
|
||||
"bytes": 218759
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-656b5-ides-ad-and-leaks-rejection-chromium/error-context.md",
|
||||
"sha256": "1381e5cacc8fa22c9284ab08c509fce398d06025f511bdf9728ce72114571dce",
|
||||
"bytes": 1052
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-656b5-ides-ad-and-leaks-rejection-chromium/test-failed-1.png",
|
||||
"sha256": "735a0cec743607f507b1867a8950b4355911a1bc2f921dc528b7f18bb90d220d",
|
||||
"bytes": 87860
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-656b5-ides-ad-and-leaks-rejection-chromium/trace.zip",
|
||||
"sha256": "6157ee7bbeb711c8383d6446041f6996a1319f797acd2760c7e48157401d48d4",
|
||||
"bytes": 1798906
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-656b5-ides-ad-and-leaks-rejection-firefox/error-context.md",
|
||||
"sha256": "75434d727d466dd2039012c6dbbf7911e5b61edd980bd1e37d006cb21d50b7c9",
|
||||
"bytes": 1012
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-656b5-ides-ad-and-leaks-rejection-firefox/test-failed-1.png",
|
||||
"sha256": "5db8a1e7b06fcbee03f9565bdf47e54e6a947bec8428dc86373e49ef7a08f72b",
|
||||
"bytes": 120538
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-656b5-ides-ad-and-leaks-rejection-firefox/trace.zip",
|
||||
"sha256": "694f76bd0b69461142e1cec9cded3a15f0104aa2adbeacf44c14a52d8944ee13",
|
||||
"bytes": 1841850
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-656b5-ides-ad-and-leaks-rejection-webkit/error-context.md",
|
||||
"sha256": "7438e7961c27d09cc8d467ed4de0353438eb516fe1916ae1bc57ba3777cc75eb",
|
||||
"bytes": 9387
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-656b5-ides-ad-and-leaks-rejection-webkit/test-failed-1.png",
|
||||
"sha256": "95e60e1b5079a2b8d6781739d0a07e6ee2707291afc206f174e070e652e0c4e3",
|
||||
"bytes": 298847
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-656b5-ides-ad-and-leaks-rejection-webkit/trace.zip",
|
||||
"sha256": "6d8398abafef162b4e7c09995590bfc5b7f0e9b5623d710aa2f1196c00e6bdf0",
|
||||
"bytes": 1124496
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-80f2b-skip-before-accepting-click-chromium/error-context.md",
|
||||
"sha256": "e6a1ebb88deaa3ec5e8fae581eefd17c1b972c1048bdeda9c517913ed04a801a",
|
||||
"bytes": 1051
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-80f2b-skip-before-accepting-click-chromium/test-failed-1.png",
|
||||
"sha256": "0f3ddf737ab3f65b9a75eb0cf33f1b284b09b4d139c82b7d184661383c8fce18",
|
||||
"bytes": 78490
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-80f2b-skip-before-accepting-click-chromium/trace.zip",
|
||||
"sha256": "9f7476df6927de06ff6f8730a47192c4d41b0c2d1fba79659cda6134d9e43d8b",
|
||||
"bytes": 260402
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-80f2b-skip-before-accepting-click-firefox/error-context.md",
|
||||
"sha256": "071d0912347be755bd6a0c10ecdb9b8427f9bc0d6290fd32c9b3b90d4926972d",
|
||||
"bytes": 1011
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-80f2b-skip-before-accepting-click-firefox/test-failed-1.png",
|
||||
"sha256": "bfd97ba43d66ef7efb4d668e67d7b32dfbf0a59b81511624bd7bdfa710670850",
|
||||
"bytes": 113846
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-80f2b-skip-before-accepting-click-firefox/trace.zip",
|
||||
"sha256": "a254caf91c161d477458938a0a55dc9473efd6ed4f61860f0cc55829e34b91b5",
|
||||
"bytes": 627188
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-80f2b-skip-before-accepting-click-webkit/error-context.md",
|
||||
"sha256": "3753e5223e183391bdd1e64b5eecfa91913137d0fa867d767eec81b96dc0c9fb",
|
||||
"bytes": 9386
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-80f2b-skip-before-accepting-click-webkit/test-failed-1.png",
|
||||
"sha256": "b683cb6356f72b2da1663ed013314fafeede71567092354bbb813a93733630ec",
|
||||
"bytes": 285997
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-80f2b-skip-before-accepting-click-webkit/trace.zip",
|
||||
"sha256": "6fc3c47ae13f0d1ff055efc9f4807892ef43b238a4978c7dc518975819344a44",
|
||||
"bytes": 376820
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-acaa7-acement-does-not-replay-ads-chromium/error-context.md",
|
||||
"sha256": "ca2aaa7a2287486729730e5b3da91a95a2d5f4f052dc384c64ca077a87f97454",
|
||||
"bytes": 776
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-acaa7-acement-does-not-replay-ads-chromium/test-failed-1.png",
|
||||
"sha256": "c845edd149dd22c95b4b35306d539a5be66814a91d73b095399e2a714ea711dc",
|
||||
"bytes": 79244
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-acaa7-acement-does-not-replay-ads-chromium/trace.zip",
|
||||
"sha256": "fe1d50163e2e23c793b20ea613d5758810875b50b4e57d98e765acb9a0377dab",
|
||||
"bytes": 1002871
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-acaa7-acement-does-not-replay-ads-firefox/error-context.md",
|
||||
"sha256": "76f4f46789ab9f41ccd95e98be82b71655dbfe9d8a3c6a7b7b0d3a14fd0c9c16",
|
||||
"bytes": 736
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-acaa7-acement-does-not-replay-ads-firefox/test-failed-1.png",
|
||||
"sha256": "abb3c79c6c686a9ea02b7ade660e726570f8365e48db2117d1aeaa111858ea5b",
|
||||
"bytes": 111130
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-acaa7-acement-does-not-replay-ads-firefox/trace.zip",
|
||||
"sha256": "136cc4518b14769b612212a84fb8ba5dbb6316711b27a91877023b1a92fa4dc9",
|
||||
"bytes": 1017446
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-acaa7-acement-does-not-replay-ads-webkit/error-context.md",
|
||||
"sha256": "a7ebf7c5d9b11a7d4c80578763c70ba98dce0c180f7c0c0f202d0231a79cbce7",
|
||||
"bytes": 9111
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-acaa7-acement-does-not-replay-ads-webkit/test-failed-1.png",
|
||||
"sha256": "0dbfa9bf58a269c1b5cbc322ad359afa65f0bdb1846de179a2d7fa5f84ce09fa",
|
||||
"bytes": 274748
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-acaa7-acement-does-not-replay-ads-webkit/trace.zip",
|
||||
"sha256": "da3644df7827d24ef08faf7cde5b3a47419a3cd4ca5184a30f3cd3ce71287b50",
|
||||
"bytes": 557989
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-c07a9-ction-while-timer-continues-chromium/error-context.md",
|
||||
"sha256": "dfaa242162bd86466001b172ccfba30125394c64096b323430468e1079da3af7",
|
||||
"bytes": 1054
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-c07a9-ction-while-timer-continues-chromium/test-failed-1.png",
|
||||
"sha256": "6dda9159fcf611dad32d36d218ac7c09386ddf75a808cd7e1c42ebe4070083b6",
|
||||
"bytes": 83727
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-c07a9-ction-while-timer-continues-chromium/trace.zip",
|
||||
"sha256": "b9df6fa13affc44a7a75de205d35ae91422191c21f5b361f6d2828f41f91bbb6",
|
||||
"bytes": 311510
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-c07a9-ction-while-timer-continues-firefox/error-context.md",
|
||||
"sha256": "2efe6713bd57b1d014c023451d956241eb9ab99b7f9073b4c6ed95ddf281d448",
|
||||
"bytes": 1014
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-c07a9-ction-while-timer-continues-firefox/test-failed-1.png",
|
||||
"sha256": "3f206be256985c9c5dab216bd29bb5f756e68150a87519e23040381f8eaf2e94",
|
||||
"bytes": 117115
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-c07a9-ction-while-timer-continues-firefox/trace.zip",
|
||||
"sha256": "8942b1f772e0ea8b9f02b7ea0b31ff36af301083adff8bdfaa5799289eee9242",
|
||||
"bytes": 871673
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-c07a9-ction-while-timer-continues-webkit/error-context.md",
|
||||
"sha256": "3359cc703f59649c19828ce59812a2fbe084dacffff1c59df538a6fdbf4f4bf7",
|
||||
"bytes": 9107
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-c07a9-ction-while-timer-continues-webkit/test-failed-1.png",
|
||||
"sha256": "2b48187e2521ca1a1f9b3a3ca38ed1338f5f29e6a52935dc8aa1724d1304f4d0",
|
||||
"bytes": 274693
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-c07a9-ction-while-timer-continues-webkit/trace.zip",
|
||||
"sha256": "e26c568b9f5aa3040313ab553a62000d3ccd31e9fa26bdf7a060ca3d4c26c190",
|
||||
"bytes": 475883
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-dcb98-ls-open-to-playable-content-chromium/error-context.md",
|
||||
"sha256": "dd120a04a6c9d1e57510e100cb430d74ef493e341eb91066e2673ef2b5f13a60",
|
||||
"bytes": 1041
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-dcb98-ls-open-to-playable-content-chromium/test-failed-1.png",
|
||||
"sha256": "f671f716475a8fca5079a19ba84e00b56cadd47ca753b58994cdd66aa8f24ffa",
|
||||
"bytes": 84342
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-dcb98-ls-open-to-playable-content-chromium/trace.zip",
|
||||
"sha256": "10b3140dba2ac710b1901dc99c808ebaaa3992c833f51713d427f38f41795cbc",
|
||||
"bytes": 467915
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-dcb98-ls-open-to-playable-content-firefox/error-context.md",
|
||||
"sha256": "f3c4253b907c8f08626339cbf218e9605d2a504c7413482e77af15788c4d6384",
|
||||
"bytes": 1001
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-dcb98-ls-open-to-playable-content-firefox/test-failed-1.png",
|
||||
"sha256": "6d30ee0f6c41e920b0b4964e6b7c05f8aac29b97c0089ffd6e0a404369fb45a8",
|
||||
"bytes": 117412
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-dcb98-ls-open-to-playable-content-firefox/trace.zip",
|
||||
"sha256": "21a815b1452a91c12b2359a7bb3277840b48c0bc8d610b85c482db41c26e2368",
|
||||
"bytes": 721477
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-dcb98-ls-open-to-playable-content-webkit/error-context.md",
|
||||
"sha256": "14c4d712045f85d4e24f947e851d9d706e3c8519b9b9cc2128b0719f327be2ca",
|
||||
"bytes": 9376
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-dcb98-ls-open-to-playable-content-webkit/test-failed-1.png",
|
||||
"sha256": "47a7d1cd17b853d1b2188289f3d3246832a8621ee69b640eb0adab2aa7eafca1",
|
||||
"bytes": 293849
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-dcb98-ls-open-to-playable-content-webkit/trace.zip",
|
||||
"sha256": "052ba810895a6e311543feb5391c0f6d398a44f9eb4638f442e1752ddef1c7ca",
|
||||
"bytes": 458015
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-f9294-completion-restores-content-chromium/error-context.md",
|
||||
"sha256": "9bbfe13a8e4dc22155a51556b33cd22e2d9964057e70fb82ca6b37458e662db1",
|
||||
"bytes": 895
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-f9294-completion-restores-content-chromium/test-failed-1.png",
|
||||
"sha256": "28b603ae124d31daeef45e85a281cec9568f90c6ef3434367b179a7e4e3efaa2",
|
||||
"bytes": 83842
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-f9294-completion-restores-content-chromium/trace.zip",
|
||||
"sha256": "4247eacee77713dd5d9d5d7236406fb82567cd396b16a6692fb42fe529233ee4",
|
||||
"bytes": 957983
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-f9294-completion-restores-content-firefox/error-context.md",
|
||||
"sha256": "0140e20e5dec56efb58df2b9b6c976286a69cc19ad57d7512c1c6cb41913de85",
|
||||
"bytes": 1020
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-f9294-completion-restores-content-firefox/test-failed-1.png",
|
||||
"sha256": "4964632cf9d26783dbe72f88b10b9f043fe17cdea51aedb7986f7b5b7341a828",
|
||||
"bytes": 117251
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-f9294-completion-restores-content-firefox/trace.zip",
|
||||
"sha256": "ace815077dcf9fd7cbc334ec748cc013accfcea33980713c9236b4f6cae02312",
|
||||
"bytes": 1415954
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-f9294-completion-restores-content-webkit/error-context.md",
|
||||
"sha256": "6b038ca192ca8029d4e0e0215629c051e5276302bdad08ecf11c7f903f4e7382",
|
||||
"bytes": 9395
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-f9294-completion-restores-content-webkit/test-failed-1.png",
|
||||
"sha256": "9f4cb835fbef26265cab5d12725406f6eeaf01c072b052a22e32b266acee3880",
|
||||
"bytes": 289469
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-shared-results/ads-published-4-5-5-core-p-f9294-completion-restores-content-webkit/trace.zip",
|
||||
"sha256": "4c721c1b153e7644a4a27e013f084e8635b16678d639c5fe8ca8f2b7f54274a5",
|
||||
"bytes": 1152077
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "lifecycle",
|
||||
"note": "Firefox console text did not include DOMException.message; replaced text rendering assertion with transparent original argument observation. Intermediate source build, before final artifact acceptance.",
|
||||
"stats": {
|
||||
"startTime": "2026-09-11T23:41:40.573Z",
|
||||
"duration": 159246.112,
|
||||
"expected": 138,
|
||||
"skipped": 0,
|
||||
"unexpected": 6,
|
||||
"flaky": 0
|
||||
},
|
||||
"report": {
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-report.json",
|
||||
"sha256": "300f7747114bad33dada5f1f47e38969249a2d845a4a3b333c6907c5d090eaeb",
|
||||
"bytes": 9414532
|
||||
},
|
||||
"log": {
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle.log",
|
||||
"sha256": "02e97aebd97cf0942667b330e118c171028e51ea42f54bc2957f717228b22d3f",
|
||||
"bytes": 42188
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/.last-run.json",
|
||||
"sha256": "f0cb82bbf13a73c2704ac594b6ac6685c0ad6fa4950b8543c054da699242ef0f",
|
||||
"bytes": 341
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-candidate-core-candida-13e45--without-an-unhandled-error-firefox/error-context.md",
|
||||
"sha256": "abfa031e13a6a9efe4fd4faebfcd479fd5973e53972ba8e722b040f73106bc55",
|
||||
"bytes": 14686
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-candidate-core-candida-13e45--without-an-unhandled-error-firefox/test-failed-1.png",
|
||||
"sha256": "9d9e3123c4c51b0c3f3b0957fe143e983cb15d68ecb4d5dbb1d6aa2721d95086",
|
||||
"bytes": 106245
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-candidate-core-candida-13e45--without-an-unhandled-error-firefox/trace.zip",
|
||||
"sha256": "d48cb4a82e6ca24b9eab8875632d219b6e1f85ff96467f187c0673473274dfad",
|
||||
"bytes": 792087
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-candidate-core-candida-ba6b9--without-an-unhandled-error-firefox/error-context.md",
|
||||
"sha256": "69e9d71857fbcd9e8c0988ed37bd65bf48a09e579fa1e088c78be94238f385e3",
|
||||
"bytes": 14681
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-candidate-core-candida-ba6b9--without-an-unhandled-error-firefox/test-failed-1.png",
|
||||
"sha256": "3a8afc70cb1a37b2bf6fbade0dd7da15dac966175da8c2a165aea071d1c215ca",
|
||||
"bytes": 106532
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-candidate-core-candida-ba6b9--without-an-unhandled-error-firefox/trace.zip",
|
||||
"sha256": "5c257128599f2c9a1fe54468ea1ffc42a7e101a872086ef888fcb8e3527e6c65",
|
||||
"bytes": 457990
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-4-5-5-core-c-2c63b--without-an-unhandled-error-firefox/error-context.md",
|
||||
"sha256": "9e5c9f04f6036fab2b4067dbfa208d33ffde1ff851e5bb117e0c8969d78e8de0",
|
||||
"bytes": 14687
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-4-5-5-core-c-2c63b--without-an-unhandled-error-firefox/test-failed-1.png",
|
||||
"sha256": "18df6a31cbbace68ce36d49932d39c80913a5f01d3c2242a4609ddb4ca2ddbe8",
|
||||
"bytes": 103178
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-4-5-5-core-c-2c63b--without-an-unhandled-error-firefox/trace.zip",
|
||||
"sha256": "c4f1e54fd76c14295ee6b9e972325b47729bffd35569515d97b177cf167a581e",
|
||||
"bytes": 518630
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-4-5-5-core-c-b921a--without-an-unhandled-error-firefox/error-context.md",
|
||||
"sha256": "7b8d9f1459e49e794f6959419121cca3ded4219a1b1e1abe55c09062be9ceef8",
|
||||
"bytes": 14692
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-4-5-5-core-c-b921a--without-an-unhandled-error-firefox/test-failed-1.png",
|
||||
"sha256": "b5a1d4125c6c7d3bb8328c16ea8d97385f123756da1fa8c3c521d916383d60e5",
|
||||
"bytes": 104744
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-4-5-5-core-c-b921a--without-an-unhandled-error-firefox/trace.zip",
|
||||
"sha256": "0ba655ceaa0133c9bdf06eb994d53da45b3e6700a7dd16c38209f223bd5cfb8a",
|
||||
"bytes": 577326
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-core-candida-35c50--without-an-unhandled-error-firefox/error-context.md",
|
||||
"sha256": "cf36c7c818d24d0a2d7b04d1bd4a83f467c218c745d5eb9bc3e8329fee174a55",
|
||||
"bytes": 14686
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-core-candida-35c50--without-an-unhandled-error-firefox/test-failed-1.png",
|
||||
"sha256": "9d9e3123c4c51b0c3f3b0957fe143e983cb15d68ecb4d5dbb1d6aa2721d95086",
|
||||
"bytes": 106245
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-core-candida-35c50--without-an-unhandled-error-firefox/trace.zip",
|
||||
"sha256": "6ab10ac7dad026bc26a8a0e991bc6045057ded0cb5847218fc1048acc49105cf",
|
||||
"bytes": 790965
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-core-candida-85573--without-an-unhandled-error-firefox/error-context.md",
|
||||
"sha256": "c210842e0e5a80c8e6aecc16cc34d2f18420a3a46ae0d4644231857807879f5e",
|
||||
"bytes": 14681
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-core-candida-85573--without-an-unhandled-error-firefox/test-failed-1.png",
|
||||
"sha256": "d9b2568d99ae3711c5ede23bf093e81088a0bf320599870024270fa1d7400e18",
|
||||
"bytes": 106388
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-lifecycle-results/ads-published-core-candida-85573--without-an-unhandled-error-firefox/trace.zip",
|
||||
"sha256": "3242cd978b05aa458d42aacf40226aa322a3894485ec88addab609bcfb6094dc",
|
||||
"bytes": 473549
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main",
|
||||
"note": "Actual main core/plugin artifacts; three cores and three engines; normal contracts and candidate lifecycle plus published historical observations.",
|
||||
"stats": {
|
||||
"startTime": "2026-09-11T23:45:21.943Z",
|
||||
"duration": 151353.921,
|
||||
"expected": 144,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"report": {
|
||||
"file": "refactor/.cache/ads03-browser-main-report.json",
|
||||
"sha256": "52ac530bc6a2099bcc95de92fea665286d1645e5741967eb48f3e0ce07d12bd4",
|
||||
"bytes": 9432469
|
||||
},
|
||||
"log": {
|
||||
"file": "refactor/.cache/ads03-browser-main.log",
|
||||
"sha256": "bca4fdc2e85fd9614ca230f15ffb7ce884b2277f43145fa7bf2d8e4e4943b539",
|
||||
"bytes": 25043
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-main-results/.last-run.json",
|
||||
"sha256": "91d1c43004802cd49950d78eb11c8fa7d05da8ffffe219a8b13b2f561bc00903",
|
||||
"bytes": 45
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "legacy",
|
||||
"note": "Actual legacy core/plugin artifacts; same complete matrix, no skips or retries.",
|
||||
"stats": {
|
||||
"startTime": "2026-09-11T23:48:36.896Z",
|
||||
"duration": 151422.821,
|
||||
"expected": 144,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"report": {
|
||||
"file": "refactor/.cache/ads03-browser-legacy-report.json",
|
||||
"sha256": "206e4355f9f7108e562c7801919b2ef9534b332f1a15eb1e1dc4dc6f929b3a3e",
|
||||
"bytes": 9435856
|
||||
},
|
||||
"log": {
|
||||
"file": "refactor/.cache/ads03-browser-legacy.log",
|
||||
"sha256": "ab1b0cd3c60880bbf5216027696bc976c7d8e275f7465291a884a969f13104c6",
|
||||
"bytes": 25041
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"file": "refactor/.cache/ads03-browser-legacy-results/.last-run.json",
|
||||
"sha256": "91d1c43004802cd49950d78eb11c8fa7d05da8ffffe219a8b13b2f561bc00903",
|
||||
"bytes": 45
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ci": {
|
||||
"file": "refactor/.cache/ads03-ci.log",
|
||||
"sha256": "1887d58330da102df5ab43934fa7322440f2600ea62878b8ab064a548bd5453f",
|
||||
"bytes": 86881,
|
||||
"status": "passed",
|
||||
"tests": 830,
|
||||
"unit": 770,
|
||||
"engineering": 14,
|
||||
"baseline": 46,
|
||||
"productionTypeScript": 269,
|
||||
"coreDeclarationsUnchanged": 37,
|
||||
"warning": "Existing generated docs/assets/ts/artplayer.d.ts unused eslint-disable; no new lint errors."
|
||||
},
|
||||
"unchanged": [
|
||||
"Public declarations",
|
||||
"Package version/exports",
|
||||
"Style source",
|
||||
"Runtime/development dependencies and lockfile"
|
||||
],
|
||||
"limitations": [
|
||||
"Public declaration reconciliation and old CommonJS default shape pending 04/06.",
|
||||
"Real devices, native fullscreen and genuine hidden-page policy pending 05.",
|
||||
"Existing Ads risk entries stay open until their full closure evidence is complete.",
|
||||
"No push/tag/publish/merge."
|
||||
],
|
||||
"visualReview": {
|
||||
"file": "refactor/.cache/ads03-main-review.png",
|
||||
"source": "Main report: published 4.5.5 core / candidate Ads, Chromium",
|
||||
"observed": "Local decoded pattern, countdown and mute/fullscreen controls visible."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
# PKG-ADS-03:Ads 职责拆分与生命周期修复
|
||||
|
||||
起点 069ad4aa。实际实现地图见包内 [ARCHITECTURE.md](../../packages/artplayer-plugin-ads/ARCHITECTURE.md)。
|
||||
本步已经进入生产源码,公开声明/格式类型入口仍由 04 处理,完整分发和 demo 由 06 接续。
|
||||
|
||||
## 结构改造
|
||||
|
||||
原 index.js 拆为 7 个严格 TS 模块:入口、配置、内部类型、计时、资源、视图和 session。
|
||||
计时模块不依赖核心/DOM;视图仅接收父节点、图标、三个 DOM 工具和回调;session 管理
|
||||
状态转换及媒体请求。entry 中保留旧声明 constructor 边界的局部断言;私有固定 HTML
|
||||
对应的节点种类断言集中在 view,未用 any 或全局索引签名掩盖全部类型问题。
|
||||
|
||||
配置每次 attach 独立归一化;i18n 仍浅合并;emit 的配置仍为同一活对象,修改事件配置
|
||||
中的 totalDuration 会影响后续 tick。移除未经验证的 >=5 数字门槛,使用已有 validator/
|
||||
DOM 工具;DOM 监听自有,兼容原生 visibility 与新核心事件桥,重复恢复只有一个 timer。
|
||||
|
||||
核心 4.5.5 的实际 npm 归档、SHA512/SHA256、完整成员清单和选定源码/主入口指纹记录在
|
||||
[ads-core.json](../baselines/ads-core.json),新增基线测试。浏览器服务器正式映射该
|
||||
历史核心,manifest 记录实际字节来源;没有先加载新核心再覆盖全局变量/样式。
|
||||
|
||||
## 有意修正的缺陷与兼容边界
|
||||
|
||||
1. 重复 play/metadata/ready 不再创建多个计时链或视图;终止和暂停真正取消已排队 tick。
|
||||
2. 跳过幂等;销毁后方法/媒体回调均无效。正常 skip 仍先请求主片播放,再暂停广告、
|
||||
隐藏 DOM、同步 emit 原配置。同步播放/广告暂停回调触发 destroy 时不再继续通知。
|
||||
3. 提前 play/pause 无操作;提前 skip 取消尚未开始的 preroll 并 emit 一次,不创建 DOM
|
||||
或提前自动播放主片。旧路径抛错/留下永不结束遮罩,不作为兼容要求保留。
|
||||
4. 零阈值关闭按钮立即可用;全屏图标按初始状态及外部 fullscreen 事件同步。
|
||||
5. 媒体监听早于 src 赋值,覆盖同步/缓存 metadata;广告 play 失败观察原错误并按
|
||||
媒体失败完成广告。主片恢复失败保持完成状态并记录原错误,外部 art.play 不被改写。
|
||||
6. 销毁独立释放 timer、core/DOM/document 监听、媒体 src 和 overlay,保留调用方
|
||||
新的 template.$ads 值及外部监听。部分构建失败也清理已拥有的节点和订阅。
|
||||
|
||||
正常公共 play/pause 只控制倒计时,未被改成媒体暂停/播放接口。视频优先于 HTML、
|
||||
数值/默认时长、完整 i18n、URL/事件参数、同步返回及全部既有 CSS 类名保持。
|
||||
结束时仍保留隐藏 overlay 与媒体 source/error 状态,到 destroy 才释放媒体。
|
||||
初始隐藏文档暂停计时;真实设备后台策略仍需后续验收。
|
||||
|
||||
## 测试与原始失败
|
||||
|
||||
先增加的 10 个候选缺陷断言在旧源码上全部失败;初次实现受控测试的 Node 节点缺少
|
||||
remove 方法,补足最小 DOM 意图模型,不伪装真实浏览器。之后增加动态事件配置、同步
|
||||
src metadata、Promise 拒绝/晚到、部分构建失败、外部监听与模板所有权、全屏状态等断言。
|
||||
广告暂停时同步 destroy 的额外回归先 19 通过/1 失败,补终止状态检查后通过。
|
||||
|
||||
正式三格式首次 182/186 通过;VM 与主环境 Promise 的跨 realm 同化多于两次 microtask。
|
||||
改成让一个事件循环轮次排空已排队微任务,而非固定两次 Promise.resolve;未替换 VM 的
|
||||
Promise 或修改生产实现来配合测试。最终三格式加源码/历史观察 186 项全部通过。
|
||||
|
||||
首次加入实际 4.5.5 的浏览器运行 66 通过/33 失败:测试夹具误用该核心不存在的 art.video。
|
||||
改为已经存在的 template.$video;生产插件没有该依赖。扩展后 138/144 通过,6 个 Firefox
|
||||
失败为 console 文本没有序列化 DOMException.message;改用透明 console.warn 参数记录,
|
||||
仍调用原方法、严格核对原异常名/消息,不屏蔽其他页面异常。原始报告与 trace 保留。
|
||||
|
||||
正式 main 和 legacy 三引擎分别 144 项全部通过,无跳过或重试,报告中的候选插件哈希
|
||||
逐例匹配本次正式文件;4.5.5 核心来源也核对实际 manifest 指纹。已查看其加载候选 main
|
||||
的视频截图,画面、倒计时、静音/全屏控件均可见。完整本地 CI 830 项通过(770 单元、
|
||||
14 工程、46 基线),269 个生产 TS 严格检查、核心 37 份声明无漂移。既有生成编辑器
|
||||
声明的一条 unused eslint-disable warning 保留,没有新增 lint 错误。
|
||||
最终记录见 [执行证据](../baselines/ads-lifecycle-validation.json)。同步源码/构建产物由
|
||||
仓库 yarn build 生成,未手工编辑 dist/docs compiled。
|
||||
本步不增加依赖,新增 tsconfig 从 npm 包排除,Ads 生命周期测试接入 test:ads/test:unit。
|
||||
计划验证曾拒绝直接指向 refactor 外架构文件的 evidence 项,已改为内部变更记录再链接
|
||||
包内文档;没有修改校验规则。异步测试新增 import 顺序按现有 lint 修正。
|
||||
|
||||
## 后续与回退
|
||||
|
||||
ADS 的生命周期、媒体/UI 风险保留到相应关闭证据齐全;DOM/媒体真实销毁、4.5.5 组合
|
||||
证据也不代表所有历史核心或移动设备已认证。04 需要处理历史 export=、totalDuration
|
||||
错误 string 声明与工作区 source/type 接受面;05 完成实际设备/隐藏页/全屏;06 隔离
|
||||
tarball/旧 default namespace/8082 demo。DASH 等其他开放媒体风险仍有效。
|
||||
|
||||
本地独立提交包含本步生产拆分、生成产物、回归、架构/执行文档与状态;可整体 revert
|
||||
回到 02 冻结实现与测试基线。没有 push、tag、publish 或 merge。
|
||||
@@ -33,7 +33,7 @@ Node10 是 TS 模块解析模式名,不代表支持 Node.js 10。现代 es2020
|
||||
| 包 / 工作区版本 | 能力与固定样本/资源要求 | 当前状态、待补环境 | 负责验证任务 |
|
||||
| --- | --- | --- | --- |
|
||||
| artplayer / 5.4.1 | 原生媒体、字幕、切源、控件/设置、全屏/PiP、SSR;ENV-MEDIA + VTT/SRT | 发布 5.4.0 已冻结;桌面部分基线,移动/原生全屏/PiP/多引擎待测 | CORE-07/16/17/18/22/23、ENG-05 |
|
||||
| artplayer-plugin-ads / npm 1.0.6、工作区 2.1.0 未发布 | 五个 1.0.x 归档已冻结;HTML/视频、本片恢复、跳过、错误/销毁;本地短广告和主片 | 关联历史核心 4.5.5 尚待实际归档/浏览器验证;与 5.4.1/候选核心组合均待。无需借 IMA 结果替代自有逻辑 | PKG-ADS-01/02/05/06 |
|
||||
| artplayer-plugin-ads / npm 1.0.6、工作区 2.1.0 未发布 | 五个 1.0.x 归档及实际核心 4.5.5 归档已冻结;HTML/视频、本片恢复、跳过、错误/销毁;本地短广告和主片 | 4.5.5/5.4.1/候选核心 × 发布/候选插件实际组合见 ads-validation.md;设备/隐藏页/全屏与完整分发仍待,不扩大到全部历史核心。无需借 IMA 结果替代自有逻辑 | PKG-ADS-01/02/03/05/06 |
|
||||
| artplayer-plugin-ambilight / 1.1.0 | Canvas 采样、CORS、resize、后台/销毁;同源及受控跨源 MP4 | 待真实画布和资源测量 | PKG-AMBILIGHT-01/02/05/06 |
|
||||
| artplayer-plugin-asr / 2.1.0 | PCM 音频输出/暂停/销毁;AudioContext + 调用方服务 | 待音频环境与调用方 WebSocket 验证;示例服务版本未知,不在 PR 调线上服务 | PKG-ASR-01/02/05/06、EX-03 |
|
||||
| artplayer-plugin-audio-track / 1.1.0 | 独立外部 Audio 与主视频时钟同步;本地 MP4+AAC,偏移/切源/缓冲/失败样本 | 1.1.0 发布及调用契约见 [包基线](baselines/audio-track-contract.md);旧核心 5.4.0 为具体对照点,桌面/Safari 实测待完成 | PKG-AUDIO-01/02/05/06 |
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 217 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 137 / doing 4 / blocked 0 / done 76 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 136 / doing 4 / blocked 0 / done 77 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -258,7 +258,7 @@
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| PKG-ADS-01 | artplayer-plugin-ads<br>核对包契约与历史用法 | BASE-05 | 核对 npm 1.0.0~1.0.6 与未发布工作区 2.1.0 的真实参数、类型、结果、倒计时、分发和旧核心来源 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
|
||||
| PKG-ADS-02 | artplayer-plugin-ads<br>建立特有行为与错误测试 | PKG-ADS-01, ENG-03, ENG-05 | 固定 npm 1.0.6 与工作区契约;HTML/视频/图片 HTML、跳过阈值、计时链、工厂复用、多实例、过早/重复方法、播放拒绝与销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-ADS-03 | artplayer-plugin-ads<br>整理内部职责与资源 | PKG-ADS-02, CORE-10, CORE-13 | 配置/广告状态/计时/视图/主视频恢复分离,保留产品规则;资源与版本检查改用经验证的旧核心能力边界 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo |
|
||||
| PKG-ADS-03 | artplayer-plugin-ads<br>整理内部职责与资源 | PKG-ADS-02, CORE-10, CORE-13 | 配置/广告状态/计时/视图/主视频恢复分离,保留产品规则;资源与版本检查改用经验证的旧核心能力边界 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
|
||||
| PKG-ADS-04 | artplayer-plugin-ads<br>迁移自有源码和公开类型 | PKG-ADS-03, ENG-04, ENG-06, CORE-07 | 兼容真实 html/video/url/i18n、旧 export= 与工作区 source/type 类型接受面;时长 string/number 漂移与未实现别名单独处置 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
|
||||
| PKG-ADS-05 | artplayer-plugin-ads<br>验证新旧核心和组合 | PKG-ADS-04, CORE-22 | 本地广告 fixture、多实例、正常结束/跳过/销毁;核验历史关联核心 4.5.5、发布 5.4.1 与候选核心的实际归档/能力/媒体组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-ADS-06 | artplayer-plugin-ads<br>验证分发并同步文档 | PKG-ADS-05, ENG-07 | ads.js/README/声明一致;旧 require(pkg).default、原 dist 路径、当前 callable/ESM/legacy 的隔离 tarball 及浏览器验收 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
@@ -486,3 +486,4 @@
|
||||
- PKG-DASH-05: [记录](changes/2026-09-12-PKG-DASH-05-sdk-checkpoint.md) [记录](baselines/dash-sdk.json) [记录](baselines/dash-sdk-checkpoint.json) [记录](dash-validation.md) [记录](changes/2026-09-12-PKG-DASH-05-sdk-types.md) [记录](baselines/dash-sdk-types-validation.json) [记录](changes/2026-09-12-PKG-DASH-05-sdk-events.md) [记录](baselines/dash-sdk-events-validation.json) [记录](changes/2026-09-12-PKG-DASH-05-seek-diagnosis.md) [记录](baselines/dash-seek-diagnosis.json)
|
||||
- PKG-ADS-01: [记录](baselines/ads-release.json) [记录](baselines/ads-contract.md) [记录](baselines/ads-contract-validation.json) [记录](changes/2026-09-12-PKG-ADS-01-contract.md) [记录](scripts/ads-contract.test.mjs)
|
||||
- PKG-ADS-02: [记录](changes/2026-09-12-PKG-ADS-02-tests.md) [记录](ads-validation.md) [记录](baselines/ads-validation.json)
|
||||
- PKG-ADS-03: [记录](changes/2026-09-12-PKG-ADS-03-lifecycle.md) [记录](ads-validation.md) [记录](baselines/ads-lifecycle-validation.json)
|
||||
@@ -1,5 +1,25 @@
|
||||
# 进度与证据
|
||||
|
||||
## 最新完成:Ads-03 源码拆分与生命周期修复
|
||||
|
||||
Ads 原入口拆为 7 个严格 TS 模块,职责为配置、类型、计时、资源、视图、session 和入口。
|
||||
修复重复计时/metadata、工厂复用参数串用、零阈值、过早方法、播放拒绝、销毁/重入后的
|
||||
回调;保留正常 skip 顺序、同步结果、活配置事件参数、CSS 和 play/pause 只控制倒计时。
|
||||
实际模块地图及媒体/监听/定时器所有权写入包内 ARCHITECTURE.md。
|
||||
|
||||
实际核心 4.5.5 归档与实现能力已核验并接入浏览器映射。正式三格式 Node 186 项通过;
|
||||
正式 main/legacy 的三引擎矩阵分别 144 项全部通过,无跳过/重试,覆盖 4.5.5/5.4.1/
|
||||
候选核心、新旧插件、本地真实媒体、提前/重复 skip、销毁资源及内部播放拒绝。
|
||||
完整本地 CI 830 项通过(770 单元、14 工程、46 基线),269 个生产 TS 严格检查,
|
||||
37 份核心声明无漂移。原始缺陷/测试夹具失败、报告、trace 和最终输入哈希均保留。
|
||||
|
||||
当前 217 项:77 done、4 doing、136 todo。下一步 Ads-04 处理准确公开声明、历史
|
||||
export=、string 时长误声明与工作区 source/type 类型接受面;尚未称整个 Ads 发布验收
|
||||
完成。隐藏页/设备/全屏和完整分发仍由 05/06 接续,既有 DASH 等开放风险不变。
|
||||
见 [本步记录](changes/2026-09-12-PKG-ADS-03-lifecycle.md)、
|
||||
[执行证据](baselines/ads-lifecycle-validation.json) 和 [测试维护](ads-validation.md)。
|
||||
以下为历史检查点当时状态。
|
||||
|
||||
## 最新完成:Ads-02 行为与真实媒体回归
|
||||
|
||||
新增 58 项 Node 测试,覆盖源码、实际 npm 1.0.6 和冻结的未发布工作区 2.1.0;
|
||||
|
||||
+29
-9
@@ -2988,7 +2988,7 @@
|
||||
"confirmation": "source-observed",
|
||||
"status": "open",
|
||||
"owners": ["PKG-ADS-02", "PKG-ADS-04"],
|
||||
"evidence": ["refactor/baselines/ads-release.json", "refactor/baselines/ads-contract.md", "packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.ts", "packages/artplayer-plugin-ads/src/index.js"],
|
||||
"evidence": ["refactor/baselines/ads-release.json", "refactor/baselines/ads-contract.md", "packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.ts", "packages/artplayer-plugin-ads/src/index.ts"],
|
||||
"compatibleResolution": "同时保护实际 npm 字段/旧声明消费者和当前类型接受面;真实数字时长、i18n 与 source/type 别名的行为差异单独登记,不伪称旧实现支持别名。",
|
||||
"closureCriteria": "两套旧消费者、可选工厂、合法运行字段和错误参数有严格正反例,声明与真实方法一致。"
|
||||
},
|
||||
@@ -3007,8 +3007,19 @@
|
||||
"title": "Ads 旧发布关联核心 4.5.5,当前源码新增 >=5 门槛和 document 事件桥依赖",
|
||||
"confirmation": "source-observed",
|
||||
"status": "open",
|
||||
"owners": ["PKG-ADS-02", "PKG-ADS-03", "PKG-ADS-05"],
|
||||
"evidence": ["refactor/baselines/ads-release.json", "refactor/baselines/ads-contract.md", "packages/artplayer-plugin-ads/src/index.js"],
|
||||
"owners": [
|
||||
"PKG-ADS-02",
|
||||
"PKG-ADS-03",
|
||||
"PKG-ADS-05"
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/baselines/ads-release.json",
|
||||
"refactor/baselines/ads-contract.md",
|
||||
"packages/artplayer-plugin-ads/src/index.ts",
|
||||
"refactor/baselines/ads-lifecycle-validation.json",
|
||||
"refactor/changes/2026-09-12-PKG-ADS-03-lifecycle.md",
|
||||
"packages/artplayer-plugin-ads/ARCHITECTURE.md"
|
||||
],
|
||||
"compatibleResolution": "先核验实际核心归档和能力,通过本地 fallback 保留原用户调用;不能用未发布源码门槛强制全部旧消费者升级,也不泛称全部 4.x 通过。",
|
||||
"closureCriteria": "4.5.5 关联来源和实际可用组合明确,5.4.1/候选核心与原生 visibility/事件桥路径有真实浏览器证据。"
|
||||
},
|
||||
@@ -3024,11 +3035,14 @@
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/baselines/ads-contract.md",
|
||||
"packages/artplayer-plugin-ads/src/index.js",
|
||||
"packages/artplayer-plugin-ads/src/index.ts",
|
||||
"test/ads.test.js",
|
||||
"test/browser/ads.spec.js",
|
||||
"refactor/ads-validation.md",
|
||||
"refactor/baselines/ads-validation.json"
|
||||
"refactor/baselines/ads-validation.json",
|
||||
"refactor/baselines/ads-lifecycle-validation.json",
|
||||
"refactor/changes/2026-09-12-PKG-ADS-03-lifecycle.md",
|
||||
"packages/artplayer-plugin-ads/ARCHITECTURE.md"
|
||||
],
|
||||
"compatibleResolution": "先复现重复 play 的计时链、skip 后计时、destroy 后恢复与复用工厂 options;以实例所有权和幂等清理修正,不更改广告时长/跳过规则。",
|
||||
"closureCriteria": "时钟/订阅/DOM 与主片恢复的成功、异常、重入、销毁、复用和多实例回归通过。"
|
||||
@@ -3045,12 +3059,15 @@
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/baselines/ads-contract.md",
|
||||
"packages/artplayer-plugin-ads/src/index.js",
|
||||
"packages/artplayer-plugin-ads/src/index.ts",
|
||||
"docs/assets/example/ads.js",
|
||||
"test/ads.test.js",
|
||||
"test/browser/ads.spec.js",
|
||||
"refactor/ads-validation.md",
|
||||
"refactor/baselines/ads-validation.json"
|
||||
"refactor/baselines/ads-validation.json",
|
||||
"refactor/baselines/ads-lifecycle-validation.json",
|
||||
"refactor/changes/2026-09-12-PKG-ADS-03-lifecycle.md",
|
||||
"packages/artplayer-plugin-ads/ARCHITECTURE.md"
|
||||
],
|
||||
"compatibleResolution": "用本地媒体验证事件安装顺序、加载失败与拒绝、倒计时和广告播放的独立语义,恢复主片时不吞公共拒绝或在销毁后播放。",
|
||||
"closureCriteria": "实际广告/主片的加载、暂停/恢复、失败/换源/销毁和隐藏状态有新旧核心组合证据。"
|
||||
@@ -3067,12 +3084,15 @@
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/baselines/ads-contract.md",
|
||||
"packages/artplayer-plugin-ads/src/index.js",
|
||||
"packages/artplayer-plugin-ads/src/index.ts",
|
||||
"docs/assets/example/ads.js",
|
||||
"test/ads.test.js",
|
||||
"test/browser/ads.spec.js",
|
||||
"refactor/ads-validation.md",
|
||||
"refactor/baselines/ads-validation.json"
|
||||
"refactor/baselines/ads-validation.json",
|
||||
"refactor/baselines/ads-lifecycle-validation.json",
|
||||
"refactor/changes/2026-09-12-PKG-ADS-03-lifecycle.md",
|
||||
"packages/artplayer-plugin-ads/ARCHITECTURE.md"
|
||||
],
|
||||
"compatibleResolution": "复现 playDuration<=0 文案可关闭但 isCanClose 尚为 false;按既有 demo 规则修复,验证全屏按钮和外部退出状态,保留 CSS/DOM 钩子。",
|
||||
"closureCriteria": "零/正/不可跳阈值、真实点击、倒计时/详情/静音/全屏与外部状态组合均有回归。"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import test from 'node:test'
|
||||
import { archiveFiles, ensureArchive, hash, readMember } from './releases.mjs'
|
||||
|
||||
test('Ads historical core archive and selected implementation members remain verified', async () => {
|
||||
const baseline = JSON.parse(fs.readFileSync(new URL('../baselines/ads-core.json', import.meta.url)))
|
||||
const ads = JSON.parse(fs.readFileSync(new URL('../baselines/ads-release.json', import.meta.url)))
|
||||
assert.equal(baseline.release.name, 'artplayer')
|
||||
assert.equal(baseline.release.version, ads.historicalCore.version)
|
||||
const archive = await ensureArchive(baseline.release)
|
||||
assert.deepEqual(archiveFiles(archive), baseline.archiveMembers)
|
||||
for (const [member, expected] of Object.entries(baseline.release.files))
|
||||
assert.equal(hash(readMember(archive, member)), expected)
|
||||
assert.deepEqual(JSON.parse(readMember(archive, 'package/package.json')), baseline.release.manifest)
|
||||
assert(Object.hasOwn(baseline.release.files, `package/${baseline.release.manifest.main}`))
|
||||
})
|
||||
+6
-2
@@ -2567,11 +2567,15 @@
|
||||
"CORE-10",
|
||||
"CORE-13"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "配置/广告状态/计时/视图/主视频恢复分离,保留产品规则;资源与版本检查改用经验证的旧核心能力边界",
|
||||
"acceptance": "结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-12-PKG-ADS-03-lifecycle.md",
|
||||
"ads-validation.md",
|
||||
"baselines/ads-lifecycle-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-ADS-04",
|
||||
|
||||
@@ -80,6 +80,11 @@ allowJs=true、checkJs=false 允许渐进迁移时引用旧 JS;只将迁移的
|
||||
|
||||
## 后续接入
|
||||
|
||||
Ads 的 7 个自有 TS 模块由 packages/artplayer-plugin-ads/tsconfig.json 严格检查,
|
||||
无生产 JS 遗留。03 的内部 Input/Options 与旧公开声明暂时分开;公开声明仍有已登记
|
||||
的历史输入/类型漂移,由 PKG-ADS-04 建立精确消费者后统一,不能把源码严格检查称为
|
||||
旧/新发布类型矩阵完成。
|
||||
|
||||
Audio Track 的自有源码由 packages/artplayer-plugin-audio-track/tsconfig.json 严格检查。
|
||||
默认/legacy 类型保留历史 update 的完整 Option;新增 /runtime 复用同一运行文件,
|
||||
提供 UpdateOption/RuntimeResult。test/types/audio-track.ts 同时验证旧 Parameters、
|
||||
|
||||
@@ -7,6 +7,9 @@ Ads bundle against controlled clock/host contracts. It is included in `test:unit
|
||||
The helper uses the real option validator but does not simulate media decoding or layout.
|
||||
Historical defect assertions apply only to frozen implementations. See
|
||||
[Ads validation](../refactor/ads-validation.md) for artifact overrides and browser scope.
|
||||
`ads-lifecycle.test.js` adds candidate-only resource, reentry, early-method and Promise
|
||||
regressions. Async observations drain one event-loop turn, including VM Promise assimilation,
|
||||
rather than assuming a fixed number of microtasks. The published defect observations stay intact.
|
||||
|
||||
| Command | Scope |
|
||||
| ------------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { getEventListeners } from 'node:events'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Controlled lifecycle contracts use Node's runner.
|
||||
import test from 'node:test'
|
||||
import { setImmediate as settle } from 'node:timers/promises'
|
||||
import { adsEnvironment, adsImplementations } from './helpers/ads.js'
|
||||
|
||||
const implementations = (await adsImplementations()).filter(item => !item.historical)
|
||||
const skips = host => host.calls.filter(call => call[0] === 'emit' && call[1] === 'artplayerPluginAds:skip')
|
||||
const contentPlays = host => host.calls.filter(call => call[0] === 'content.play')
|
||||
|
||||
for (const { name, factory } of implementations) {
|
||||
test(`${name}: repeated play owns one timer and pause clears all countdown work`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const plugin = factory({ html: 'ad', totalDuration: 3 })(host.art)
|
||||
host.start()
|
||||
plugin.play()
|
||||
plugin.play()
|
||||
assert.equal(env.timers.size, 1)
|
||||
env.tick(1000)
|
||||
assert.equal(host.node('countdown').innerHTML, '2秒')
|
||||
plugin.pause()
|
||||
assert.equal(env.timers.size, 0)
|
||||
})
|
||||
|
||||
test(`${name}: skip clears timers and becomes inert under repeated methods/events`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const plugin = factory({ html: 'ad', totalDuration: 1 })(host.art)
|
||||
host.start()
|
||||
plugin.skip()
|
||||
plugin.skip()
|
||||
plugin.play()
|
||||
host.visibility(false)
|
||||
env.tick(5000)
|
||||
assert.equal(env.timers.size, 0)
|
||||
assert.equal(skips(host).length, 1)
|
||||
assert.equal(contentPlays(host).length, 1)
|
||||
})
|
||||
|
||||
test(`${name}: destroy cancels countdown and future public calls never resume content`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const plugin = factory({ html: 'ad', totalDuration: 1 })(host.art)
|
||||
host.start()
|
||||
host.art.destroy()
|
||||
plugin.play()
|
||||
plugin.skip()
|
||||
env.tick(5000)
|
||||
assert.equal(env.timers.size, 0)
|
||||
assert.equal(contentPlays(host).length, 0)
|
||||
assert.equal(skips(host).length, 0)
|
||||
})
|
||||
|
||||
test(`${name}: one factory retains per-instance normalized event payloads`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const first = env.host()
|
||||
const second = env.host()
|
||||
const attach = factory({ html: 'shared' })
|
||||
const one = attach(first.art)
|
||||
first.start()
|
||||
attach(second.art)
|
||||
second.start()
|
||||
one.skip()
|
||||
assert.equal(skips(first)[0][2], first.normalized)
|
||||
assert.notEqual(skips(first)[0][2], second.normalized)
|
||||
})
|
||||
|
||||
test(`${name}: zero threshold close works before the first timer tick`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
factory({ html: 'ad', playDuration: 0 })(host.art)
|
||||
host.start()
|
||||
host.fire('close', 'click')
|
||||
assert.equal(skips(host).length, 1)
|
||||
assert.equal(env.timers.size, 0)
|
||||
})
|
||||
|
||||
test(`${name}: play before initialization is inert and skip cancels pending preroll`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const plugin = factory({ html: 'ad' })(host.art)
|
||||
plugin.play()
|
||||
assert.equal(env.timers.size, 0)
|
||||
plugin.skip()
|
||||
host.start()
|
||||
assert.equal(host.node(), undefined)
|
||||
assert.equal(skips(host).length, 1)
|
||||
assert.equal(contentPlays(host).length, 0)
|
||||
})
|
||||
|
||||
test(`${name}: duplicate metadata starts the ad once`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
factory({ video: 'ad.mp4' })(host.art)
|
||||
host.start()
|
||||
host.fire('video', 'loadedmetadata')
|
||||
host.fire('video', 'loadedmetadata')
|
||||
assert.equal(env.timers.size, 1)
|
||||
assert.equal(host.calls.filter(call => call[0] === 'ad.play').length, 1)
|
||||
})
|
||||
|
||||
test(`${name}: core version numbers do not reject hosts providing the required capabilities`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
host.art.constructor.version = '4.5.5'
|
||||
factory({ html: 'ad', totalDuration: 2 })(host.art)
|
||||
host.start()
|
||||
env.tick(1000)
|
||||
host.visibility(true)
|
||||
env.tick(5000)
|
||||
assert.equal(host.node('countdown').innerHTML, '1秒')
|
||||
host.visibility(false)
|
||||
env.tick(1000)
|
||||
assert.equal(skips(host).length, 1)
|
||||
})
|
||||
|
||||
test(`${name}: synchronous content-play failure still hides ad and completes exactly once`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const warning = []
|
||||
t.mock.method(console, 'warn', (...args) => warning.push(args))
|
||||
const plugin = factory({ html: 'ad' })(host.art)
|
||||
host.start()
|
||||
const error = new Error('Content play failed')
|
||||
host.art.play = () => {
|
||||
throw error
|
||||
}
|
||||
assert.doesNotThrow(() => plugin.skip())
|
||||
assert.equal(host.node().style.display, 'none')
|
||||
assert.equal(skips(host).length, 1)
|
||||
assert(warning.some(args => args.includes(error)))
|
||||
})
|
||||
|
||||
test(`${name}: destroy during content restoration suppresses subsequent skip dispatch`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const plugin = factory({ html: 'ad' })(host.art)
|
||||
host.start()
|
||||
host.art.on('play', () => host.art.destroy())
|
||||
plugin.skip()
|
||||
assert.equal(skips(host).length, 0)
|
||||
assert.equal(env.timers.size, 0)
|
||||
})
|
||||
|
||||
test(`${name}: metadata emitted by the src setter is observed after listeners are installed`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const append = host.art.constructor.utils.append
|
||||
host.art.constructor.utils.append = (...args) => {
|
||||
const node = append(...args)
|
||||
if (node.tagName === 'VIDEO') {
|
||||
let source
|
||||
Object.defineProperty(node, 'src', {
|
||||
configurable: true,
|
||||
get: () => source,
|
||||
set(value) {
|
||||
source = value
|
||||
node.dispatchEvent(new Event('loadedmetadata'))
|
||||
},
|
||||
})
|
||||
}
|
||||
return node
|
||||
}
|
||||
factory({ video: 'cached.mp4' })(host.art)
|
||||
host.start()
|
||||
assert.equal(env.timers.size, 1)
|
||||
assert.equal(host.calls.filter(call => call[0] === 'ad.play').length, 1)
|
||||
})
|
||||
|
||||
test(`${name}: rejected ad play restores content once and observes the original error`, async (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const warnings = []
|
||||
t.mock.method(console, 'warn', (...args) => warnings.push(args))
|
||||
factory({ video: 'ad.mp4' })(host.art)
|
||||
host.start()
|
||||
const error = new Error('Ad play rejected')
|
||||
host.node('video').play = () => Promise.reject(error)
|
||||
host.fire('video', 'loadedmetadata')
|
||||
await settle()
|
||||
assert.equal(skips(host).length, 1)
|
||||
assert.equal(contentPlays(host).length, 1)
|
||||
assert.equal(env.timers.size, 0)
|
||||
assert(warnings.some(args => args.includes(error)))
|
||||
})
|
||||
|
||||
test(`${name}: late ad play fulfillment after destroy pauses owned media without restoring content`, async (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
factory({ video: 'ad.mp4' })(host.art)
|
||||
host.start()
|
||||
let resolve
|
||||
const video = host.node('video')
|
||||
video.play = () => new Promise((done) => {
|
||||
resolve = done
|
||||
})
|
||||
host.fire('video', 'loadedmetadata')
|
||||
host.art.destroy()
|
||||
const pauses = host.calls.filter(call => call[0] === 'ad.pause').length
|
||||
video.paused = false
|
||||
resolve()
|
||||
await settle()
|
||||
assert.equal(video.paused, true)
|
||||
assert.equal(host.calls.filter(call => call[0] === 'ad.pause').length, pauses + 1)
|
||||
assert.equal(contentPlays(host).length, 0)
|
||||
assert.equal(video.src, undefined)
|
||||
assert.equal(host.art.template.$ads, undefined)
|
||||
})
|
||||
|
||||
test(`${name}: destroy releases own DOM/document listeners and leaves foreign listeners intact`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
factory({ video: 'ad.mp4' })(host.art)
|
||||
host.start()
|
||||
const video = host.node('video')
|
||||
const foreign = () => {}
|
||||
video.addEventListener('loadedmetadata', foreign)
|
||||
assert.equal(getEventListeners(video, 'loadedmetadata').length, 2)
|
||||
assert.equal(getEventListeners(env.document, 'visibilitychange').length, 1)
|
||||
host.art.destroy()
|
||||
assert.deepEqual(getEventListeners(video, 'loadedmetadata'), [foreign])
|
||||
assert.equal(getEventListeners(video, 'error').length, 0)
|
||||
assert.equal(getEventListeners(env.document, 'visibilitychange').length, 0)
|
||||
assert.equal(host.node().parentNode, null)
|
||||
assert.equal(host.art.template.$ads, undefined)
|
||||
host.fire('video', 'loadedmetadata')
|
||||
assert.equal(env.timers.size, 0)
|
||||
})
|
||||
|
||||
test(`${name}: partial view failure removes owned root and subscriptions before propagating`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const append = host.art.constructor.utils.append
|
||||
const error = new Error('View failed')
|
||||
host.art.constructor.utils.append = (...args) => {
|
||||
if (host.node('loading'))
|
||||
throw error
|
||||
return append(...args)
|
||||
}
|
||||
factory({ html: 'ad' })(host.art)
|
||||
assert.throws(() => host.start(), candidate => candidate === error)
|
||||
assert.equal(host.node().parentNode, null)
|
||||
assert.equal(host.art.template.$ads, undefined)
|
||||
assert.equal([...host.listeners.values()].flatMap(set => [...set]).length, 0)
|
||||
assert.equal(env.timers.size, 0)
|
||||
})
|
||||
|
||||
test(`${name}: synchronous destroy during root creation leaves no newly appended overlay`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const append = host.art.constructor.utils.append
|
||||
host.art.constructor.utils.append = (...args) => {
|
||||
const node = append(...args)
|
||||
if (node.className === 'artplayer-plugin-ads')
|
||||
host.art.destroy()
|
||||
return node
|
||||
}
|
||||
factory({ html: 'ad' })(host.art)
|
||||
host.start()
|
||||
assert.equal(host.node().parentNode, null)
|
||||
assert.equal(host.art.template.$ads, undefined)
|
||||
assert.equal(env.timers.size, 0)
|
||||
assert.equal(host.calls.filter(call => call[0] === 'content.pause').length, 0)
|
||||
})
|
||||
|
||||
test(`${name}: fullscreen icons follow initial and externally changed state`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
host.art.fullscreen = true
|
||||
factory({ html: 'ad' })(host.art)
|
||||
host.start()
|
||||
assert.equal(host.nodes.get('fullscreenOn').style.display, 'none')
|
||||
assert.equal(host.nodes.get('fullscreenOff').style.display, 'inline-flex')
|
||||
host.art.fullscreen = false
|
||||
host.art.emit('fullscreen', false)
|
||||
assert.equal(host.nodes.get('fullscreenOn').style.display, 'inline-flex')
|
||||
assert.equal(host.nodes.get('fullscreenOff').style.display, 'none')
|
||||
})
|
||||
|
||||
test(`${name}: destroy during ad pause prevents late UI writes and skip notification`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const plugin = factory({ video: 'ad.mp4' })(host.art)
|
||||
host.start()
|
||||
const video = host.node('video')
|
||||
let once = false
|
||||
video.pause = () => {
|
||||
if (!once) {
|
||||
once = true
|
||||
host.art.destroy()
|
||||
}
|
||||
}
|
||||
plugin.skip()
|
||||
assert.equal(skips(host).length, 0)
|
||||
assert.equal(host.node().style.display, undefined)
|
||||
})
|
||||
|
||||
test(`${name}: destroy preserves a caller replacement of template.$ads`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
factory({ html: 'ad' })(host.art)
|
||||
host.start()
|
||||
const foreign = {}
|
||||
host.art.template.$ads = foreign
|
||||
host.art.destroy()
|
||||
assert.equal(host.art.template.$ads, foreign)
|
||||
assert.equal(host.node().parentNode, null)
|
||||
})
|
||||
|
||||
test(`${name}: rejected play after destroy is observed without restoring or reporting cancellation`, async (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
const warnings = []
|
||||
t.mock.method(console, 'warn', (...args) => warnings.push(args))
|
||||
factory({ video: 'ad.mp4' })(host.art)
|
||||
host.start()
|
||||
let reject
|
||||
host.node('video').play = () => new Promise((_, fail) => {
|
||||
reject = fail
|
||||
})
|
||||
host.fire('video', 'loadedmetadata')
|
||||
host.art.destroy()
|
||||
reject(new Error('Cancelled by destruction'))
|
||||
await settle()
|
||||
assert.equal(warnings.length, 0)
|
||||
assert.equal(contentPlays(host).length, 0)
|
||||
assert.equal(skips(host).length, 0)
|
||||
})
|
||||
}
|
||||
@@ -252,6 +252,20 @@ for (const { name, factory } of implementations) {
|
||||
assert.equal(skips(host).length, 1)
|
||||
assert.equal(env.timers.size, 0)
|
||||
})
|
||||
|
||||
test(`${name}: normalized event options remain live for subsequent countdown ticks`, (t) => {
|
||||
const env = adsEnvironment(t)
|
||||
const host = env.host()
|
||||
factory({ html: 'ad', totalDuration: 10 })(host.art)
|
||||
host.art.on('artplayerPluginAds:click', (option) => {
|
||||
option.totalDuration = 2
|
||||
})
|
||||
host.start()
|
||||
host.fire('html', 'click')
|
||||
env.tick(2000)
|
||||
assert.equal(skips(host).length, 1)
|
||||
assert.equal(host.node('countdown').innerHTML, '0秒')
|
||||
})
|
||||
}
|
||||
|
||||
// Freeze legacy defects separately; migrated candidates must not inherit these assertions.
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
# Browser regression entry
|
||||
|
||||
`yarn test:browser test/browser/ads.spec.js` tests actual Ads 1.0.6 and candidate code
|
||||
with published 5.4.1 and candidate cores using local image/video media. It covers actual
|
||||
with published 4.5.5/5.4.1 and candidate cores using local image/video media. It covers actual
|
||||
decoded pixels, countdown, skipping, content restoration, 404 and source replacement.
|
||||
Published-only rejection cases inject an exact play failure; they do not certify device
|
||||
autoplay policies. See [Ads validation](../../refactor/ads-validation.md).
|
||||
Candidate lifecycle cases check early/duplicate skip, real media release after destroy and
|
||||
observed internal play rejections. The server exposes the verified 4.5.5 bundle separately
|
||||
and records its fingerprint; the fixture uses the shared template.$video surface.
|
||||
|
||||
Audio Track tests use the source and frozen 1.1.0 plugin on both cores. The local AAC fixture
|
||||
has a pinned hash and generation command in media/audio-tone.json and uses the existing Range
|
||||
|
||||
@@ -26,8 +26,9 @@ test.afterEach(async ({ page }, testInfo) => {
|
||||
return {
|
||||
events: window.adsEvents,
|
||||
rejected: window.adsRejected,
|
||||
warnings: window.adsWarnings,
|
||||
ad: ad && { time: ad.currentTime, paused: ad.paused, width: ad.videoWidth, height: ad.videoHeight, error: ad.error?.code },
|
||||
content: window.art && { time: window.art.currentTime, paused: window.art.video.paused, destroyed: window.art.isDestroy },
|
||||
content: window.art && { time: window.art.currentTime, paused: window.art.template.$video.paused, destroyed: window.art.isDestroy },
|
||||
}
|
||||
})
|
||||
await testInfo.attach('ads-state', { contentType: 'application/json', body: JSON.stringify(state) })
|
||||
@@ -40,7 +41,7 @@ test.afterEach(async ({ page }, testInfo) => {
|
||||
})
|
||||
})
|
||||
|
||||
async function openAds(page, core, plugin, option, testInfo) {
|
||||
async function openAds(page, core, plugin, option, testInfo, start = true) {
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
const code = plugin === 'published' ? published : candidate
|
||||
await page.addScriptTag({ content: code })
|
||||
@@ -59,10 +60,96 @@ async function openAds(page, core, plugin, option, testInfo) {
|
||||
}, option)
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await expect(page.locator('.artplayer-plugin-ads')).toHaveCount(0)
|
||||
await page.locator('#play').click()
|
||||
if (start)
|
||||
await page.locator('#play').click()
|
||||
}
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const core of ['published-4.5.5', 'published', 'candidate']) {
|
||||
test(`${core} core / candidate Ads: skip before play suppresses preroll without autoplay`, async ({ page }, testInfo) => {
|
||||
await openAds(page, core, 'candidate', { html: 'pending ad' }, testInfo, false)
|
||||
await page.evaluate(() => window.art.plugins.artplayerPluginAds.skip())
|
||||
expect(await page.evaluate(() => window.art.template.$video.paused)).toBe(true)
|
||||
expect(await page.evaluate(() => window.adsEvents.filter(event => event.name === 'skip').length)).toBe(1)
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.3)
|
||||
await expect(page.locator('.artplayer-plugin-ads')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test(`${core} core / candidate Ads: immediate zero-threshold skip completes only once`, async ({ page }, testInfo) => {
|
||||
await openAds(page, core, 'candidate', { html: 'ad', playDuration: 0, totalDuration: 1 }, testInfo)
|
||||
await page.locator('.artplayer-plugin-ads-close').click()
|
||||
await expect(page.locator('.artplayer-plugin-ads')).toBeHidden()
|
||||
await page.evaluate(() => {
|
||||
window.art.plugins.artplayerPluginAds.skip()
|
||||
window.art.plugins.artplayerPluginAds.play()
|
||||
})
|
||||
await page.waitForTimeout(1200)
|
||||
expect(await page.evaluate(() => window.adsEvents.filter(event => event.name === 'skip').length)).toBe(1)
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.3)
|
||||
})
|
||||
|
||||
test(`${core} core / candidate Ads: destroy releases actual ad media and prevents delayed playback`, async ({ page }, testInfo) => {
|
||||
await page.addInitScript(() => {
|
||||
window.adsPlayCalls = []
|
||||
const play = HTMLMediaElement.prototype.play
|
||||
HTMLMediaElement.prototype.play = function () {
|
||||
window.adsPlayCalls.push(this.className)
|
||||
return play.call(this)
|
||||
}
|
||||
})
|
||||
await openAds(page, core, 'candidate', { video: '/test/pattern.mp4', muted: true, totalDuration: 2 }, testInfo)
|
||||
await expect.poll(() => page.locator('.artplayer-plugin-ads-video').evaluate(video => video.currentTime)).toBeGreaterThan(0.2)
|
||||
const before = await page.evaluate(() => {
|
||||
window.retainedAd = document.querySelector('.artplayer-plugin-ads-video')
|
||||
window.art.destroy(false)
|
||||
window.retainedAd.dispatchEvent(new Event('loadedmetadata'))
|
||||
window.retainedAd.dispatchEvent(new Event('error'))
|
||||
return window.adsPlayCalls.length
|
||||
})
|
||||
await expect(page.locator('.artplayer-plugin-ads')).toHaveCount(0)
|
||||
await page.waitForTimeout(2200)
|
||||
expect(await page.evaluate(() => ({
|
||||
paused: window.retainedAd.paused,
|
||||
src: window.retainedAd.getAttribute('src'),
|
||||
calls: window.adsPlayCalls.length,
|
||||
skips: window.adsEvents.filter(event => event.name === 'skip').length,
|
||||
hasTemplate: Boolean(window.art.template.$ads),
|
||||
}))).toEqual({ paused: true, src: null, calls: before, skips: 0, hasTemplate: false })
|
||||
})
|
||||
|
||||
for (const target of ['ad', 'content']) {
|
||||
test(`${core} core / candidate Ads: observes ${target} play rejection without an unhandled error`, async ({ page }, testInfo) => {
|
||||
await page.addInitScript((target) => {
|
||||
window.adsWarnings = []
|
||||
const warn = console.warn
|
||||
console.warn = function (...args) {
|
||||
window.adsWarnings.push(args.map(value => value?.message ? { name: value.name, message: value.message } : value))
|
||||
return warn.apply(this, args)
|
||||
}
|
||||
const play = HTMLMediaElement.prototype.play
|
||||
HTMLMediaElement.prototype.play = function () {
|
||||
const selected = target === 'ad'
|
||||
? this.classList.contains('artplayer-plugin-ads-video')
|
||||
: this === window.art?.template.$video && document.querySelector('.artplayer-plugin-ads')
|
||||
if (selected)
|
||||
return Promise.reject(new DOMException(`Ads candidate ${target} rejection`, 'NotAllowedError'))
|
||||
return play.call(this)
|
||||
}
|
||||
}, target)
|
||||
const option = target === 'ad' ? { video: '/test/pattern.mp4', muted: true, totalDuration: 10 } : { html: 'ad', totalDuration: 1 }
|
||||
await openAds(page, core, 'candidate', option, testInfo)
|
||||
await expect.poll(() => page.evaluate(() => window.adsEvents.filter(event => event.name === 'skip').length)).toBe(1)
|
||||
await expect(page.locator('.artplayer-plugin-ads')).toBeHidden()
|
||||
await expect.poll(() => page.evaluate(() => window.adsWarnings)).toEqual([
|
||||
['Artplayer Ads:', { name: 'NotAllowedError', message: `Ads candidate ${target} rejection` }],
|
||||
])
|
||||
if (target === 'ad')
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.3)
|
||||
else
|
||||
expect(await page.evaluate(() => window.art.template.$video.paused)).toBe(true)
|
||||
})
|
||||
}
|
||||
|
||||
for (const plugin of ['published', 'candidate']) {
|
||||
const label = `${core} core / ${plugin} Ads`
|
||||
|
||||
@@ -71,7 +158,7 @@ for (const core of ['published', 'candidate']) {
|
||||
const root = page.locator('.artplayer-plugin-ads')
|
||||
await expect(root).toBeVisible()
|
||||
await expect.poll(() => page.locator('img[alt="Ad artwork"]').evaluate(image => image.complete && image.naturalWidth > 0)).toBe(true)
|
||||
expect(await page.evaluate(() => window.art.video.paused)).toBe(true)
|
||||
expect(await page.evaluate(() => window.art.template.$video.paused)).toBe(true)
|
||||
await page.locator('.artplayer-plugin-ads-close').click()
|
||||
expect(await page.evaluate(() => window.adsEvents.filter(event => event.name === 'skip').length)).toBe(0)
|
||||
await expect(page.locator('.artplayer-plugin-ads-close')).toHaveText('关闭广告')
|
||||
@@ -183,7 +270,7 @@ for (const core of ['published', 'candidate']) {
|
||||
})
|
||||
const play = HTMLMediaElement.prototype.play
|
||||
HTMLMediaElement.prototype.play = function () {
|
||||
if (this === window.art?.video && document.querySelector('.artplayer-plugin-ads'))
|
||||
if (this === window.art?.template.$video && document.querySelector('.artplayer-plugin-ads'))
|
||||
return Promise.reject(new DOMException('Ads test controlled content rejection', 'NotAllowedError'))
|
||||
return play.call(this)
|
||||
}
|
||||
@@ -191,7 +278,7 @@ for (const core of ['published', 'candidate']) {
|
||||
await openAds(page, core, 'published', { html: 'ad', totalDuration: 1 }, testInfo)
|
||||
await expect.poll(() => page.evaluate(() => window.adsRejected)).toEqual([{ name: 'NotAllowedError', message: 'Ads test controlled content rejection' }])
|
||||
await expect(page.locator('.artplayer-plugin-ads')).toBeHidden()
|
||||
expect(await page.evaluate(() => window.art.video.paused)).toBe(true)
|
||||
expect(await page.evaluate(() => window.art.template.$video.paused)).toBe(true)
|
||||
expect(await page.evaluate(() => window.adsEvents.filter(event => event.name === 'skip').length)).toBe(1)
|
||||
})
|
||||
|
||||
|
||||
@@ -59,6 +59,11 @@ for (const extension of ['mp4', 'webm']) {
|
||||
const name = `test/browser/media/pattern.${extension}`
|
||||
add(`/test/pattern.${extension}`, fs.readFileSync(path.join(workspace, name)), { kind: 'generated-media', file: name })
|
||||
}
|
||||
const adsCore = JSON.parse(fs.readFileSync(path.join(workspace, 'refactor/baselines/ads-core.json'), 'utf8')).release
|
||||
const adsCoreMember = `package/${adsCore.manifest.main}`
|
||||
const adsCoreBytes = readMember(await ensureArchive(adsCore), adsCoreMember)
|
||||
assert.equal(hash(adsCoreBytes), adsCore.files[adsCoreMember], 'Historical Ads core differs from frozen release')
|
||||
add('/published-4.5.5/artplayer.js', adsCoreBytes, { kind: 'npm-release', version: adsCore.version, integrity: adsCore.integrity, member: adsCoreMember })
|
||||
add('/test/audio-tone.m4a', fs.readFileSync(path.join(workspace, 'test/browser/media/audio-tone.m4a')), { kind: 'generated-media', file: 'test/browser/media/audio-tone.m4a' })
|
||||
|
||||
const mime = { '.html': 'text/html', '.js': 'text/javascript', '.mjs': 'text/javascript', '.json': 'application/json', '.css': 'text/css', '.mp4': 'video/mp4', '.m4a': 'audio/mp4', '.webm': 'video/webm', '.vtt': 'text/vtt', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg' }
|
||||
@@ -123,7 +128,7 @@ const server = http.createServer((req, res) => {
|
||||
if (url.pathname === '/test/player.html') {
|
||||
const core = url.searchParams.get('core') || 'candidate'
|
||||
const chapter = url.searchParams.get('chapter') || 'candidate'
|
||||
assert(['candidate', 'published'].includes(core) && ['candidate', 'published'].includes(chapter), 'Invalid test combination')
|
||||
assert(['candidate', 'published', 'published-4.5.5'].includes(core) && ['candidate', 'published'].includes(chapter), 'Invalid test combination')
|
||||
const html = fs.readFileSync(path.join(workspace, 'test/browser/player.html'), 'utf8')
|
||||
.replace('__CORE__', core)
|
||||
.replace('__CHAPTER__', chapter)
|
||||
|
||||
@@ -109,11 +109,28 @@ export function adsEnvironment(t) {
|
||||
calls.push(['ad.pause'])
|
||||
this.paused = true
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (this.parentNode)
|
||||
this.parentNode.children = this.parentNode.children.filter(child => child !== this)
|
||||
this.parentNode = null
|
||||
calls.push(['remove', this.className])
|
||||
}
|
||||
|
||||
removeAttribute(name) {
|
||||
calls.push(['removeAttribute', this.className, name])
|
||||
delete this[name]
|
||||
}
|
||||
|
||||
load() {
|
||||
calls.push(['ad.load'])
|
||||
}
|
||||
}
|
||||
const prefix = 'artplayer-plugin-ads'
|
||||
function append(parent, value) {
|
||||
if (typeof value !== 'string') {
|
||||
parent.children.push(value)
|
||||
value.parentNode = parent
|
||||
return value
|
||||
}
|
||||
const match = value.match(/^<(\w+)\s[^>]*class="([^"]*)"[^>]*>([\s\S]*)<\/\w+>$/)
|
||||
@@ -121,6 +138,7 @@ export function adsEnvironment(t) {
|
||||
const [, tag, className, inner] = match
|
||||
const node = new Node(tag, className, inner)
|
||||
parent.children.push(node)
|
||||
node.parentNode = parent
|
||||
for (const child of inner.matchAll(/<div class="([^"]+)"[^>]*>([\s\S]*?)<\/div>/g))
|
||||
node.children.push(new Node('div', child[1], child[2]))
|
||||
return node
|
||||
|
||||
Reference in new issue
Block a user