From fb1cfa701de30fcae4d10b7779729910a449b0d8 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Sat, 12 Sep 2026 08:25:17 +0800 Subject: [PATCH] refactor(ads): [PKG-ADS-04] reconcile public types and historical imports --- docs/assets/ts/artplayer-plugin-ads.d.ts | 124 +++--- docs/compiled/artplayer-plugin-ads.js | 2 +- docs/compiled/artplayer-plugin-ads.legacy.js | 2 +- docs/compiled/artplayer-plugin-ads.mjs | 3 +- package.json | 3 +- packages/artplayer-plugin-ads/ARCHITECTURE.md | 42 +- packages/artplayer-plugin-ads/README.md | 51 +++ .../dist/artplayer-plugin-ads.js | 2 +- .../dist/artplayer-plugin-ads.legacy.js | 2 +- .../dist/artplayer-plugin-ads.mjs | 3 +- packages/artplayer-plugin-ads/package.json | 44 ++- packages/artplayer-plugin-ads/src/index.ts | 4 +- packages/artplayer-plugin-ads/src/options.ts | 4 +- packages/artplayer-plugin-ads/src/types.ts | 23 +- packages/artplayer-plugin-ads/tsconfig.json | 2 +- .../types/artplayer-plugin-ads.d.cts | 4 + .../types/artplayer-plugin-ads.d.mts | 2 + .../types/artplayer-plugin-ads.d.ts | 124 ++++-- .../types/runtime/index.d.cts | 4 + .../types/runtime/index.d.mts | 2 + .../types/runtime/index.d.ts | 11 + refactor/ads-validation.md | 13 + refactor/baselines/ads-types-validation.json | 368 ++++++++++++++++++ .../changes/2026-09-12-PKG-ADS-04-types.md | 50 +++ refactor/fixtures/consumers/ads-published.ts | 8 + refactor/fixtures/consumers/ads-workspace.ts | 8 + refactor/plan.md | 5 +- refactor/progress.md | 15 + refactor/risk-table.md | 2 +- refactor/risks.json | 46 ++- refactor/scripts/ads-package-types.mjs | 94 +++++ refactor/scripts/ads-types.test.mjs | 88 +++++ refactor/tasks.json | 7 +- refactor/typechecking.md | 7 + scripts/build-ts.js | 2 +- scripts/plugin-editor-types.mjs | 45 +++ scripts/typecheck.mjs | 2 +- test/ads.test.js | 14 + test/browser/ads-editor-types.spec.js | 66 ++++ test/types/ads-source.ts | 7 + test/types/ads.ts | 52 +++ 41 files changed, 1208 insertions(+), 149 deletions(-) create mode 100644 packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.cts create mode 100644 packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.mts create mode 100644 packages/artplayer-plugin-ads/types/runtime/index.d.cts create mode 100644 packages/artplayer-plugin-ads/types/runtime/index.d.mts create mode 100644 packages/artplayer-plugin-ads/types/runtime/index.d.ts create mode 100644 refactor/baselines/ads-types-validation.json create mode 100644 refactor/changes/2026-09-12-PKG-ADS-04-types.md create mode 100644 refactor/fixtures/consumers/ads-published.ts create mode 100644 refactor/fixtures/consumers/ads-workspace.ts create mode 100644 refactor/scripts/ads-package-types.mjs create mode 100644 refactor/scripts/ads-types.test.mjs create mode 100644 test/browser/ads-editor-types.spec.js create mode 100644 test/types/ads-source.ts create mode 100644 test/types/ads.ts diff --git a/docs/assets/ts/artplayer-plugin-ads.d.ts b/docs/assets/ts/artplayer-plugin-ads.d.ts index 618fdd6c1..866befee7 100644 --- a/docs/assets/ts/artplayer-plugin-ads.d.ts +++ b/docs/assets/ts/artplayer-plugin-ads.d.ts @@ -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 { + /** @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 { + /** @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; diff --git a/docs/compiled/artplayer-plugin-ads.js b/docs/compiled/artplayer-plugin-ads.js index c43c5e479..e7515e91b 100644 --- a/docs/compiled/artplayer-plugin-ads.js +++ b/docs/compiled/artplayer-plugin-ads.js @@ -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,'
');a(d);const u=o(d,i.video?'':`
${i.html}
`),p=i.video?u:null,c=o(d,'
');o(c,n.loading);const y=o(d,'
\n
\n
\n
'),f=r(".artplayer-plugin-ads-close",y),g=r(".artplayer-plugin-ads-countdown",y),v=o(d,`
\n
${i.i18n.detail}
\n
\n
\n
`),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=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:none;position:absolute;top:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-timer>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control{display:none;position:absolute;bottom:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-control>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg{width:20px;height:20px}.artplayer-plugin-ads .artplayer-plugin-ads-loading{display:flex;align-items:center;justify-content:center;position:absolute;inset:0;width:100%;height:100%}",document.head.appendChild(e)}return function(e){return n=>{const t=n.constructor;if("function"!==typeof t.validator||!["append","query","setStyle"].every(e=>"function"===typeof Reflect.get(t.utils||{},e)))throw new Error("Artplayer Ads requires the core validator and DOM utilities");const l=function(e,n){return n({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"}})}(e,t.validator),{volume:a,volumeClose:o,fullscreenOn:r,fullscreenOff:s,loading:d}=n.icons;return i(n,l,{volume:a,volumeClose:o,fullscreenOn:r,fullscreenOff:s,loading:d},t.utils)}}}); +!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,'
');a(d);const u=o(d,i.video?'':`
${i.html}
`),p=i.video?u:null,c=o(d,'
');o(c,n.loading);const y=o(d,'
\n
\n
\n
'),f=r(".artplayer-plugin-ads-close",y),g=r(".artplayer-plugin-ads-countdown",y),v=o(d,`
\n
${i.i18n.detail}
\n
\n
\n
`),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)))throw new Error("Artplayer Ads requires the core validator and DOM utilities");const l=function(e,n){return n({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"}})}(e,t.validator),{volume:a,volumeClose:o,fullscreenOn:r,fullscreenOff:s,loading:d}=n.icons;return i(n,l,{volume:a,volumeClose:o,fullscreenOn:r,fullscreenOff:s,loading:d},t.utils)}}const a=Object.assign(l,{default:l});if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-ads")){const e=document.createElement("style");e.id="artplayer-plugin-ads",e.textContent=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:none;position:absolute;top:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-timer>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control{display:none;position:absolute;bottom:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-control>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg{width:20px;height:20px}.artplayer-plugin-ads .artplayer-plugin-ads-loading{display:flex;align-items:center;justify-content:center;position:absolute;inset:0;width:100%;height:100%}",document.head.appendChild(e)}return a}); diff --git a/docs/compiled/artplayer-plugin-ads.legacy.js b/docs/compiled/artplayer-plugin-ads.legacy.js index 0c4f02174..56f333132 100644 --- a/docs/compiled/artplayer-plugin-ads.legacy.js +++ b/docs/compiled/artplayer-plugin-ads.legacy.js @@ -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,'
');i(d);const u=r(d,l.video?'':`
${l.html}
`),p=l.video?u:null,c=r(d,'
');r(c,n.loading);const y=r(d,'
\n
\n
\n
'),f=o(".artplayer-plugin-ads-close",y),g=o(".artplayer-plugin-ads-countdown",y),v=r(d,`
\n
${l.i18n.detail}
\n
\n
\n
`),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)},fullscreen(){e.fullscreen=!e.fullscreen,m()&&r.fullscreen(e.fullscreen)}}),i.on("fullscreen",()=>r.fullscreen(e.fullscreen));const c=()=>a.hidden?v.pause():v.play();i.dom(a,"visibilitychange",c),i.on("document:visibilitychange",c);const y=()=>{if(m()&&!g){if(g=!0,v.start(),a.hidden&&v.pause(),r.video){const e=r.video;x(()=>e.play(),e=>{m()&&(o(e),w())},()=>b(e))}m()&&r.ready()}};r.video?(i.dom(r.video,"error",w),i.dom(r.video,"loadedmetadata",y),r.video.src=n.video):y()}catch(r){throw k(),r}}}try{r.on("destroy",k),i.on("ready",()=>{f||"waiting"!==c||(f=!0,i.on("play",O),i.on("video:playing",O))}),e.isDestroy&&k()}catch(A){throw k(),A}return{name:"artplayerPluginAds",skip:w,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=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:none;position:absolute;top:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-timer>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control{display:none;position:absolute;bottom:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-control>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg{width:20px;height:20px}.artplayer-plugin-ads .artplayer-plugin-ads-loading{display:flex;align-items:center;justify-content:center;position:absolute;inset:0;width:100%;height:100%}",document.head.appendChild(e)}return function(e){return n=>{const l=n.constructor;if("function"!==typeof l.validator||!["append","query","setStyle"].every(e=>"function"===typeof Reflect.get(l.utils||{},e)))throw new Error("Artplayer Ads requires the core validator and DOM utilities");const t=r(e,l.validator),{volume:i,volumeClose:o,fullscreenOn:a,fullscreenOff:s,loading:u}=n.icons;return d(n,t,{volume:i,volumeClose:o,fullscreenOn:a,fullscreenOff:s,loading:u},l.utils)}}}); +!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,'
');i(d);const u=r(d,t.video?'':`
${t.html}
`),p=t.video?u:null,c=r(d,'
');r(c,n.loading);const y=r(d,'
\n
\n
\n
'),f=o(".artplayer-plugin-ads-close",y),g=o(".artplayer-plugin-ads-countdown",y),v=r(d,`
\n
${t.i18n.detail}
\n
\n
\n
`),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)},fullscreen(){e.fullscreen=!e.fullscreen,m()&&r.fullscreen(e.fullscreen)}}),i.on("fullscreen",()=>r.fullscreen(e.fullscreen));const c=()=>a.hidden?v.pause():v.play();i.dom(a,"visibilitychange",c),i.on("document:visibilitychange",c);const y=()=>{if(m()&&!g){if(g=!0,v.start(),a.hidden&&v.pause(),r.video){const e=r.video;x(()=>e.play(),e=>{m()&&(o(e),w())},()=>b(e))}m()&&r.ready()}};r.video?(i.dom(r.video,"error",w),i.dom(r.video,"loadedmetadata",y),r.video.src=n.video):y()}catch(r){throw k(),r}}}try{r.on("destroy",k),i.on("ready",()=>{f||"waiting"!==c||(f=!0,i.on("play",O),i.on("video:playing",O))}),e.isDestroy&&k()}catch(A){throw k(),A}return{name:"artplayerPluginAds",skip:w,pause:v.pause,play:v.play}}function u(e){return n=>{const t=n.constructor;if("function"!==typeof t.validator||!["append","query","setStyle"].every(e=>"function"===typeof Reflect.get(t.utils||{},e)))throw new Error("Artplayer Ads requires the core validator and DOM utilities");const l=r(e,t.validator),{volume:i,volumeClose:o,fullscreenOn:a,fullscreenOff:s,loading:u}=n.icons;return d(n,l,{volume:i,volumeClose:o,fullscreenOn:a,fullscreenOff:s,loading:u},t.utils)}}const p=Object.assign(u,{default:u});if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-ads")){const e=document.createElement("style");e.id="artplayer-plugin-ads",e.textContent=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:none;position:absolute;top:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-timer>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control{display:none;position:absolute;bottom:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-control>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg{width:20px;height:20px}.artplayer-plugin-ads .artplayer-plugin-ads-loading{display:flex;align-items:center;justify-content:center;position:absolute;inset:0;width:100%;height:100%}",document.head.appendChild(e)}return p}); diff --git a/docs/compiled/artplayer-plugin-ads.mjs b/docs/compiled/artplayer-plugin-ads.mjs index b402d67a5..73386eb0d 100644 --- a/docs/compiled/artplayer-plugin-ads.mjs +++ b/docs/compiled/artplayer-plugin-ads.mjs @@ -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 }; diff --git a/package.json b/package.json index 204109747..f5fe967e6 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/packages/artplayer-plugin-ads/ARCHITECTURE.md b/packages/artplayer-plugin-ads/ARCHITECTURE.md index b2afce755..5d5aa0374 100644 --- a/packages/artplayer-plugin-ads/ARCHITECTURE.md +++ b/packages/artplayer-plugin-ads/ARCHITECTURE.md @@ -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[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. diff --git a/packages/artplayer-plugin-ads/README.md b/packages/artplayer-plugin-ads/README.md index 6e7f1fc9b..789f5102c 100644 --- a/packages/artplayer-plugin-ads/README.md +++ b/packages/artplayer-plugin-ads/README.md @@ -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[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) diff --git a/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.js b/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.js index c43c5e479..e7515e91b 100644 --- a/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.js +++ b/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.js @@ -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,'
');a(d);const u=o(d,i.video?'':`
${i.html}
`),p=i.video?u:null,c=o(d,'
');o(c,n.loading);const y=o(d,'
\n
\n
\n
'),f=r(".artplayer-plugin-ads-close",y),g=r(".artplayer-plugin-ads-countdown",y),v=o(d,`
\n
${i.i18n.detail}
\n
\n
\n
`),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=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:none;position:absolute;top:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-timer>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control{display:none;position:absolute;bottom:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-control>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg{width:20px;height:20px}.artplayer-plugin-ads .artplayer-plugin-ads-loading{display:flex;align-items:center;justify-content:center;position:absolute;inset:0;width:100%;height:100%}",document.head.appendChild(e)}return function(e){return n=>{const t=n.constructor;if("function"!==typeof t.validator||!["append","query","setStyle"].every(e=>"function"===typeof Reflect.get(t.utils||{},e)))throw new Error("Artplayer Ads requires the core validator and DOM utilities");const l=function(e,n){return n({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"}})}(e,t.validator),{volume:a,volumeClose:o,fullscreenOn:r,fullscreenOff:s,loading:d}=n.icons;return i(n,l,{volume:a,volumeClose:o,fullscreenOn:r,fullscreenOff:s,loading:d},t.utils)}}}); +!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,'
');a(d);const u=o(d,i.video?'':`
${i.html}
`),p=i.video?u:null,c=o(d,'
');o(c,n.loading);const y=o(d,'
\n
\n
\n
'),f=r(".artplayer-plugin-ads-close",y),g=r(".artplayer-plugin-ads-countdown",y),v=o(d,`
\n
${i.i18n.detail}
\n
\n
\n
`),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)))throw new Error("Artplayer Ads requires the core validator and DOM utilities");const l=function(e,n){return n({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"}})}(e,t.validator),{volume:a,volumeClose:o,fullscreenOn:r,fullscreenOff:s,loading:d}=n.icons;return i(n,l,{volume:a,volumeClose:o,fullscreenOn:r,fullscreenOff:s,loading:d},t.utils)}}const a=Object.assign(l,{default:l});if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-ads")){const e=document.createElement("style");e.id="artplayer-plugin-ads",e.textContent=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:none;position:absolute;top:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-timer>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control{display:none;position:absolute;bottom:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-control>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg{width:20px;height:20px}.artplayer-plugin-ads .artplayer-plugin-ads-loading{display:flex;align-items:center;justify-content:center;position:absolute;inset:0;width:100%;height:100%}",document.head.appendChild(e)}return a}); diff --git a/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.legacy.js b/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.legacy.js index 0c4f02174..56f333132 100644 --- a/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.legacy.js +++ b/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.legacy.js @@ -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,'
');i(d);const u=r(d,l.video?'':`
${l.html}
`),p=l.video?u:null,c=r(d,'
');r(c,n.loading);const y=r(d,'
\n
\n
\n
'),f=o(".artplayer-plugin-ads-close",y),g=o(".artplayer-plugin-ads-countdown",y),v=r(d,`
\n
${l.i18n.detail}
\n
\n
\n
`),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)},fullscreen(){e.fullscreen=!e.fullscreen,m()&&r.fullscreen(e.fullscreen)}}),i.on("fullscreen",()=>r.fullscreen(e.fullscreen));const c=()=>a.hidden?v.pause():v.play();i.dom(a,"visibilitychange",c),i.on("document:visibilitychange",c);const y=()=>{if(m()&&!g){if(g=!0,v.start(),a.hidden&&v.pause(),r.video){const e=r.video;x(()=>e.play(),e=>{m()&&(o(e),w())},()=>b(e))}m()&&r.ready()}};r.video?(i.dom(r.video,"error",w),i.dom(r.video,"loadedmetadata",y),r.video.src=n.video):y()}catch(r){throw k(),r}}}try{r.on("destroy",k),i.on("ready",()=>{f||"waiting"!==c||(f=!0,i.on("play",O),i.on("video:playing",O))}),e.isDestroy&&k()}catch(A){throw k(),A}return{name:"artplayerPluginAds",skip:w,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=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:none;position:absolute;top:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-timer>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control{display:none;position:absolute;bottom:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-control>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg{width:20px;height:20px}.artplayer-plugin-ads .artplayer-plugin-ads-loading{display:flex;align-items:center;justify-content:center;position:absolute;inset:0;width:100%;height:100%}",document.head.appendChild(e)}return function(e){return n=>{const l=n.constructor;if("function"!==typeof l.validator||!["append","query","setStyle"].every(e=>"function"===typeof Reflect.get(l.utils||{},e)))throw new Error("Artplayer Ads requires the core validator and DOM utilities");const t=r(e,l.validator),{volume:i,volumeClose:o,fullscreenOn:a,fullscreenOff:s,loading:u}=n.icons;return d(n,t,{volume:i,volumeClose:o,fullscreenOn:a,fullscreenOff:s,loading:u},l.utils)}}}); +!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,'
');i(d);const u=r(d,t.video?'':`
${t.html}
`),p=t.video?u:null,c=r(d,'
');r(c,n.loading);const y=r(d,'
\n
\n
\n
'),f=o(".artplayer-plugin-ads-close",y),g=o(".artplayer-plugin-ads-countdown",y),v=r(d,`
\n
${t.i18n.detail}
\n
\n
\n
`),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)},fullscreen(){e.fullscreen=!e.fullscreen,m()&&r.fullscreen(e.fullscreen)}}),i.on("fullscreen",()=>r.fullscreen(e.fullscreen));const c=()=>a.hidden?v.pause():v.play();i.dom(a,"visibilitychange",c),i.on("document:visibilitychange",c);const y=()=>{if(m()&&!g){if(g=!0,v.start(),a.hidden&&v.pause(),r.video){const e=r.video;x(()=>e.play(),e=>{m()&&(o(e),w())},()=>b(e))}m()&&r.ready()}};r.video?(i.dom(r.video,"error",w),i.dom(r.video,"loadedmetadata",y),r.video.src=n.video):y()}catch(r){throw k(),r}}}try{r.on("destroy",k),i.on("ready",()=>{f||"waiting"!==c||(f=!0,i.on("play",O),i.on("video:playing",O))}),e.isDestroy&&k()}catch(A){throw k(),A}return{name:"artplayerPluginAds",skip:w,pause:v.pause,play:v.play}}function u(e){return n=>{const t=n.constructor;if("function"!==typeof t.validator||!["append","query","setStyle"].every(e=>"function"===typeof Reflect.get(t.utils||{},e)))throw new Error("Artplayer Ads requires the core validator and DOM utilities");const l=r(e,t.validator),{volume:i,volumeClose:o,fullscreenOn:a,fullscreenOff:s,loading:u}=n.icons;return d(n,l,{volume:i,volumeClose:o,fullscreenOn:a,fullscreenOff:s,loading:u},t.utils)}}const p=Object.assign(u,{default:u});if("undefined"!==typeof document&&!document.getElementById("artplayer-plugin-ads")){const e=document.createElement("style");e.id="artplayer-plugin-ads",e.textContent=".artplayer-plugin-ads{position:absolute;z-index:150;inset:0;width:100%;height:100%;overflow:hidden;font-size:13px;line-height:1;color:#fff;background-color:#000}.artplayer-plugin-ads .artplayer-plugin-ads-html{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-video{width:100%;height:100%}.artplayer-plugin-ads .artplayer-plugin-ads-timer{display:none;position:absolute;top:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-timer>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control{display:none;position:absolute;bottom:10px;right:10px}.artplayer-plugin-ads .artplayer-plugin-ads-control>div{display:flex;align-items:center;background-color:#00000080;border-radius:15px;margin-left:5px;padding:5px 10px;cursor:pointer}.artplayer-plugin-ads .artplayer-plugin-ads-control .art-icon svg{width:20px;height:20px}.artplayer-plugin-ads .artplayer-plugin-ads-loading{display:flex;align-items:center;justify-content:center;position:absolute;inset:0;width:100%;height:100%}",document.head.appendChild(e)}return p}); diff --git a/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.mjs b/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.mjs index b402d67a5..73386eb0d 100644 --- a/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.mjs +++ b/packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.mjs @@ -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 }; diff --git a/packages/artplayer-plugin-ads/package.json b/packages/artplayer-plugin-ads/package.json index ae0abe798..8820322b0 100644 --- a/packages/artplayer-plugin-ads/package.json +++ b/packages/artplayer-plugin-ads/package.json @@ -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" + ] + } + } } diff --git a/packages/artplayer-plugin-ads/src/index.ts b/packages/artplayer-plugin-ads/src/index.ts index ba3127508..6eb4b595e 100644 --- a/packages/artplayer-plugin-ads/src/index.ts +++ b/packages/artplayer-plugin-ads/src/index.ts @@ -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' diff --git a/packages/artplayer-plugin-ads/src/options.ts b/packages/artplayer-plugin-ads/src/options.ts index 9d21ef27d..93110b881 100644 --- a/packages/artplayer-plugin-ads/src/options.ts +++ b/packages/artplayer-plugin-ads/src/options.ts @@ -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 } diff --git a/packages/artplayer-plugin-ads/src/types.ts b/packages/artplayer-plugin-ads/src/types.ts index 040420755..8918ffecd 100644 --- a/packages/artplayer-plugin-ads/src/types.ts +++ b/packages/artplayer-plugin-ads/src/types.ts @@ -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 +export type Input = CompatOption export type Host = Pick export type Utilities = Pick export type Icons = Pick export type Cleanup = () => void -export interface Result { - name: 'artplayerPluginAds' - skip: Cleanup - pause: Cleanup - play: Cleanup -} +export type Result = PublicResult diff --git a/packages/artplayer-plugin-ads/tsconfig.json b/packages/artplayer-plugin-ads/tsconfig.json index c9218405d..bba301755 100644 --- a/packages/artplayer-plugin-ads/tsconfig.json +++ b/packages/artplayer-plugin-ads/tsconfig.json @@ -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"] } diff --git a/packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.cts b/packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.cts new file mode 100644 index 000000000..6054ce616 --- /dev/null +++ b/packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.cts @@ -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 diff --git a/packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.mts b/packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.mts new file mode 100644 index 000000000..c727752f8 --- /dev/null +++ b/packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.mts @@ -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' diff --git a/packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.ts b/packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.ts index d09720bbf..b98001092 100644 --- a/packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.ts +++ b/packages/artplayer-plugin-ads/types/artplayer-plugin-ads.d.ts @@ -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 { + /** @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 { + /** @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 diff --git a/packages/artplayer-plugin-ads/types/runtime/index.d.cts b/packages/artplayer-plugin-ads/types/runtime/index.d.cts new file mode 100644 index 000000000..7c305bf4c --- /dev/null +++ b/packages/artplayer-plugin-ads/types/runtime/index.d.cts @@ -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 diff --git a/packages/artplayer-plugin-ads/types/runtime/index.d.mts b/packages/artplayer-plugin-ads/types/runtime/index.d.mts new file mode 100644 index 000000000..a1d627b35 --- /dev/null +++ b/packages/artplayer-plugin-ads/types/runtime/index.d.mts @@ -0,0 +1,2 @@ +export type { Option, Result, RuntimeFactory, Translations } from '../artplayer-plugin-ads.js' +export { default } from './index.js' diff --git a/packages/artplayer-plugin-ads/types/runtime/index.d.ts b/packages/artplayer-plugin-ads/types/runtime/index.d.ts new file mode 100644 index 000000000..5c7033f27 --- /dev/null +++ b/packages/artplayer-plugin-ads/types/runtime/index.d.ts @@ -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 diff --git a/refactor/ads-validation.md b/refactor/ads-validation.md index 2d0273296..e830dc20a 100644 --- a/refactor/ads-validation.md +++ b/refactor/ads-validation.md @@ -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 宿主。 diff --git a/refactor/baselines/ads-types-validation.json b/refactor/baselines/ads-types-validation.json new file mode 100644 index 000000000..4aef5b648 --- /dev/null +++ b/refactor/baselines/ads-types-validation.json @@ -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." + ] +} diff --git a/refactor/changes/2026-09-12-PKG-ADS-04-types.md b/refactor/changes/2026-09-12-PKG-ADS-04-types.md new file mode 100644 index 000000000..d2e1673d5 --- /dev/null +++ b/refactor/changes/2026-09-12-PKG-ADS-04-types.md @@ -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[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、合并或发布。 diff --git a/refactor/fixtures/consumers/ads-published.ts b/refactor/fixtures/consumers/ads-published.ts new file mode 100644 index 000000000..ce1133696 --- /dev/null +++ b/refactor/fixtures/consumers/ads-published.ts @@ -0,0 +1,8 @@ +import ads from 'artplayer-plugin-ads' + +const option: Parameters[0] = { html: 'ad', video: 'ad.mp4', url: '/ad', totalDuration: '10', playDuration: 2, muted: true } +const plugin = ads(option) +const resultName: ReturnType['name'] = 'artplayerPluginAds' +const result: ReturnType = { name: resultName, skip() {}, pause() {}, play() {} } +const returns: void[] = [result.skip(), result.pause(), result.play()] +void [returns, plugin] diff --git a/refactor/fixtures/consumers/ads-workspace.ts b/refactor/fixtures/consumers/ads-workspace.ts new file mode 100644 index 000000000..cea2ba215 --- /dev/null +++ b/refactor/fixtures/consumers/ads-workspace.ts @@ -0,0 +1,8 @@ +import ads from 'artplayer-plugin-ads' + +const option: Parameters[0] = { source: 'ad.mp4', type: 'video', totalDuration: 10, playDuration: 2, muted: true } +const plugin = ads(option) +const resultName: ReturnType['name'] = 'artplayerPluginAds' +const result: ReturnType = { name: resultName, skip() {}, pause() {}, play() {} } +const returns: void[] = [result.skip(), result.pause(), result.play()] +void [returns, plugin] diff --git a/refactor/plan.md b/refactor/plan.md index 08b574b36..75abc9e6a 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -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
核对包契约与历史用法 | BASE-05 | 核对 npm 1.0.0~1.0.6 与未发布工作区 2.1.0 的真实参数、类型、结果、倒计时、分发和旧核心来源 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done | | PKG-ADS-02 | artplayer-plugin-ads
建立特有行为与错误测试 | PKG-ADS-01, ENG-03, ENG-05 | 固定 npm 1.0.6 与工作区契约;HTML/视频/图片 HTML、跳过阈值、计时链、工厂复用、多实例、过早/重复方法、播放拒绝与销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done | | PKG-ADS-03 | artplayer-plugin-ads
整理内部职责与资源 | PKG-ADS-02, CORE-10, CORE-13 | 配置/广告状态/计时/视图/主视频恢复分离,保留产品规则;资源与版本检查改用经验证的旧核心能力边界 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done | -| PKG-ADS-04 | artplayer-plugin-ads
迁移自有源码和公开类型 | 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
迁移自有源码和公开类型 | 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
验证新旧核心和组合 | PKG-ADS-04, CORE-22 | 本地广告 fixture、多实例、正常结束/跳过/销毁;核验历史关联核心 4.5.5、发布 5.4.1 与候选核心的实际归档/能力/媒体组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | | PKG-ADS-06 | artplayer-plugin-ads
验证分发并同步文档 | 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) diff --git a/refactor/progress.md b/refactor/progress.md index 16638c2b8..7d4975f59 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -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 和入口。 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index 2e21ff07c..4af7c6277 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -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 | diff --git a/refactor/risks.json b/refactor/risks.json index 0ff1e252b..ec18d165e 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -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 的旧/新导入、浏览器与声明入口全部验证,版本占用再次核实。" }, diff --git a/refactor/scripts/ads-package-types.mjs b/refactor/scripts/ads-package-types.mjs new file mode 100644 index 000000000..05b2dda91 --- /dev/null +++ b/refactor/scripts/ads-package-types.mjs @@ -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 +}) diff --git a/refactor/scripts/ads-types.test.mjs b/refactor/scripts/ads-types.test.mjs new file mode 100644 index 000000000..b8b72561a --- /dev/null +++ b/refactor/scripts/ads-types.test.mjs @@ -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[0]; const duration: string | undefined = input.totalDuration; void duration;`], + ['workspace', workspace, `declare const input: Parameters[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/) +}) diff --git a/refactor/tasks.json b/refactor/tasks.json index 8516e98ff..18c9ef21c 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -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", diff --git a/refactor/typechecking.md b/refactor/typechecking.md index e77375d48..aa9418cd4 100644 --- a/refactor/typechecking.md +++ b/refactor/typechecking.md @@ -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 对五组编译模式检查正反例, diff --git a/scripts/build-ts.js b/scripts/build-ts.js index 05ddb17dc..faf8fddd4 100644 --- a/scripts/build-ts.js +++ b/scripts/build-ts.js @@ -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` diff --git a/scripts/plugin-editor-types.mjs b/scripts/plugin-editor-types.mjs index bed9e79ba..940fc4ce3 100644 --- a/scripts/plugin-editor-types.mjs +++ b/scripts/plugin-editor-types.mjs @@ -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([ diff --git a/scripts/typecheck.mjs b/scripts/typecheck.mjs index 82611376d..db34843f2 100644 --- a/scripts/typecheck.mjs +++ b/scripts/typecheck.mjs @@ -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') diff --git a/test/ads.test.js b/test/ads.test.js index ce632c349..57ae2d2a5 100644 --- a/test/ads.test.js +++ b/test/ads.test.js @@ -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) diff --git a/test/browser/ads-editor-types.spec.js b/test/browser/ads-editor-types.spec.js new file mode 100644 index 000000000..adf7c90ac --- /dev/null +++ b/test/browser/ads-editor-types.spec.js @@ -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: 'ad', 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[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) +}) diff --git a/test/types/ads-source.ts b/test/types/ads-source.ts new file mode 100644 index 000000000..45a73dda6 --- /dev/null +++ b/test/types/ads-source.ts @@ -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] diff --git a/test/types/ads.ts b/test/types/ads.ts new file mode 100644 index 000000000..e5a76f0c5 --- /dev/null +++ b/test/types/ads.ts @@ -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: 'ad', 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[0] = oldPublished +const inferredWorkspace: Parameters[0] = oldWorkspace +const raw: Parameters[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 = 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]