refactor(ads): [PKG-ADS-04] reconcile public types and historical imports

This commit is contained in:
Harvey Zhao committed 2026-09-12 08:25:17 +08:00
1 parent 39e2d4cdfb
commit fb1cfa701d
41 files changed
+1208 -149

No files matched your search

+75 -49
View File
@@ -1,52 +1,78 @@
interface Option {
/**
* 广告源文本,支持视频链接、图片链接、HTML文本
*/
source: string
/**
* 知名广告的类型:'video' | 'image' | 'html'
*/
type: 'video' | 'image' | 'html'
/**
* 广告必看的时长,单位为秒
*/
playDuration?: number
/**
* 广告总的时长,单位为秒
*/
totalDuration?: number
/**
* 视频广告是否默认静音
*/
muted?: boolean
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginAds {
interface Translations {
close: string
countdown: string
detail: string
canBeClosed: string
}
/** Implemented options. Video takes precedence over HTML. */
interface Option {
html?: string
video?: string
url?: string
/** Seconds before the close button becomes available. @default 5 */
playDuration?: number
/** Total advertisement duration in seconds. @default 10 */
totalDuration?: number
/** Initial ad-video mute state. @default false */
muted?: boolean
/** All four fields replace the default translation object together. */
i18n?: Translations
}
/** Historical published declaration. String durations still fail runtime validation. */
interface LegacyOption extends Omit<Option, 'totalDuration'> {
/** @deprecated Incorrect in the old declaration; use a numeric duration. */
totalDuration?: string
}
/** Historical unpublished workspace declaration; these fields are not runtime aliases. */
interface WorkspaceOption extends Option {
/** @deprecated Ignored by the runtime. Use html or video instead. */
source: string
/** @deprecated Ignored by the runtime. Images are supplied through html. */
type: 'video' | 'image' | 'html'
}
/** Input acceptance for both historical declaration families. */
interface CompatOption extends Omit<Option, 'totalDuration'> {
/** @deprecated The string branch exists for old types only and is rejected at runtime. */
totalDuration?: number | string
/** @deprecated Ignored by the runtime. Use html or video instead. */
source?: string
/** @deprecated Ignored by the runtime. Images are supplied through html. */
type?: 'video' | 'image' | 'html'
}
interface Result {
name: 'artplayerPluginAds'
/** Complete once; before initialization this cancels the pending preroll. */
skip: () => void
/** Pause only the countdown, leaving ad video playback unchanged. */
pause: () => void
/** Resume only the countdown without adding extra timers. */
play: () => void
}
interface Callable {
(option?: Option): (art: Artplayer) => Result
/** @deprecated Compatibility with erroneous old string-duration declarations only. */
(option: LegacyOption): (art: Artplayer) => Result
(option: WorkspaceOption): (art: Artplayer) => Result
(option?: CompatOption): (art: Artplayer) => Result
/** Required final signature keeps Parameters extraction free of top-level undefined. */
(option: CompatOption): (art: Artplayer) => Result
}
interface Factory extends Callable {
/** Same function; supports historical require(package).default calls. */
readonly default: Callable
}
interface RuntimeCallable {
(option?: Option): (art: Artplayer) => Result
(option: Option): (art: Artplayer) => Result
}
/** Accurate typing for the identical implementation at /runtime. */
interface RuntimeFactory extends RuntimeCallable {
readonly default: RuntimeCallable
}
}
interface Ads {
name: 'artplayerPluginAds'
/**
* 跳过广告
*/
skip: () => void
/**
* 暂停广告
*/
pause: () => void
/**
* 播放广告
*/
play: () => void
}
declare const artplayerPluginAds: (option: Option) => (art: Artplayer) => Ads
export default artplayerPluginAds
declare const artplayerPluginAds: artplayerPluginAds.Factory
export = artplayerPluginAds
export as namespace artplayerPluginAds;
+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!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
!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),w),m=()=>"active"===c&&!i.isDestroy,h=()=>"destroyed"===c||i.isDestroy;function x(n){try{n?.pause()}catch(t){e(t)}}function b(n,t,i){try{Promise.resolve(n()).then(()=>{m()||i?.()},t).catch(e)}catch(l){t(l)}}function w(){if("ended"!==c&&"destroyed"!==c&&!i.isDestroy){if(c="ended",v.stop(),r.dispose(),y){if(b(()=>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 O(){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 b=i.playDuration<=0;function w(){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;b=0===e?i.playDuration<=0:n<1||Number.isNaN(n),f.innerHTML=b?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),w()):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",()=>{b&&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,w()}),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:w,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;b(()=>t.play(),n=>{m()&&(e(n),w())},()=>x(t))}m()&&n.ready()}};n.video?(r.dom(n.video,"error",w),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",O),r.on("video:playing",O))}),i.isDestroy&&k()}catch(A){throw k(),A}return{name:"artplayerPluginAds",skip:w,pause:v.pause,play:v.play}}function l(e){return n=>{const t=n.constructor;if("function"!==typeof t.validator||!["append","query","setStyle"].every(e=>"function"===typeof Reflect.get(t.utils||{},e)))throwLine truncated
+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!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
!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,t=Object.prototype.hasOwnProperty,l=Object.prototype.propertyIsEnumerable,i=(n,t,l)=>t in n?e(n,t,{enumerable:!0,configurable:!0,writable:!0,value:l}):n[t]=l;function r(e,r){return r(((e,r)=>{for(var o in r||(r={}))t.call(r,o)&&i(e,o,r[o]);if(n)for(var o of n(r))l.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 t=!1;function l(e,l){if(!t){n.push(l);try{e()}finally{t&&l()}}}return{on(n,i){const r=()=>{t||i()};l(()=>{e.on(n,r)},()=>{e.off(n,r)})},dom(e,n,i){const r=()=>{t||i()};l(()=>e.addEventListener(n,r),()=>e.removeEventListener(n,r))},dispose(){if(!t){t=!0;for(const t of n.splice(0).reverse())try{t()}catch(e){o(e)}}}}}const s=(e,n)=>n.replace("%s",String(e));function d(e,n,t,l){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,t){let l,i=0,r=!1,o=!1,a=!1;function s(){r&&o&&!a&&void 0===l&&(l=setTimeout(()=>{l=void 0,!a&&o&&(i+=1,n(i),a||(i>=e()?t():s()))},1e3))}function d(){o=!1,void 0!==l&&clearTimeout(l),l=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,t){try{Promise.resolve(e()).then(()=>{m()||null==t||t()},n).catch(o)}catch(l){n(l)}}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,t,l,i){const{append:r,query:o,setStyle:a}=l,d=r(e,'<div class="artplayer-plugin-ads"></div>');i(d);const u=r(d,t.video?'<video class="artplayer-plugin-ads-video" loop playsInline></video>':`<div class="artplayer-plugin-ads-html">${t.html}</div>`),p=t.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">${t.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=t.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=t.playDuration-e;x=0===e?t.playDuration<=0:n<1||Number.isNaN(n),f.innerHTML=x?t.i18n.close:s(n,t.i18n.canBeClosed),g.innerHTML=s(t.totalDuration-e,t.i18n.countdown)}return t.playDuration>=t.totalDuration&&a(f,"display","none"),t.url||a(m,"display","none"),p?(r(h,n.volume),r(h,n.volumeClose),p.muted=Boolean(t.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),t.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,t,n,l,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
+2 -1
View File
@@ -390,6 +390,7 @@ function artplayerPluginAds(input) {
return createSession(art, option, { volume, volumeClose, fullscreenOn, fullscreenOff, loading }, constructor.utils);
};
}
const index = Object.assign(artplayerPluginAds, { default: artplayerPluginAds });
if (typeof document !== "undefined" && !document.getElementById("artplayer-plugin-ads")) {
const element = document.createElement("style");
element.id = "artplayer-plugin-ads";
@@ -397,5 +398,5 @@ if (typeof document !== "undefined" && !document.getElementById("artplayer-plugi
document.head.appendChild(element);
}
export {
artplayerPluginAds as default
index as default
};
+2 -1
View File
@@ -62,7 +62,8 @@
"test:package:release": "node scripts/package-check.mjs --release",
"build:types": "node scripts/build-types.mjs --write",
"check:types": "node scripts/build-types.mjs --check",
"test:dash-types-package": "node refactor/scripts/dash-package-types.mjs"
"test:dash-types-package": "node refactor/scripts/dash-package-types.mjs",
"test:ads-types-package": "node refactor/scripts/ads-package-types.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
+36 -6
View File
@@ -1,9 +1,9 @@
# 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.
returned `{ name, skip, pause, play }` are synchronous. Runtime input remains html/video/url
plus timing, mute and i18n. Historical source/type fields are accepted by the compatibility
declaration but are not implemented runtime aliases.
## Module boundaries
@@ -11,7 +11,7 @@ implemented runtime aliases. Runtime input remains html/video/url plus timing, m
| --- | --- |
| 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/types.ts | Normalized options and narrow core/DOM dependencies, reusing public input/result shapes |
| 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 |
@@ -22,6 +22,33 @@ only a parent, icons, options, three DOM utilities and callbacks, rather than th
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.
## Public declarations and imports
`types/artplayer-plugin-ads.d.ts` owns the callable namespace and public shapes. CommonJS
bridges use `.d.cts`; ESM bridges use `.d.mts`. Root and `/legacy` accept historical inputs.
The new `/runtime` entry resolves to the same JS implementation and restricts options to
implemented fields. `Option` is accurate, `LegacyOption` preserves the published string
duration declaration error, and `WorkspaceOption` represents the unpublished source/type
declaration. `CompatOption` accepts both families. No runtime duration coercion was added.
The existing validator rejects string durations before the single localized normalization
assertion in options.ts. Translation objects still replace all four fields together.
The factory has a self-referencing `.default`, supporting both callable require and the
published `require(package).default` access. This does not emulate the old noncallable
CommonJS namespace object's identity or key set. The returned plugin and its lifecycle
remain unchanged. Root and `/runtime` resolve to the same module for each module system.
The editor generator in scripts/plugin-editor-types.mjs accepts this type-only namespace
shape explicitly and rejects unknown imports/exports. Generate docs globals with
`yarn build:ts`; test their semantics and actual Monaco emit/run, not only parseability.
ADS-TYPE-01 is accepted with scope by the maintainer on 2026-09-12: `Parameters<typeof factory>[0]` accepts both old option
objects, but reading totalDuration yields `number | string | undefined`; the published
declaration yielded `string | undefined`. Workspace source/type fields also become optional.
Frozen consumers reproduce these differences. This approval covers only these historical
inference corrections, not general API changes. Preserve the diagnostic cases and migration
examples in the README; do not claim every historical TS program compiles unchanged.
## State and ordering
`waiting -> active -> ended`, with any state able to become `destroyed`. Ready arms the two
@@ -78,6 +105,9 @@ generates main/legacy/ESM through the repository build. Do not hand-edit dist or
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
reconciliation is recorded in PKG-ADS-04; complete historical distribution and demo validation
remain PKG-ADS-06. `yarn test:ads-types-package` packs and installs outside the workspace,
checks all installed bytes, frozen offline install, five compiler modes and Node exports.
`yarn test:browser test/browser/ads-editor-types.spec.js` verifies actual Monaco integration.
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.
+51
View File
@@ -5,6 +5,57 @@ Ads plugin for ArtPlayer
Implementation, resource ownership and regression commands:
[maintenance guide](ARCHITECTURE.md).
## Usage and TypeScript
```ts
import Artplayer from 'artplayer'
import ads from 'artplayer-plugin-ads'
new Artplayer({
container: '#player',
url: '/content.mp4',
plugins: [ads({ video: '/advertisement.mp4', url: '/details', playDuration: 5, totalDuration: 10 })],
})
```
Use `html` for HTML or an image element string. When both are present, `video` takes
precedence. `playDuration` controls when closing is allowed; `totalDuration` controls
the countdown. Durations are numeric seconds. Plugin `play()` and `pause()` control
only the countdown; `skip()` completes the advertisement once. All three return void.
For accurate options without historical declaration mistakes, import the same runtime
from `artplayer-plugin-ads/runtime`. Root and `/legacy` retain old input acceptance:
the old `totalDuration: string` declaration compiles but strings still fail runtime
validation; old workspace `source/type` fields are ignored. Use `video` or `html`.
An `i18n` object must supply `close`, `countdown`, `detail` and `canBeClosed` together.
CommonJS supports both `require('artplayer-plugin-ads')(options)` and
`require('artplayer-plugin-ads').default(options)`. ESM uses the default export.
## Migrating inferred option types
The maintainer approved correcting conflicting historical inference on 2026-09-12.
Existing option calls remain accepted, but `Parameters<typeof ads>[0].totalDuration`
now reads as `number | string | undefined`; historical workspace `source/type` are
optional. Code that assumed only one old declaration's field types may need adjustment.
This does not make string durations or source/type runtime aliases valid.
Prefer accurate options for new or migrated code:
```ts
import type { Option } from 'artplayer-plugin-ads'
import ads from 'artplayer-plugin-ads/runtime'
const option: Option = { video: '/advertisement.mp4', totalDuration: 10 }
const duration: number | undefined = option.totalDuration
const plugin = ads(option)
```
For a generic wrapper receiving historical input, narrow `totalDuration` before numeric
operations and check `source/type` before reading them as required fields. Do not use a
cast to hide string durations: the runtime validator rejects them. `LegacyOption` and
`WorkspaceOption` remain available for code explicitly representing those historical
declarations; `Option` describes the implemented configuration.
## Demo
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-ads/index.js&example=ads)
+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!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
!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),w),m=()=>"active"===c&&!i.isDestroy,h=()=>"destroyed"===c||i.isDestroy;function x(n){try{n?.pause()}catch(t){e(t)}}function b(n,t,i){try{Promise.resolve(n()).then(()=>{m()||i?.()},t).catch(e)}catch(l){t(l)}}function w(){if("ended"!==c&&"destroyed"!==c&&!i.isDestroy){if(c="ended",v.stop(),r.dispose(),y){if(b(()=>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 O(){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 b=i.playDuration<=0;function w(){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;b=0===e?i.playDuration<=0:n<1||Number.isNaN(n),f.innerHTML=b?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),w()):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",()=>{b&&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,w()}),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:w,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;b(()=>t.play(),n=>{m()&&(e(n),w())},()=>x(t))}m()&&n.ready()}};n.video?(r.dom(n.video,"error",w),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",O),r.on("video:playing",O))}),i.isDestroy&&k()}catch(A){throw k(),A}return{name:"artplayerPluginAds",skip:w,pause:v.pause,play:v.play}}function l(e){return n=>{const t=n.constructor;if("function"!==typeof t.validator||!["append","query","setStyle"].every(e=>"function"===typeof Reflect.get(t.utils||{},e)))throwLine truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!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
!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,t=Object.prototype.hasOwnProperty,l=Object.prototype.propertyIsEnumerable,i=(n,t,l)=>t in n?e(n,t,{enumerable:!0,configurable:!0,writable:!0,value:l}):n[t]=l;function r(e,r){return r(((e,r)=>{for(var o in r||(r={}))t.call(r,o)&&i(e,o,r[o]);if(n)for(var o of n(r))l.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 t=!1;function l(e,l){if(!t){n.push(l);try{e()}finally{t&&l()}}}return{on(n,i){const r=()=>{t||i()};l(()=>{e.on(n,r)},()=>{e.off(n,r)})},dom(e,n,i){const r=()=>{t||i()};l(()=>e.addEventListener(n,r),()=>e.removeEventListener(n,r))},dispose(){if(!t){t=!0;for(const t of n.splice(0).reverse())try{t()}catch(e){o(e)}}}}}const s=(e,n)=>n.replace("%s",String(e));function d(e,n,t,l){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,t){let l,i=0,r=!1,o=!1,a=!1;function s(){r&&o&&!a&&void 0===l&&(l=setTimeout(()=>{l=void 0,!a&&o&&(i+=1,n(i),a||(i>=e()?t():s()))},1e3))}function d(){o=!1,void 0!==l&&clearTimeout(l),l=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,t){try{Promise.resolve(e()).then(()=>{m()||null==t||t()},n).catch(o)}catch(l){n(l)}}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,t,l,i){const{append:r,query:o,setStyle:a}=l,d=r(e,'<div class="artplayer-plugin-ads"></div>');i(d);const u=r(d,t.video?'<video class="artplayer-plugin-ads-video" loop playsInline></video>':`<div class="artplayer-plugin-ads-html">${t.html}</div>`),p=t.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">${t.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=t.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=t.playDuration-e;x=0===e?t.playDuration<=0:n<1||Number.isNaN(n),f.innerHTML=x?t.i18n.close:s(n,t.i18n.canBeClosed),g.innerHTML=s(t.totalDuration-e,t.i18n.countdown)}return t.playDuration>=t.totalDuration&&a(f,"display","none"),t.url||a(m,"display","none"),p?(r(h,n.volume),r(h,n.volumeClose),p.muted=Boolean(t.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),t.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,t,n,l,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
@@ -390,6 +390,7 @@ function artplayerPluginAds(input) {
return createSession(art, option, { volume, volumeClose, fullscreenOn, fullscreenOff, loading }, constructor.utils);
};
}
const index = Object.assign(artplayerPluginAds, { default: artplayerPluginAds });
if (typeof document !== "undefined" && !document.getElementById("artplayer-plugin-ads")) {
const element = document.createElement("style");
element.id = "artplayer-plugin-ads";
@@ -397,5 +398,5 @@ if (typeof document !== "undefined" && !document.getElementById("artplayer-plugi
document.head.appendChild(element);
}
export {
artplayerPluginAds as default
index as default
};
+37 -7
View File
@@ -19,19 +19,49 @@
],
"exports": {
".": {
"types": "./types/artplayer-plugin-ads.d.ts",
"import": "./dist/artplayer-plugin-ads.mjs",
"require": "./dist/artplayer-plugin-ads.js"
"import": {
"types": "./types/artplayer-plugin-ads.d.mts",
"default": "./dist/artplayer-plugin-ads.mjs"
},
"require": {
"types": "./types/artplayer-plugin-ads.d.cts",
"default": "./dist/artplayer-plugin-ads.js"
}
},
"./legacy": {
"types": "./types/artplayer-plugin-ads.d.ts",
"import": "./dist/artplayer-plugin-ads.legacy.js",
"require": "./dist/artplayer-plugin-ads.legacy.js"
"import": {
"types": "./types/artplayer-plugin-ads.d.cts",
"default": "./dist/artplayer-plugin-ads.legacy.js"
},
"require": {
"types": "./types/artplayer-plugin-ads.d.cts",
"default": "./dist/artplayer-plugin-ads.legacy.js"
}
},
"./runtime": {
"import": {
"types": "./types/runtime/index.d.mts",
"default": "./dist/artplayer-plugin-ads.mjs"
},
"require": {
"types": "./types/runtime/index.d.cts",
"default": "./dist/artplayer-plugin-ads.js"
}
}
},
"main": "./dist/artplayer-plugin-ads.js",
"module": "./dist/artplayer-plugin-ads.mjs",
"types": "./types/artplayer-plugin-ads.d.ts",
"legacy": "./dist/artplayer-plugin-ads.legacy.js",
"browserslist": "last 1 Chrome version"
"browserslist": "last 1 Chrome version",
"typesVersions": {
"*": {
"legacy": [
"types/artplayer-plugin-ads.d.ts"
],
"runtime": [
"types/runtime/index.d.ts"
]
}
}
}
+3 -1
View File
@@ -4,7 +4,7 @@ import { normalizeOptions } from './options'
import { createSession } from './session'
import style from './style.less?inline'
export default function artplayerPluginAds(input?: Input) {
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.
@@ -17,6 +17,8 @@ export default function artplayerPluginAds(input?: Input) {
}
}
export default Object.assign(artplayerPluginAds, { default: artplayerPluginAds })
if (typeof document !== 'undefined' && !document.getElementById('artplayer-plugin-ads')) {
const element = document.createElement('style')
element.id = 'artplayer-plugin-ads'
+3 -1
View File
@@ -2,6 +2,8 @@ import type Artplayer from 'artplayer'
import type { Input, Options } from './types'
export function normalizeOptions(input: Input | undefined, validate: typeof Artplayer.validator): Options {
// The legacy declaration accepts strings, but this unchanged validator proves
// numeric runtime durations before the result crosses the declaration boundary.
return validate({
html: '',
video: '',
@@ -24,5 +26,5 @@ export function normalizeOptions(input: Input | undefined, validate: typeof Artp
totalDuration: 'number',
muted: '?boolean',
i18n: { close: 'string', countdown: 'string', detail: 'string', canBeClosed: 'string' },
})
}) as Options
}
+5 -18
View File
@@ -1,31 +1,18 @@
import type Artplayer from 'artplayer'
import type { CompatOption, Option, Result as PublicResult, Translations as PublicTranslations } from '../types/artplayer-plugin-ads'
export interface Translations {
close: string
countdown: string
detail: string
canBeClosed: string
}
export type Translations = PublicTranslations
export interface Options {
html?: string
video?: string
url?: string
export interface Options extends Option {
playDuration: number
totalDuration: number
muted?: boolean
i18n: Translations
}
export type Input = Partial<Options>
export type Input = CompatOption
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
}
export type Result = PublicResult
+1 -1
View File
@@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "allowJs": false },
"include": ["src/**/*.ts", "types/**/*.d.ts", "../../types/assets.d.ts"]
"include": ["src/**/*.ts", "types/**/*.d.ts", "types/**/*.d.mts", "types/**/*.d.cts", "../../types/assets.d.ts"]
}
@@ -0,0 +1,4 @@
// eslint-disable-next-line ts/no-require-imports -- Preserve the CommonJS export-assignment namespace.
import artplayerPluginAds = require('./artplayer-plugin-ads.js')
export = artplayerPluginAds
@@ -0,0 +1,2 @@
export { default } from './artplayer-plugin-ads.js'
export type { Callable, CompatOption, Factory, LegacyOption, Option, Result, RuntimeCallable, RuntimeFactory, Translations, WorkspaceOption } from './artplayer-plugin-ads.js'
+81 -43
View File
@@ -1,51 +1,89 @@
/* eslint-disable ts/no-redeclare -- The callable export and public type namespace intentionally merge. */
import type Artplayer from 'artplayer'
interface Option {
/**
* 广告源文本,支持视频链接、图片链接、HTML文本
*/
source: string
declare namespace artplayerPluginAds {
interface Translations {
close: string
countdown: string
detail: string
canBeClosed: string
}
/**
* 知名广告的类型:'video' | 'image' | 'html'
*/
type: 'video' | 'image' | 'html'
/** Implemented options. Video takes precedence over HTML. */
interface Option {
html?: string
video?: string
url?: string
/** Seconds before the close button becomes available. @default 5 */
playDuration?: number
/** Total advertisement duration in seconds. @default 10 */
totalDuration?: number
/** Initial ad-video mute state. @default false */
muted?: boolean
/** All four fields replace the default translation object together. */
i18n?: Translations
}
/**
* 广告必看的时长,单位为秒
*/
playDuration?: number
/** Historical published declaration. String durations still fail runtime validation. */
interface LegacyOption extends Omit<Option, 'totalDuration'> {
/** @deprecated Incorrect in the old declaration; use a numeric duration. */
totalDuration?: string
}
/**
* 广告总的时长,单位为秒
*/
totalDuration?: number
/** Historical unpublished workspace declaration; these fields are not runtime aliases. */
interface WorkspaceOption extends Option {
/** @deprecated Ignored by the runtime. Use html or video instead. */
source: string
/** @deprecated Ignored by the runtime. Images are supplied through html. */
type: 'video' | 'image' | 'html'
}
/**
* 视频广告是否默认静音
*/
muted?: boolean
/** Input acceptance for both historical declaration families. */
interface CompatOption extends Omit<Option, 'totalDuration'> {
/** @deprecated The string branch exists for old types only and is rejected at runtime. */
totalDuration?: number | string
/** @deprecated Ignored by the runtime. Use html or video instead. */
source?: string
/** @deprecated Ignored by the runtime. Images are supplied through html. */
type?: 'video' | 'image' | 'html'
}
interface Result {
name: 'artplayerPluginAds'
/** Complete once; before initialization this cancels the pending preroll. */
skip: () => void
/** Pause only the countdown, leaving ad video playback unchanged. */
pause: () => void
/** Resume only the countdown without adding extra timers. */
play: () => void
}
interface Callable {
(option?: Option): (art: Artplayer) => Result
/** @deprecated Compatibility with erroneous old string-duration declarations only. */
(option: LegacyOption): (art: Artplayer) => Result
(option: WorkspaceOption): (art: Artplayer) => Result
(option?: CompatOption): (art: Artplayer) => Result
/** Required final signature keeps Parameters extraction free of top-level undefined. */
(option: CompatOption): (art: Artplayer) => Result
}
interface Factory extends Callable {
/** Same function; supports historical require(package).default calls. */
readonly default: Callable
}
interface RuntimeCallable {
(option?: Option): (art: Artplayer) => Result
(option: Option): (art: Artplayer) => Result
}
/** Accurate typing for the identical implementation at /runtime. */
interface RuntimeFactory extends RuntimeCallable {
readonly default: RuntimeCallable
}
}
interface Ads {
name: 'artplayerPluginAds'
/**
* 跳过广告
*/
skip: () => void
/**
* 暂停广告
*/
pause: () => void
/**
* 播放广告
*/
play: () => void
}
declare const artplayerPluginAds: (option: Option) => (art: Artplayer) => Ads
export default artplayerPluginAds
declare const artplayerPluginAds: artplayerPluginAds.Factory
export = artplayerPluginAds
export as namespace artplayerPluginAds
@@ -0,0 +1,4 @@
// eslint-disable-next-line ts/no-require-imports -- Preserve the CommonJS export-assignment namespace.
import artplayerPluginAds = require('./index.js')
export = artplayerPluginAds
@@ -0,0 +1,2 @@
export type { Option, Result, RuntimeFactory, Translations } from '../artplayer-plugin-ads.js'
export { default } from './index.js'
+11
View File
@@ -0,0 +1,11 @@
/* eslint-disable ts/no-redeclare -- The callable export and public type namespace intentionally merge. */
import type * as Definition from '../artplayer-plugin-ads'
declare const artplayerPluginAds: Definition.RuntimeFactory
declare namespace artplayerPluginAds {
type Option = Definition.Option
type Result = Definition.Result
type RuntimeFactory = Definition.RuntimeFactory
type Translations = Definition.Translations
}
export = artplayerPluginAds
+13
View File
@@ -37,6 +37,19 @@ Node 测试已接入 test:unit,因此 ci:check 会执行。浏览器测试独
`ARTPLAYER_ADS_ARTIFACT` 给浏览器指定单个 script 产物,默认按仓库 Vite 配置从源码
内存构建。路径无效直接失败,不自动回退。实际发布 bundle 总是先校验归档及成员哈希。
## 04 类型与编辑器验证
`refactor/scripts/ads-types.test.mjs` 验证五种类型消费模式、十个无效用法,以及实际
npm 和冻结工作区声明。普通调用通过不代表 scalar 提取兼容;诊断测试明确复现
旧字段读取在候选上的 TS2322。用户明确接受此项修正后,ADS-TYPE-01 标为
accepted-with-scope;保留诊断并编译迁移示例,不把批准范围扩大到其他 API。
`yarn test:ads-types-package` 在工作区外安装实际 tarball,验证安装字节、冻结离线
重装、五种类型模式和 Node exports。`test/browser/ads-editor-types.spec.js` 从
本地 docs 加载真实 Monaco 和生成声明,执行其编译结果并检查 default 工厂身份。
`test/ads.test.js` 增加候选 `.default === factory` 及同步取消断言,覆盖三种产物。
这些检查不替代全部历史子路径、媒体/设备与发布验收。
## 边界与文件职责
- `test/helpers/ads.js`:来源加载、冻结 bundle 的 VM、可控时钟和最小 ArtPlayer 宿主。
@@ -0,0 +1,368 @@
{
"schemaVersion": 1,
"task": "PKG-ADS-04",
"status": "done",
"sourceCommit": "39e2d4cd",
"inputs": [
{
"file": "package.json",
"sha256LF": "7d56a87d087e96cb34465201cdf5c0926b5714a215f749e22bba96f908a64711"
},
{
"file": "scripts/build-ts.js",
"sha256LF": "0453b9a9ac25f5be99e9ecfe222c6ae1d16540374b2b6c9b7529a54bdd50f91a"
},
{
"file": "scripts/plugin-editor-types.mjs",
"sha256LF": "6ecf896e084a19e5a9a9e7aca65a1108d96d842e3e2da61a05755d74a8f7f9c8"
},
{
"file": "scripts/typecheck.mjs",
"sha256LF": "9e439c300afa6e37813db312478a3ac3f47d0bb363e615ec49c67cd6ecdbb883"
},
{
"file": "test/types/ads.ts",
"sha256LF": "16b333c61f7a57f9d8ba8e6fab6e40aba761c4dc746e99d6b38e122315b0dde3"
},
{
"file": "test/types/ads-source.ts",
"sha256LF": "02221946a6cb726045c4910a40027ff8e0a6e011426443e28e11762bc1cdb52e"
},
{
"file": "test/ads.test.js",
"sha256LF": "0b5406a5aca3cd69672343f00492f6fcb22db17ec39056f1d89fb5cdd467a7d6"
},
{
"file": "test/browser/ads-editor-types.spec.js",
"sha256LF": "e55c8674e93b04342e6ca0325a411b74fa4cf8d331e57e571e555c4c1e706b80"
},
{
"file": "refactor/scripts/ads-types.test.mjs",
"sha256LF": "ddecd08afe4f04740c3c915e3a54c88d94fecd4796cbe8e1c668d69c46322685"
},
{
"file": "refactor/scripts/ads-package-types.mjs",
"sha256LF": "846af87f5f7f0c84fcbccecd4b3dfd6e2c1946ce47dfe7122760fe5f25cf5b6a"
},
{
"file": "refactor/fixtures/consumers/ads-published.ts",
"sha256LF": "0b8e5bb9bb74aebcf309b7af8942a7cbe6036de14f07fac121959eeb7d7d3910"
},
{
"file": "refactor/fixtures/consumers/ads-workspace.ts",
"sha256LF": "aee3b18b9c8f566a71e1e9d31a2ce8cdeae5664b246806bceed66bc97e18129e"
},
{
"file": "docs/assets/ts/artplayer-plugin-ads.d.ts",
"sha256LF": "bf128828d5b104fc844a4f134a02162e57472d8c927a50df7b3878f4c5ed7652"
},
{
"file": "packages/artplayer-plugin-ads/package.json",
"sha256LF": "9a7d0a2e917902bb64282566e96123244a81aa4859a82c6b1c711d639660a9bf"
},
{
"file": "packages/artplayer-plugin-ads/tsconfig.json",
"sha256LF": "583e4ac1afa99e3a29c7f899df9fdeefcbd14d3847d69143e3fa6a8facac6d99"
},
{
"file": "packages/artplayer-plugin-ads/src/countdown.ts",
"sha256LF": "7dab796a3d8fb757da7f87e4396403f0687af52d0876cf74ebb2b8db7c2e752a"
},
{
"file": "packages/artplayer-plugin-ads/src/index.ts",
"sha256LF": "7b0e155a6371c71a5c3f5ba0e9e95737648a2b73b0cf124536de8b6f79a92371"
},
{
"file": "packages/artplayer-plugin-ads/src/options.ts",
"sha256LF": "3ce2ad78b9f0288314fa75adf14776adf10f2205789268e9599a6d551b3b8b4f"
},
{
"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": "219d5b06bdbece09b88640fe6f2bc27c44dd34eff5909cec9f7405a9dced759a"
},
{
"file": "packages/artplayer-plugin-ads/src/view.ts",
"sha256LF": "7d2cba7a3e20ce02288627723ebc9256d61cee313c3a7d00a31373b7f250eb91"
},
{
"file": "packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.cts",
"sha256LF": "ccd2d5f47bb94f00cad3950d8fa8670f6fd46b384876b0d1c0325ff209a1a1d1"
},
{
"file": "packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.mts",
"sha256LF": "f8c5b9843d0992c2f01200581b8d1c166ba4d3423ccc87496c0a4c0bae5a9c5b"
},
{
"file": "packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.ts",
"sha256LF": "a347e5a3e244ff0e58a28cad7c0e84adcdbadbef4dfe2128c525e6d317e7e0bb"
},
{
"file": "packages/artplayer-plugin-ads/types/runtime/index.d.cts",
"sha256LF": "d97fbe931948bce1eb4ab3be7780f096a4b0095db31c1d82b4df1f80ab5c2d90"
},
{
"file": "packages/artplayer-plugin-ads/types/runtime/index.d.mts",
"sha256LF": "5f1f7a86be47bd72571e2665f37007c7c250ea0b774806c4864221bd7bef5bc5"
},
{
"file": "packages/artplayer-plugin-ads/types/runtime/index.d.ts",
"sha256LF": "ddaa0c124b4d1d2b673f1305bbe625dda83d4296eb7d0a9ede202201bd483b8e"
}
],
"artifacts": [
{
"file": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.js",
"sha256": "bc718001050bbe73f0fb450fbdffdf127dd2b0839e19fbd473fe0856e6cdae76",
"bytes": 7276,
"copy": {
"file": "docs/compiled/artplayer-plugin-ads.js",
"sha256": "bc718001050bbe73f0fb450fbdffdf127dd2b0839e19fbd473fe0856e6cdae76",
"bytes": 7276
}
},
{
"file": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.legacy.js",
"sha256": "3f60e8a5d9fc93affff4ecc97b4b4035f743ab076da6baa05e3ea50052141fa6",
"bytes": 7715,
"copy": {
"file": "docs/compiled/artplayer-plugin-ads.legacy.js",
"sha256": "3f60e8a5d9fc93affff4ecc97b4b4035f743ab076da6baa05e3ea50052141fa6",
"bytes": 7715
}
},
{
"file": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.mjs",
"sha256": "e04ac6e56f2f9170fd56f40f0a01c4ed990de6dc6abf5f176d2df4e340f23b6a",
"bytes": 13059,
"copy": {
"file": "docs/compiled/artplayer-plugin-ads.mjs",
"sha256": "e04ac6e56f2f9170fd56f40f0a01c4ed990de6dc6abf5f176d2df4e340f23b6a",
"bytes": 13059
}
}
],
"checks": [
{
"file": "refactor/.cache/ads04-types-first.log",
"sha256": "c18abde08ebfb2e58d076c2be9537ede0f26b98697822dec1380e68c259336e3",
"bytes": 490
},
{
"file": "refactor/.cache/ads04-types-final.log",
"sha256": "adf2534bbbb1e480c6697889554333c4db37dbf488c3dae84b35c91f25082bab",
"bytes": 481
},
{
"file": "refactor/.cache/ads04-build.log",
"sha256": "b13f278c4875a97015e66b17287b83c8e59d539a453136f9389c1fc03c30e282",
"bytes": 1438
},
{
"file": "refactor/.cache/ads04-artifacts-node.log",
"sha256": "56b6327c75b2e231e5a6f5c4ed31cc715fc8459f17d2770cc5a9f70fc3405509",
"bytes": 21421
},
{
"file": "refactor/.cache/ads04-lint-final.log",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
}
],
"node": {
"passed": 190,
"failed": 0,
"scope": "Source, three candidate builds, both frozen implementations. Four new candidate default identity contracts."
},
"ci": {
"file": "refactor/.cache/ads04-ci.log",
"sha256": "89a90190c53142bf8b46a75d7cea7f77e0a64739ec4a0e40d44e15d965c82f9d",
"bytes": 87364,
"passed": 834,
"groups": [
771,
14,
49
],
"productionTS": 269,
"note": "Full local CI before final approval documentation and migration fixture additions; final focused type tests and isolated installation rerun afterwards. No runtime changes afterwards."
},
"installed": {
"report": {
"file": "refactor/.cache/ads-package-types-ViT0Rk/report.json",
"sha256": "790e857b2de783682ce920528f8e434900027c031e58389e6168d661270f99b5",
"bytes": 27243
},
"files": [
{
"file": "refactor/.cache/ads-package-types-ViT0Rk/artplayer-pack.log",
"sha256": "2bfac87c586ecfd7b5bcd526df4ea5b029ecf6aa64cce74aa7d40655a7fb608f",
"bytes": 109
},
{
"file": "refactor/.cache/ads-package-types-ViT0Rk/artplayer-plugin-ads-pack.log",
"sha256": "40c8eba25889d42ff743390b7a30a180dc0d799cc61f37425a3a17099df35855",
"bytes": 120
},
{
"file": "refactor/.cache/ads-package-types-ViT0Rk/artplayer-plugin-ads.tgz",
"sha256": "61fc2065715603a6fec0385ea6f2c583c72a06717c60e590b644714fa40142da",
"bytes": 13099
},
{
"file": "refactor/.cache/ads-package-types-ViT0Rk/artplayer.tgz",
"sha256": "2139af017670a4f30f667bf5fdc1aa680f3e03b9c8d3cb767fc92d6345aedff5",
"bytes": 280395
},
{
"file": "refactor/.cache/ads-package-types-ViT0Rk/frozen-install.log",
"sha256": "6d8a95fad69ac94a55d176b472fc3a0f72783092e49b7e683156b6717510f4a8",
"bytes": 56
},
{
"file": "refactor/.cache/ads-package-types-ViT0Rk/install.log",
"sha256": "c3b09f4c65bfb4a1bf31ddd2e3328c22988cf52a31e36c31ea2de4d3132a89c6",
"bytes": 142
},
{
"file": "refactor/.cache/ads-package-types-ViT0Rk/report.json",
"sha256": "790e857b2de783682ce920528f8e434900027c031e58389e6168d661270f99b5",
"bytes": 27243
},
{
"file": "refactor/.cache/ads-package-types-ViT0Rk/runtime-exports.log",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
{
"file": "refactor/.cache/ads-package-types-ViT0Rk/yarn.lock",
"sha256": "dfbe3a9bb57cda8f2a56e56be62eb86f4621357bfe809404c53d8627c6f50012",
"bytes": 1142
}
],
"log": {
"file": "refactor/.cache/ads04-isolated-approved.log",
"sha256": "5fff64be2b2a5842114383e1f179d82bafe85935bd37f383653416f6c5eeea65",
"bytes": 623
}
},
"browsers": [
{
"name": "editor-browser",
"stats": {
"startTime": "2026-09-12T00:13:32.234Z",
"duration": 5034.381,
"expected": 3,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"report": {
"file": "refactor/.cache/ads04-editor-browser-report.json",
"sha256": "dc368e26442ebd5ba13c248420f3ec0f0a6ab131bb0b202f2c235b3db45e5e92",
"bytes": 33504
},
"log": {
"file": "refactor/.cache/ads04-editor-browser.log",
"sha256": "5e883ca8658726669d00b22cf288437588dae0a571c7c13a1527fdd2c18de80f",
"bytes": 1624
},
"results": [
{
"file": "refactor/.cache/ads04-editor-browser-results/.last-run.json",
"sha256": "91d1c43004802cd49950d78eb11c8fa7d05da8ffffe219a8b13b2f561bc00903",
"bytes": 45
}
]
},
{
"name": "editor-legacy-browser",
"stats": {
"startTime": "2026-09-12T00:18:00.519Z",
"duration": 6241.242,
"expected": 3,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"report": {
"file": "refactor/.cache/ads04-editor-legacy-browser-report.json",
"sha256": "a4986570a83a82c1ef1d3649d41b49c02cd242bf0bed48905b218d838d754afd",
"bytes": 33696
},
"log": {
"file": "refactor/.cache/ads04-editor-legacy-browser.log",
"sha256": "565c6e84a7fdf4c5be7763aa618319f2e79f9b85370d461a78fac770e5092503",
"bytes": 1624
},
"results": [
{
"file": "refactor/.cache/ads04-editor-legacy-browser-results/.last-run.json",
"sha256": "91d1c43004802cd49950d78eb11c8fa7d05da8ffffe219a8b13b2f561bc00903",
"bytes": 45
}
]
},
{
"name": "browser-main",
"stats": {
"startTime": "2026-09-12T00:15:13.125Z",
"duration": 152809.649,
"expected": 144,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"report": {
"file": "refactor/.cache/ads04-browser-main-report.json",
"sha256": "3db67f4bc41458cdb0d5f6e51d981656111fe6010378aa135f5fc1c3a8955fff",
"bytes": 9439737
},
"log": {
"file": "refactor/.cache/ads04-browser-main.log",
"sha256": "987573115af5eeaf03ea33d355238a1c12fd17ae0dea9e01836d151d5931c42b",
"bytes": 25421
},
"results": [
{
"file": "refactor/.cache/ads04-browser-main-results/.last-run.json",
"sha256": "91d1c43004802cd49950d78eb11c8fa7d05da8ffffe219a8b13b2f561bc00903",
"bytes": 45
}
]
}
],
"acceptedDifference": {
"id": "ADS-TYPE-01",
"date": "2026-09-12",
"decision": "接受这项类型推导修正,写清迁移说明并继续",
"scope": "Only scalar inference correction between conflicting published/workspace declarations. Old calls accepted; string durations still rejected at runtime; migration examples compiled.",
"publishedDiagnostics": [
2322
],
"workspaceDiagnostics": [
2322,
2322
],
"compilers": [
"4.3.5",
"5.9.3"
]
},
"unresolved": [
"Full legacy media matrix from 03 was not rerun for 04: 04 checks all three formats in Node, main media 144 and main/legacy editor 3 each.",
"Native fullscreen, real hidden-page/device behavior and complete historical distribution remain 05/06.",
"No push, tag, merge or npm publication."
]
}
@@ -0,0 +1,50 @@
# PKG-ADS-04 类型与导入兼容
本步完成公开类型迁移;整个 Ads 发布验收未完成。起点 `39e2d4cd`。
## 实际改动
- 公开声明补齐真实 html/video/url/i18n、同步 Result 与四种输入形状。
根入口以 export= 和类型命名空间保存旧 CommonJS/全局模式,增加 ESM/CJS 声明桥。
- `/runtime` 指向同一 JS 文件,只提供准确参数;根入口保留两套历史输入接受面。
src/types 复用公开形状,options 中现有 validator 后才断言归一化结果,无字符串转换。
- 入口增加 `.default === factory`,兼容实际 npm 的 require(pkg).default 调用和
工作区 callable 调用;未模仿旧模块对象不可调用、Object.keys 等 namespace 反射形状。
- 编辑器生成器显式支持纯类型 namespace,拒绝未知导入和不支持的节点;实际 Monaco
从 docs/assets/js/vs 加载、编译并运行当前生成声明及真实 bundle。
- 新增 `yarn test:ads-types-package`:临时打包、工作区外离线安装、冻结重装、所有成员
哈希验证、五种编译模式与 Node require/import 同一运行时验证。未增加依赖或锁文件变化。
## API-09 / API-11 已确认差异
实际 npm 1.0.6 的 totalDuration 声明为 string;未发布工作区 2.1.0 为 number,
并要求 source/type。两者与实际运行时字段不同。普通调用和配置对象赋值已同时验证,
但无法把同一属性既准确推导为 string 又推导为 number。
当前兼容输入使 `Parameters<typeof ads>[0].totalDuration` 读取为
`number | string | undefined`。原发布消费者把它赋给 `string | undefined` 会产生
TS2322;原工作区把 source 赋给 string、duration 赋给 number | undefined 也会产生
TS2322。这是真实兼容差异,不能因为旧输入调用通过而称所有旧 TS 程序无改动兼容。
`LegacyOption` / `WorkspaceOption` 和准确 `/runtime` 可表达各自形状,但要求用户
修改类型写法本身不算无改动兼容。
2026-09-12,向用户提供上述具体影响和验证结果后,用户明确答复:
“接受这项类型推导修正,写清迁移说明并继续”。ADS-TYPE-01 据此标为
accepted-with-scope,仅接受两套历史声明矛盾导致的 scalar 推导修正;不扩大到其他
API、运行时行为或发布豁免。README 提供准确 Option、/runtime、数字字段及泛型
输入收窄迁移说明,类型测试也编译同样的安全读取模式。
`refactor/scripts/ads-types.test.mjs` 从校验过的发布 tarball 和冻结 Git 声明读取旧类型,
同一普通消费者在旧/新声明通过;额外 scalar 消费者先在旧声明通过,再精确断言候选
1/2 个 TS2322。此诊断测试持续保留;差异的接受依据为上述用户明确决策,测试本身
只证明实际影响范围。
## 验证与接续
执行结果与输入哈希见 [执行证据](../baselines/ads-types-validation.json)。
源码/三格式 Node、五模式类型、安装及编辑器测试均单独列出;全屏/真隐藏页/设备、
完整历史 dist 路径和最终发布门槛继续由05/06承接。已有 DASH 风险保持开放。
实际模块地图与维护命令见 [包内架构](../../packages/artplayer-plugin-ads/ARCHITECTURE.md)。
撤销本步时同时撤销入口 .default、公开声明/桥、manifest、生成产物和消费者测试;
保留03生命周期修复。按任务独立本地提交,无推送、tag、合并或发布。
@@ -0,0 +1,8 @@
import ads from 'artplayer-plugin-ads'
const option: Parameters<typeof ads>[0] = { html: '<b>ad</b>', video: 'ad.mp4', url: '/ad', totalDuration: '10', playDuration: 2, muted: true }
const plugin = ads(option)
const resultName: ReturnType<typeof plugin>['name'] = 'artplayerPluginAds'
const result: ReturnType<typeof plugin> = { name: resultName, skip() {}, pause() {}, play() {} }
const returns: void[] = [result.skip(), result.pause(), result.play()]
void [returns, plugin]
@@ -0,0 +1,8 @@
import ads from 'artplayer-plugin-ads'
const option: Parameters<typeof ads>[0] = { source: 'ad.mp4', type: 'video', totalDuration: 10, playDuration: 2, muted: true }
const plugin = ads(option)
const resultName: ReturnType<typeof plugin>['name'] = 'artplayerPluginAds'
const result: ReturnType<typeof plugin> = { name: resultName, skip() {}, pause() {}, play() {} }
const returns: void[] = [result.skip(), result.pause(), result.play()]
void [returns, plugin]
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 217 项,范围 22 个包及工作区/示例。
状态:todo 136 / doing 4 / blocked 0 / done 77 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 135 / doing 4 / blocked 0 / done 78 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -259,7 +259,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 | 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-04 | artplayer-plugin-ads<br>迁移自有源码和公开类型 | PKG-ADS-03, ENG-04, ENG-06, CORE-07 | 兼容真实 html/video/url/i18n、旧 export= 与工作区 source/type 类型接受面;时长 string/number 漂移与未实现别名单独处置 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
| 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 |
@@ -487,3 +487,4 @@
- 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)
- PKG-ADS-04: [记录](changes/2026-09-12-PKG-ADS-04-types.md) [记录](baselines/ads-types-validation.json)
+15
View File
@@ -1,5 +1,20 @@
# 进度与证据
## 最新完成:Ads-04 类型与导入兼容
公开声明补齐 html/video/url/i18n,增加 CJS/ESM 类型桥和同一运行时的准确 /runtime
类型入口。保留两套历史输入的普通调用,并补齐 require(pkg).default 自引用兼容。
严格消费者、历史声明对照、隔离安装和真实 Monaco 编译运行已经接入可重跑测试。
同一 Parameters 提取类型无法同时维持发布版 string 和工作区 number 时长字段;
普通调用通过,但旧 scalar 读取分别复现1/2个 TS2322。用户已明确接受这项类型
推导修正并要求迁移说明,ADS-TYPE-01 据此 accepted-with-scope,不扩展为其他豁免。
完整 CI 834 项通过,三格式 Node190、main真实媒体144、main/legacy编辑器各3项通过;
最终安装包五模式和迁移示例继续严格验证。04完成,下一步05真实页面与媒体/UI验收。
当前217项:78 done、4 doing、135 todo。详细结果见
[实施记录](changes/2026-09-12-PKG-ADS-04-types.md) 和
[验证证据](baselines/ads-types-validation.json)。
## 最新完成:Ads-03 源码拆分与生命周期修复
Ads 原入口拆为 7 个严格 TS 模块,职责为配置、类型、计时、资源、视图、session 和入口。
+1 -1
View File
@@ -141,7 +141,7 @@
| DASH-TYPE-01 | resolved / 源码/产物事实 | DASH 类型未表达默认工厂参数、SDK 回调对象和现代模块入口 | PKG-DASH-04 |
| DASH-DEMO-01 | open / 源码/产物事实 | DASH 示例仅新 SDK 且重复安装会累积销毁回调 | PKG-DASH-06 |
| DASH-SEEK-01 | open / 已复现 | dash.js 4.5.2 空裁剪漏更新缓冲量,稳定暂停 seek 后调度停滞 | PKG-DASH-05, REVIEW-02 |
| ADS-TYPE-01 | open / 源码/产物事实 | Ads 声明丢失真实 html/video/url,旧 totalDuration 为 string,当前 source/type 未实现 | PKG-ADS-02, PKG-ADS-04 |
| ADS-TYPE-01 | accepted-with-scope / 已复现 | Ads 声明丢失真实 html/video/url,旧 totalDuration 为 string,当前 source/type 未实现 | PKG-ADS-02, PKG-ADS-04 |
| ADS-DIST-01 | open / 已复现 | Ads npm 1.0.6 default namespace 与未发布 2.1.0 callable/exports 分发不同 | PKG-ADS-04, PKG-ADS-06, REL-01 |
| ADS-CORE-01 | open / 源码/产物事实 | Ads 旧发布关联核心 4.5.5,当前源码新增 >=5 门槛和 document 事件桥依赖 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| ADS-LIFE-01 | open / 已复现 | Ads 计时链、重复/过早调用、工厂复用与销毁缺乏资源边界 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
+37 -9
View File
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"task": "BASE-07",
"sourceCommit": "e01f39ff",
"updatedAt": "2026-09-11",
"updatedAt": "2026-09-12",
"note": "Open means closure evidence is outstanding; source-observed and unverified are not reproduced runtime bugs. A source issue already mitigated in the workspace still requires candidate regression evidence. No accepted-with-scope item is implied by baseline task completion.",
"items": [
{
@@ -2985,20 +2985,48 @@
{
"id": "ADS-TYPE-01",
"title": "Ads 声明丢失真实 html/video/url,旧 totalDuration 为 string,当前 source/type 未实现",
"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.ts"],
"compatibleResolution": "同时保护实际 npm 字段/旧声明消费者和当前类型接受面;真实数字时长、i18n 与 source/type 别名的行为差异单独登记,不伪称旧实现支持别名。",
"closureCriteria": "两套旧消费者、可选工厂、合法运行字段和错误参数有严格正反例,声明与真实方法一致。"
"confirmation": "reproduced",
"status": "accepted-with-scope",
"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.ts",
"refactor/changes/2026-09-12-PKG-ADS-04-types.md",
"refactor/scripts/ads-types.test.mjs"
],
"compatibleResolution": "保留真实html/video/url/i18n及两套历史输入的普通调用;string时长仍在运行时拒绝、source/type仍不实现。用户2026-09-12明确接受Parameters字段推导变宽,README和编译用例提供准确Option、/runtime及收窄迁移;不扩大到其他API差异。",
"closureCriteria": "两套旧消费者、可选工厂、合法运行字段和错误参数有严格正反例,声明与真实方法一致。",
"resolutionRationale": "用户在收到具体TS2322影响和两套矛盾声明证据后答复“接受这项类型推导修正,写清迁移说明并继续”。五模式、冻结声明、隔离包和真实编辑器通过;诊断测试继续保留,不声称所有历史TS程序无改动兼容。",
"resolutionEvidence": [
"refactor/changes/2026-09-12-PKG-ADS-04-types.md",
"refactor/baselines/ads-types-validation.json",
"packages/artplayer-plugin-ads/README.md",
"refactor/scripts/ads-types.test.mjs",
"test/types/ads.ts"
]
},
{
"id": "ADS-DIST-01",
"title": "Ads npm 1.0.6 default namespace 与未发布 2.1.0 callable/exports 分发不同",
"confirmation": "reproduced",
"status": "open",
"owners": ["PKG-ADS-04", "PKG-ADS-06", "REL-01"],
"evidence": ["refactor/baselines/ads-release.json", "refactor/baselines/ads-contract.md", "refactor/scripts/ads-contract.test.mjs", "packages/artplayer-plugin-ads/package.json"],
"owners": [
"PKG-ADS-04",
"PKG-ADS-06",
"REL-01"
],
"evidence": [
"refactor/baselines/ads-release.json",
"refactor/baselines/ads-contract.md",
"refactor/scripts/ads-contract.test.mjs",
"packages/artplayer-plugin-ads/package.json",
"refactor/baselines/ads-types-validation.json"
],
"compatibleResolution": "保留旧 require(pkg).default、global 与有效 dist 路径,同时支持当前 callable/ESM/legacy。工作区 2.1.0 不冒充实际已发布版本,正式目标仍按计划 3.0.0。",
"closureCriteria": "工作区外真实 tarball 的旧/新导入、浏览器与声明入口全部验证,版本占用再次核实。"
},
+94
View File
@@ -0,0 +1,94 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import ts from 'typescript'
import compat from 'typescript-compat'
import { checkFiles, packedFiles } from '../../scripts/package-check.mjs'
import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '../../scripts/package-consumer.mjs'
import { hash, readMember } from './releases.mjs'
async function main() {
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:ads-types-package')
const yarn = process.env.npm_execpath
assert(yarn && fs.existsSync(yarn))
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/ads-package-types-'))
console.log(`Ads isolated package type evidence: ${output}`)
const consumer = consumerDirectory()
try {
const packages = []
const published = JSON.parse(fs.readFileSync(path.join(workspace, 'refactor/baselines/ads-release.json'))).release
for (const name of ['artplayer', 'artplayer-plugin-ads']) {
const archive = path.join(output, `${name}.tgz`)
fs.writeFileSync(path.join(output, `${name}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', name)))
const files = packedFiles(archive)
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
checkFiles(manifest, files, name === published.name ? Object.keys(published.files) : [])
packages.push({ name, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
}
writeJson(path.join(consumer, 'package.json'), { name: 'ads-isolated-type-consumer', private: true, dependencies: Object.fromEntries(packages.map(pkg => [pkg.name, `file:${pkg.archive.replaceAll('\\', '/')}`])) })
fs.copyFileSync(path.join(workspace, 'yarn.lock'), path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, 'install.log'), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
const lock = fs.readFileSync(path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, 'frozen-install.log'), run([yarn, 'install', '--offline', '--frozen-lockfile', '--ignore-scripts', '--non-interactive'], consumer))
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
fs.writeFileSync(path.join(output, 'yarn.lock'), lock)
for (const pkg of packages) {
const root = path.join(consumer, 'node_modules', pkg.name)
assert.equal(fs.realpathSync(root), root, 'Installed package is a workspace link')
for (const [member, expected] of Object.entries(pkg.files))
assert.equal(hash(fs.readFileSync(path.join(root, member.slice(8)))), expected, `Installed bytes differ: ${member}`)
}
const matrix = []
const runtimeFile = path.join(consumer, 'exports.cjs')
fs.writeFileSync(runtimeFile, `const assert = require('node:assert/strict');
const ads = require('artplayer-plugin-ads'); const legacy = require('artplayer-plugin-ads/legacy'); const runtime = require('artplayer-plugin-ads/runtime');
for (const factory of [ads, legacy, runtime]) { assert.equal(typeof factory, 'function'); assert.equal(factory.default, factory); assert.equal(typeof factory({ html: 'ad' }), 'function'); }
assert.equal(runtime, ads);
Promise.all(['artplayer-plugin-ads', 'artplayer-plugin-ads/runtime'].map(name => import(name))).then(([root, precise]) => { assert.equal(root.default, precise.default); assert.equal(root.default.default, root.default); }).catch(error => { console.error(error); process.exitCode = 1; });`)
fs.writeFileSync(path.join(output, 'runtime-exports.log'), run([runtimeFile], consumer))
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]) {
const next = mode.startsWith('nodenext')
const extension = next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS, moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs }
const source = fs.readFileSync(path.join(workspace, 'test/types/ads.ts'), 'utf8')
const filename = path.join(consumer, `current.${extension}`)
const legacy = path.join(consumer, `legacy.${extension}`)
fs.copyFileSync(path.join(workspace, 'refactor/fixtures/consumers/ads-published.ts'), legacy)
const workspaceFixture = path.join(consumer, `workspace.${extension}`)
fs.copyFileSync(path.join(workspace, 'refactor/fixtures/consumers/ads-workspace.ts'), workspaceFixture)
const files = [filename, legacy, workspaceFixture]
if (mode === 'nodenext-cjs') {
const commonjs = path.join(consumer, 'commonjs.cts')
fs.writeFileSync(commonjs, `import ads = require('artplayer-plugin-ads'); import legacy = require('artplayer-plugin-ads/legacy'); import runtime = require('artplayer-plugin-ads/runtime');
const option: ads.Option = { html: 'ad', totalDuration: 5 }; ads(option); ads.default(option); legacy(); legacy.default(option); runtime(option); runtime.default(option);`)
files.push(commonjs)
}
function compile(code) {
fs.writeFileSync(filename, code)
const program = compiler.createProgram(files, options)
for (const file of program.getSourceFiles()) {
const actual = fs.realpathSync(file.fileName)
assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(file) && path.dirname(actual) === fs.realpathSync(path.dirname(compiler.sys.getExecutingFilePath()))), `Types escaped isolated consumer: ${actual}`)
}
return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
}
const diagnostics = compile(source)
assert.deepEqual(diagnostics, [], `${compiler.version} ${mode}`)
const invalid = compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''))
assert.equal(invalid.length, 10, 'Installed package must reject all invalid uses')
matrix.push({ compiler: compiler.version, mode, diagnostics, invalid })
}
const report = { suite: 'ads-isolated-package-types', introducedBy: 'PKG-ADS-04', scope: 'Packed current core and Ads, offline Yarn install and frozen reinstall outside workspace; five type modes and Node export identity. Historical scalar inference correction accepted with scope by maintainer; not complete media or distribution release validation.', packages, matrix }
writeJson(path.join(output, 'report.json'), report)
console.log(`Ads installed type matrix passed: ${matrix.length} modes, ten invalid uses rejected in each; ${output}`)
}
finally {
removeConsumer(consumer)
}
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
+88
View File
@@ -0,0 +1,88 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
// eslint-disable-next-line test/no-import-node-test -- Check published and workspace declaration consumers independently.
import test from 'node:test'
import { ESLint } from 'eslint'
import ts from 'typescript'
import compat from 'typescript-compat'
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../../scripts/plugin-editor-types.mjs'
import { checkConsumer } from '../../scripts/typecheck.mjs'
import { ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
test('Ads root, legacy and accurate runtime types support five compiler modes and reject invalid consumers', () => {
const source = fs.readFileSync('test/types/ads.ts', 'utf8')
for (const [compiler, mode] of modes) {
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
assert.equal(checkConsumer(compiler, mode, source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')).length, 10)
}
const commonjs = `import ads = require('artplayer-plugin-ads'); import legacy = require('artplayer-plugin-ads/legacy'); import runtime = require('artplayer-plugin-ads/runtime');
const option: ads.Option = { html: 'ad', totalDuration: 5 }; ads(option); ads.default(option); legacy(option); legacy.default(option); runtime(option); runtime.default(option);`
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', commonjs), [])
})
test('Ads historical calls compile against frozen declarations; approved scalar extraction differences remain explicit', async () => {
const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/ads-release.json')))
const member = 'package/types/artplayer-plugin-ads.d.ts'
const published = readMember(await ensureArchive(baseline.release), member)
assert.equal(hash(published), baseline.release.files[member])
const oldPath = 'packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.ts'
const workspace = execFileSync('git', ['show', `${baseline.sourceCommit}:${oldPath}`]).toString().replaceAll('\r\n', '\n')
assert.equal(hash(workspace), baseline.source[oldPath])
const directory = fs.mkdtempSync(path.join(refactorDir, '.cache/ads-legacy-types-'))
try {
for (const [family, declaration, extraction] of [
['published', published, `declare const input: Parameters<typeof ads>[0]; const duration: string | undefined = input.totalDuration; void duration;`],
['workspace', workspace, `declare const input: Parameters<typeof ads>[0]; const source: string = input.source; const duration: number | undefined = input.totalDuration; void [source, duration];`],
]) {
const source = fs.readFileSync(path.join(refactorDir, `fixtures/consumers/ads-${family}.ts`), 'utf8')
fs.writeFileSync(path.join(directory, 'legacy.d.ts'), declaration)
const filename = path.join(directory, 'consumer.ts')
for (const compiler of [ts, compat]) {
fs.writeFileSync(filename, `${source}\n${extraction}`.replace('from \'artplayer-plugin-ads\'', 'from \'./legacy\''))
const program = compiler.createProgram([filename], { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'] })
assert.deepEqual(compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') })), [], `${family} / ${compiler.version}`)
assert(program.getSourceFiles().some(file => path.resolve(file.fileName) === path.join(directory, 'legacy.d.ts')))
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', source), [])
const differences = checkConsumer(compiler, 'node10-commonjs', `${source}\n${extraction}`)
assert.deepEqual(differences.map(item => item.code), family === 'published' ? [2322] : [2322, 2322], 'ADS-TYPE-01 accepted with scope: preserve the exact approved scalar inference differences')
}
for (const [compiler, mode] of modes)
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${family} / ${compiler.version} / ${mode}`)
}
}
finally {
const resolved = fs.realpathSync(directory)
assert.equal(path.dirname(resolved), fs.realpathSync(path.join(refactorDir, '.cache')))
assert(path.basename(resolved).startsWith('ads-legacy-types-'))
fs.rmSync(resolved, { recursive: true, force: true })
}
})
test('Ads editor generation retains public namespaces and rejects unknown declaration shapes', async () => {
const source = fs.readFileSync('packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.ts', 'utf8')
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginAds')
const file = 'docs/assets/ts/artplayer-plugin-ads.d.ts'
const [formatted] = await new ESLint({ fix: true, fixTypes: ['layout'] }).lintText(generated, { filePath: file })
assert.equal(formatted.errorCount, 0)
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), formatted.output || generated)
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
const consumer = `const option: artplayerPluginAds.Option = { html: 'ad', totalDuration: 5 };
const factory: (art: Artplayer) => artplayerPluginAds.Result = artplayerPluginAds(option);
artplayerPluginAds.default(option);
// @ts-expect-error A DOM element is not HTML text.
artplayerPluginAds({ html: document.body });
// @ts-expect-error Complete translations are required.
artplayerPluginAds({ i18n: { close: 'Close' } });
void factory;`
for (const compiler of [ts, compat]) {
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 2)
}
assert.throws(() => generatePluginEditorDeclaration(`import type Unknown from 'unknown';\n${source}`, 'artplayerPluginAds'), /Unsupported CommonJS plugin editor import/)
assert.throws(() => generatePluginEditorDeclaration(source.replace('export as namespace artplayerPluginAds', ''), 'artplayerPluginAds'), /Incomplete CommonJS/)
assert.throws(() => generatePluginEditorDeclaration(source.replace('interface Translations {', 'const unexpected: string; interface Translations {'), 'artplayerPluginAds'), /only public type declarations/)
})
+5 -2
View File
@@ -2590,11 +2590,14 @@
"ENG-06",
"CORE-07"
],
"status": "todo",
"status": "done",
"risk": "H",
"deliverable": "兼容真实 html/video/url/i18n、旧 export= 与工作区 source/type 类型接受面;时长 string/number 漂移与未实现别名单独处置",
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
"evidence": []
"evidence": [
"changes/2026-09-12-PKG-ADS-04-types.md",
"baselines/ads-types-validation.json"
]
},
{
"id": "PKG-ADS-05",
+7
View File
@@ -1,5 +1,12 @@
# 已实现的类型检查与迁移入口
PKG-ADS-04 已协调公开声明:根入口保留历史输入,/runtime 使用准确输入,同一 JS
实现不进行字符串时长转换。CJS/ESM 桥、五种消费者模式、实际旧声明对照与真实 Monaco
生成运行已接入。`ads-source.ts` 检查源码与公开工厂的赋值关系;`ads.ts` 包含十个
无效用法。`yarn test:ads-types-package` 在工作区外验证 tarball 安装后的同一矩阵。
`ads-types.test.mjs` 额外复现旧 scalar 提取变宽的 TS2322;用户明确接受这项推导修正,
已记录迁移说明,不扩大为其他发布豁免。详情见 [类型记录](changes/2026-09-12-PKG-ADS-04-types.md)。
PKG-HLS-04 增加 HLS 的 5 个严格 TS 源模块,公开声明保留历史 .d.ts 并增加 CJS/ESM 条件桥。
test/types/hls-control.ts 覆盖配置省略、旧 Parameters 提取、默认字段和 SDK 泛型、同步返回,
hls-source.ts 检查源码工厂与公开工厂可相互赋值。hls-types.test.mjs 对五组编译模式检查正反例,
+1 -1
View File
@@ -43,7 +43,7 @@ console.log(`✨ Built ${artplayerTSoutput}`);
const type = pluginsTS[index]
const { name, file } = parsePluginInfo(type)
const source = String(fs.readFileSync(type))
const semanticPlugin = ['artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl'].includes(name)
const semanticPlugin = ['artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds'].includes(name)
const code = semanticPlugin
? generatePluginEditorDeclaration(source, name)
: `${source.replace(reg, '')}\nexport = ${name};\nexport as namespace ${name};\n`
+45
View File
@@ -8,6 +8,8 @@ export function generatePluginEditorDeclaration(code, name) {
assert(/^[a-z_$][\w$]*$/i.test(name), 'Invalid plugin global')
const source = ts.createSourceFile('plugin.d.ts', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
assert.equal(source.parseDiagnostics.length, 0, 'Invalid plugin declaration syntax')
if (source.statements.some(node => ts.isExportAssignment(node) && node.isExportEquals))
return generateCommonJSPluginEditor(source, name)
const factory = ts.factory
const definitions = []
const aliases = []
@@ -56,6 +58,49 @@ export function generatePluginEditorDeclaration(code, name) {
return `// Generated from the package public declaration by yarn build:ts. Do not edit.\n/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */\n${statements.map(node => printer.printNode(ts.EmitHint.Unspecified, node, source)).join('\n')}\n`
}
function generateCommonJSPluginEditor(source, name) {
const statements = []
let namespace = false
let callable = false
let exported = false
let global = false
for (const node of source.statements) {
if (ts.isImportDeclaration(node)) {
assert(node.importClause?.isTypeOnly && !node.importClause.namedBindings && node.importClause.name?.text === 'Artplayer' && node.moduleSpecifier.text === 'artplayer', 'Unsupported CommonJS plugin editor import')
continue
}
if (ts.isModuleDeclaration(node)) {
assert.equal(node.name.text, name, 'Unexpected plugin namespace')
assert(node.body && ts.isModuleBlock(node.body) && node.body.statements.every(item => ts.isInterfaceDeclaration(item) || ts.isTypeAliasDeclaration(item)), 'Editor namespace must contain only public type declarations')
namespace = true
}
else if (ts.isVariableStatement(node)) {
const definitions = node.declarationList.declarations
assert.equal(definitions.length, 1)
const definition = definitions[0]
assert.equal(definition.name.text, name)
assert(ts.isTypeReferenceNode(definition.type) && ts.isQualifiedName(definition.type.typeName)
&& definition.type.typeName.left.text === name && definition.type.typeName.right.text === 'Factory', 'Editor export must use its public Factory interface')
callable = true
}
else if (ts.isExportAssignment(node)) {
assert(node.isExportEquals && node.expression.text === name, 'Unexpected CommonJS plugin export')
exported = true
}
else if (ts.isNamespaceExportDeclaration(node)) {
assert.equal(node.name.text, name)
global = true
}
else {
assert.fail('Unsupported CommonJS plugin editor declaration')
}
statements.push(node)
}
assert(namespace && callable && exported && global, 'Incomplete CommonJS plugin editor declaration')
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
return `// Generated from the package public declaration by yarn build:ts. Do not edit.\n/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */\n${statements.map(node => printer.printNode(ts.EmitHint.Unspecified, node, source)).join('\n')}\n`
}
export function checkPluginEditorDeclaration(code, core, consumer = '', compiler = ts) {
const folder = fileURLToPath(new URL('../refactor/.cache/plugin-editor-semantic/', import.meta.url))
const sources = new Map([
+1 -1
View File
@@ -73,7 +73,7 @@ export function runTypechecks() {
console.log(`Strict project passed: ${relative(config)} (${result.files.length} root files)`)
}
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'bundler-esm'], [ts, 'nodenext-esm'], [compat, 'node10-commonjs']]) {
for (const fixture of ['test/types/public.ts', 'test/types/declaration-inputs.ts', 'test/types/declaration-legacy.ts', 'test/types/plugins-public.ts', 'test/types/playback-public.ts', 'test/types/chapter-options.ts', 'test/types/chapter-exports.ts', 'test/types/hls-control.ts', 'test/types/dash-control.ts', 'refactor/fixtures/consumers/dash-control-legacy.ts', 'test/types/audio-track.ts', 'test/types/language-value.ts', 'refactor/fixtures/consumers/language.ts', 'refactor/fixtures/consumers/legacy-plugin.ts']) {
for (const fixture of ['test/types/public.ts', 'test/types/declaration-inputs.ts', 'test/types/declaration-legacy.ts', 'test/types/plugins-public.ts', 'test/types/playback-public.ts', 'test/types/chapter-options.ts', 'test/types/chapter-exports.ts', 'test/types/hls-control.ts', 'test/types/dash-control.ts', 'test/types/ads.ts', 'refactor/fixtures/consumers/ads-published.ts', 'refactor/fixtures/consumers/ads-workspace.ts', 'refactor/fixtures/consumers/dash-control-legacy.ts', 'test/types/audio-track.ts', 'test/types/language-value.ts', 'refactor/fixtures/consumers/language.ts', 'refactor/fixtures/consumers/legacy-plugin.ts']) {
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `Consumer failed: TS ${compiler.version} ${mode} ${fixture}`)
}
if (mode === 'nodenext-cjs')
+14
View File
@@ -8,6 +8,20 @@ const skips = host => host.calls.filter(call => call[0] === 'emit' && call[1] ==
const clicks = host => host.calls.filter(call => call[0] === 'emit' && call[1] === 'artplayerPluginAds:click')
const contentPlays = host => host.calls.filter(call => call[0] === 'content.play')
for (const { name, factory } of implementations.filter(item => !item.historical)) {
test(`${name}: historical default and callable entry share one factory and synchronous contract`, (t) => {
const env = adsEnvironment(t)
assert.equal(factory.default, factory)
const host = env.host()
const result = factory.default({ html: 'ad' })(host.art)
assert.equal(result.name, 'artplayerPluginAds')
assert.equal(result.skip(), undefined)
host.start()
assert.equal(host.node(), undefined)
assert.equal(env.timers.size, 0)
})
}
for (const { name, factory } of implementations) {
test(`${name}: lazy synchronous factory starts once after ready and either playback event`, (t) => {
const env = adsEnvironment(t)
+66
View File
@@ -0,0 +1,66 @@
import fs from 'node:fs'
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { expect, test } from './fixtures.js'
test('Monaco compiles Ads namespaces and executes callable and historical default factories', async ({ page }, testInfo) => {
const code = process.env.ARTPLAYER_ADS_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_ADS_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-ads', 'umd')
const declarations = Object.fromEntries(['artplayer', 'artplayer-plugin-ads'].map(name => [name, hash(fs.readFileSync(new URL(`../../docs/assets/ts/${name}.d.ts`, import.meta.url)))]))
await testInfo.attach('ads-editor-inputs', { contentType: 'application/json', body: JSON.stringify({ candidate: process.env.ARTPLAYER_ADS_ARTIFACT || 'workspace source build', sha256: hash(code), declarations }) })
await page.goto('/test/player.html?core=candidate&chapter=published')
await page.addScriptTag({ content: code })
await page.addScriptTag({ url: '/assets/js/vs/loader.js' })
const result = await page.evaluate(async () => {
window.require.config({ paths: { vs: '/assets/js/vs' } })
await new Promise((resolve, reject) => window.require(['vs/editor/editor.main'], resolve, reject))
const api = window.monaco
const types = api.languages.typescript
types.typescriptDefaults.setCompilerOptions({ target: types.ScriptTarget.ES2020, strict: true, skipLibCheck: false, noEmit: false, types: [], allowNonTsExtensions: true })
const libraries = await Promise.all(['artplayer', 'artplayer-plugin-ads'].map(async (name) => {
const uri = `file:///${name}.d.ts`
const source = await (await fetch(`/assets/ts/${name}.d.ts`)).text()
return { uri, handle: types.typescriptDefaults.addExtraLib(source, uri) }
}))
const model = api.editor.createModel(`
const adsOption: artplayerPluginAds.Option = { html: '<b>ad</b>', totalDuration: 5,
i18n: { close: 'Close', countdown: '%s seconds', detail: 'Details', canBeClosed: '%s seconds' } };
const typedFactory: (art: Artplayer) => artplayerPluginAds.Result = artplayerPluginAds(adsOption);
const defaultFactory = artplayerPluginAds.default(adsOption);
const historical: Parameters<typeof artplayerPluginAds>[0] = { html: 'ad', totalDuration: '10' };
const workspace: artplayerPluginAds.WorkspaceOption = { source: 'ad.mp4', type: 'video' };
const oldFactory = artplayerPluginAds(historical);
const workspaceFactory = artplayerPluginAds(workspace);
`, 'typescript', api.Uri.parse('file:///ads-consumer.ts'))
const invalid = api.editor.createModel('artplayerPluginAds({ totalDuration: false })', 'typescript', api.Uri.parse('file:///ads-invalid.ts'))
try {
const worker = await (await types.getTypeScriptWorker())(model.uri, invalid.uri)
const syntax = await worker.getSyntacticDiagnostics(model.uri.toString())
const semantic = await worker.getSemanticDiagnostics(model.uri.toString())
const declarations = (await Promise.all(libraries.map(lib => worker.getSemanticDiagnostics(lib.uri)))).flat()
const bad = await worker.getSemanticDiagnostics(invalid.uri.toString())
if (syntax.length || semantic.length || declarations.length)
return { syntax, semantic, declarations, invalid: bad.map(item => item.code), executed: false }
const output = await worker.getEmitOutput(model.uri.toString())
const script = output.outputFiles.find(file => file.name.endsWith('.js'))
if (!script)
throw new Error('Monaco did not emit the Ads fixture')
// eslint-disable-next-line no-new-func -- Exercise the local editor Run action with its real emitted code.
const executed = new Function(`${script.text}\nreturn [typedFactory, defaultFactory, oldFactory, workspaceFactory].every(value => typeof value === 'function') && artplayerPluginAds.default === artplayerPluginAds`)()
return { syntax, semantic, declarations, invalid: bad.map(item => item.code), executed }
}
finally {
model.dispose()
invalid.dispose()
for (const library of libraries)
library.handle.dispose()
}
})
expect(result.syntax).toEqual([])
expect(result.semantic).toEqual([])
expect(result.declarations).toEqual([])
expect(result.invalid).toEqual([2769])
expect(result.executed).toBe(true)
})
+7
View File
@@ -0,0 +1,7 @@
import type { Factory, RuntimeFactory } from 'artplayer-plugin-ads'
import factory from '../../packages/artplayer-plugin-ads/src/index'
const compatible: Factory = factory
const precise: RuntimeFactory = factory
const acceptsCompatible: typeof factory = compatible
void [compatible, precise, acceptsCompatible]
+52
View File
@@ -0,0 +1,52 @@
import type { CompatOption, Factory, LegacyOption, Option, Result, WorkspaceOption } from 'artplayer-plugin-ads'
import Artplayer from 'artplayer'
import ads from 'artplayer-plugin-ads'
import legacy from 'artplayer-plugin-ads/legacy'
import precise from 'artplayer-plugin-ads/runtime'
const option: Option = { video: 'ad.mp4', html: '<b>ad</b>', url: '/details', totalDuration: 10, muted: true, i18n: { close: 'Close', countdown: '%s seconds', detail: 'Details', canBeClosed: 'Wait %s seconds' } }
const oldPublished: LegacyOption = { html: 'ad', totalDuration: '10' }
const oldWorkspace: WorkspaceOption = { source: 'ad.mp4', type: 'video', totalDuration: 10 }
const art = new Artplayer({ container: '#player', url: 'video.mp4', plugins: [ads(), ads(option), legacy(option), precise(option)] })
ads(oldPublished)
ads(oldWorkspace)
ads.default(option)
legacy.default(option)
precise.default(option)
ads(undefined)
precise(undefined)
const result: Result = ads(option)(art)
const methods: void[] = [result.skip(), result.play(), result.pause()]
const factory: Factory = ads
const input: CompatOption = oldWorkspace
const inferredOld: Parameters<typeof ads>[0] = oldPublished
const inferredWorkspace: Parameters<typeof ads>[0] = oldWorkspace
const raw: Parameters<typeof precise>[0] = option
const numericDuration: number | undefined = option.totalDuration
function readCompatibleDuration(value: CompatOption): number | undefined {
if (value.totalDuration !== undefined && typeof value.totalDuration !== 'number')
throw new TypeError('Ads requires numeric duration')
return value.totalDuration
}
// @ts-expect-error The precise entry rejects the historical duration typo.
precise({ totalDuration: '10' })
// @ts-expect-error source/type are not implemented runtime aliases.
precise({ source: 'ad.mp4', type: 'video' })
// @ts-expect-error Durations are not boolean in either declaration family.
ads({ totalDuration: false })
// @ts-expect-error No declaration accepted a string skip threshold.
ads({ playDuration: '1' })
// @ts-expect-error HTML is text, not a DOM node.
ads({ html: document.body })
// @ts-expect-error Translation objects replace all four fields together.
ads({ i18n: { close: 'Close' } })
// @ts-expect-error The plugin methods are synchronous.
const promise: Promise<void> = result.skip()
// @ts-expect-error Plugin name retains its literal spelling.
const name: Result['name'] = 'ads'
// @ts-expect-error Historical workspace type remains a fixed union.
ads({ source: 'ad', type: 'audio' })
// @ts-expect-error Historical string durations cannot become accurate numeric options.
const wrong: Option = oldPublished
void [methods, factory, input, inferredOld, inferredWorkspace, raw, numericDuration, readCompatibleDuration, promise, name, wrong]