refactor(vast): [PKG-VAST-03] isolate lifecycle ownership checkpoint

This commit is contained in:
Harvey Zhao committed 2026-09-12 09:33:54 +08:00
1 parent d5e1feadf5
commit 4f324a8438
30 files changed
+1453 -383

No files matched your search

@@ -0,0 +1,76 @@
# VAST implementation and maintenance
This is the PKG-VAST-03 implementation checkpoint. The published-versus-workspace
initialization conflict remains open; this checkpoint is not release acceptance.
## Module map
- `src/index.ts` owns the core destroy subscription before SDK loading begins,
awaits SDK loading and the user callback, and preserves original rejection values.
- `src/sdk.ts` is the only runtime Glomex import boundary. It also constructs IMA
requests, preserving own/inherited configuration fields and primary-field overrides.
- `src/session.ts` owns one current SDK/container pair, the four ad-event subscriptions,
active-ad request suppression, initialization rollback and explicit recreation.
- `src/view.ts` creates the existing overlay styles and distinct per-module container
IDs, including multiple instances initialized in the same millisecond.
- `src/types.ts` describes the implementation context and SDK boundary. Public legacy
declarations in `types/` remain separate pending PKG-VAST-04 reconciliation.
Dependency direction is entry -> session -> SDK/view/types. The session passes the
original `art` through the callback for compatibility; only template elements are
used for SDK allocation. Constructor utility access is narrowed once at the entry.
## Lifetime and errors
The entry subscribes before awaiting the shared SDK loader. Core destruction makes
that attachment terminal, removes the subscription and releases the current session.
It does not cancel the shared IMA script load for other instances. A late completion
does not invoke the callback or allocate a player. SDK failures still reject with
their original value. Callback failure closes the attachment and releases resources;
secondary cleanup failures are logged without replacing the original error.
Explicit plugin `destroy()` releases the current ad session, resets active-ad state,
and permits a later `init`/`playUrl`/`playRes` while the core remains alive. Core destroy
and failed attachment are terminal: request methods are inert and `init()` returns
null. Public declaration accuracy for this invalid-lifetime case belongs to04.
Each session has an owner identity. Its callbacks verify that identity and core
liveness before changing visibility. Cleanup detaches the current owner and clears
references before invoking SDK code, removes each subscription, destroys the SDK and
removes the container even when another cleanup operation throws. A cleanup exception
is observable, but cannot leave the wrapper pointing at a half-destroyed player.
Initialization is guarded against reentrancy. Constructor/subscription failure rolls
back; destruction reentered during constructor completion releases the late player.
Config accessors can execute user code, so request dispatch verifies owner identity
again after request construction. Cleanup itself prevents reentrant recreation until
it finishes. SDK content pause/resume behavior is not duplicated in this wrapper.
## Compatibility and remaining work
The checkpoint retains the workspace callback order, lazy initialization, mutable
settings/options identities, default flags, void request methods, and asynchronous
outer attachment. The historical namespace export, `id`/`$container` aliases, eager
published initialization and contradictory public return types are not yet reconciled.
See [the explicit decision](../../refactor/vast-compatibility-decision.md).
The former timestamp-only overlay ID now has a monotonic suffix to avoid same-tick
collisions; the existing `art-vast-` prefix and CSS styles remain. IDs are internal
in the workspace context; published ID compatibility is tracked with the above decision.
`@alugha/ima@2.1.0` is a direct pinned dependency because Glomex declarations import its
types while Glomex lists it only as a development dependency. Runtime imports continue
to use Glomex1.21.2. No new direct remote loader or SDK major upgrade was introduced.
## Validation
Run `yarn test:vast`, `yarn typecheck`, `yarn build artplayer-plugin-vast`, and
`yarn test:browser vast.spec.js` using the repository pinned toolchain. The node and
browser wrapper tests substitute only the Glomex SDK boundary. They cover current and
frozen historical sources, real core5.1.7/5.4.1/candidate registration, real DOM and
main-video decoding; they do not certify Google IMA ads or final npm distribution.
For state or cleanup changes start in session, for callback timing in entry, and for
SDK request fields in sdk. Keep Node candidate assertions, historical observations,
browser counterparts and [test maintenance](../../refactor/vast-validation.md) aligned.
Actual IMA/media/device validation remains05, public types04, and full distribution06.
+5
View File
@@ -2,6 +2,11 @@
VAST plugin for ArtPlayer
## Maintenance
Implementation modules, lifecycle ownership, test commands and pending compatibility
decisions are documented in [ARCHITECTURE.md](ARCHITECTURE.md).
## Demo
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-vast/index.js&example=vast)
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVast=e(),define(function(){return t.artplayerPluginVast})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVast=e()}(this,function(){"use strict";var t=null,e=function(){t=null};function i(t,e,i){return e&&function(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}(t.prototype,e),t}function n(){return n=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(t[n]=i[n])}return t},n.apply(this,arguments)}function s(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,o(t,e)}function r(t){return(r=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function o(t,e){return(o=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function a(t,e,i){return(a=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(t){return!1}}()?Reflect.construct:function(t,e,i){var n=[null];n.push.apply(n,e);var s=new(Function.bind.apply(t,n));return i&&o(s,i.prototype),s}).apply(null,arguments)}function d(t){var e="function"==typeof Map?new Map:void 0;return d=function(t){if(null===t||-1===Function.toString.call(t).indexOf("[native code]"))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==e){if(e.has(t))return e.get(t);e.set(t,i)}function i(){return a(t,arguments,r(this).constructor)}return i.prototype=Object.create(t.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),o(i,t)},d(t)}function h(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function u(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,n=new Array(e);i<e;i++)n[i]=t[i];return n}var l=0;function c(t){return"__private_"+l+++"_"+t}function f(t,e){if(!Object.prototype.hasOwnProperty.call(t,e))throw new TypeError("attempted to use private field on non-instance");return t}var v={};v.CustomEvent="function"==typeof CustomEvent?CustomEvent:function(t){return e[t]=new e("").constructor[t],e;function e(t,e){e||(e={});var i=document.createEvent("CustomEvent");return i.initCustomEvent(t,!!e.bubbles,!!e.cancelable,e.detail),i}}("prototype");var p,E,y,A=v.CustomEvent,m=c("mediaElement"),b=c("currentTime"),g=c("enabled"),O=function(){function t(t){Object.defineProperty(this,m,{writable:!0,value:void 0}),Object.defineProperty(this,b,{writable:!0,value:void 0}),Object.defineProperty(this,g,{writable:!0,value:void 0}),this.seeking=void 0,f(this,m)[m]=t,f(this,b)[b]=0,f(this,g)[g]=!1,this.seeking=!1,this.t=this.t.bind(this),this.i=this.i.bind(this),this.h=this.h.bind(this),this.enable()}var e=t.prototype;return e.i=function(){this.seeking=!0},e.h=function(){this.seeking=!1},e.t=function(){var t,e;this.seeking||null!=(t=f(this,m)[m])&&t.paused||(f(this,b)[b]=null==(e=f(this,m)[m])?void 0:e.currentTime)},e.enable=function(){var t,e,i;f(this,g)[g]||(null==(t=f(this,m)[m])||t.addEventListener("seeking",this.i),null==(e=f(this,m)[m])||e.addEventListener("seeked",this.h),null==(i=f(this,m)[m])||i.addEventListener("timeupdate",this.t),f(this,g)[g]=!0)},e.disable=function(){var t,e,i;f(this,g)[g]&&(null==(t=f(this,m)[m])||t.removeEventListener("seeking",this.i),null==(e=f(this,m)[m])||e.removeEventListener("seeked",this.h),null==(i=f(this,m)[m])||i.removeEventListener("timeupdate",this.t),f(this,g)[g]=!1)},e.play=function(){var t=this;return new Promise(function(e){var i;e(null==(i=f(t,m)[m])?void 0:i.play())})},e.pause=function(){var t;null==(t=f(this,m)[m])||t.pause()},e.reset=function(){f(this,b)[b]=0,f(this,g)[g]=!1,this.seeking=!1,this.enable()},e.destroy=function(){f(this,b)[b]=0,f(this,g)[g]=!1,this.seeking=!1,this.disable(),f(this,m)[m]=void 0},i(t,[{key:"enabled",get:function(){return f(this,g)[g]}},{key:"currentTime",get:function(){return f(this,b)[b]}},{key:"duration",get:function(){var t;return null==(t=f(this,m)[m])?void 0:t.duration}},{key:"muted",get:function(){var t;return null==(t=f(this,m)[m])?void 0:t.muted}},{key:"volume",get:function(){var t;return null==(t=f(this,m)[m])?void 0:t.volume}}]),t}(),w=function(){function t(){this.delegate=document.createDocumentFragment()}var e=t.prototype;return e.addEventListener=function(){this.delegate.addEventListener.apply(this.delegate,[].slice.call(arguments))},e.dispatchEvent=function(){return this.delegate.dispatchEvent.apply(this.delegate,[].slice.call(arguments))},e.removeEventListener=function(){return this.delegate.removeEventListener.apply(this.delegate,[].slicLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVast=e(),define(function(){return t.artplayerPluginVast})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVast=e()}(this,function(){"use strict";var t=null,e=function(){t=null};function i(t,e,i){return e&&function(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}(t.prototype,e),t}function n(){return n=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(t[n]=i[n])}return t},n.apply(this,arguments)}function r(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,o(t,e)}function s(t){return(s=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function o(t,e){return(o=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function a(t,e,i){return(a=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(t){return!1}}()?Reflect.construct:function(t,e,i){var n=[null];n.push.apply(n,e);var r=new(Function.bind.apply(t,n));return i&&o(r,i.prototype),r}).apply(null,arguments)}function u(t){var e="function"==typeof Map?new Map:void 0;return u=function(t){if(null===t||-1===Function.toString.call(t).indexOf("[native code]"))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==e){if(e.has(t))return e.get(t);e.set(t,i)}function i(){return a(t,arguments,s(this).constructor)}return i.prototype=Object.create(t.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),o(i,t)},u(t)}function h(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function d(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,n=new Array(e);i<e;i++)n[i]=t[i];return n}var l=0;function c(t){return"__private_"+l+++"_"+t}function f(t,e){if(!Object.prototype.hasOwnProperty.call(t,e))throw new TypeError("attempted to use private field on non-instance");return t}var p={};p.CustomEvent="function"==typeof CustomEvent?CustomEvent:function(t){return e[t]=new e("").constructor[t],e;function e(t,e){e||(e={});var i=document.createEvent("CustomEvent");return i.initCustomEvent(t,!!e.bubbles,!!e.cancelable,e.detail),i}}("prototype");var v,y,E,A=p.CustomEvent,m=c("mediaElement"),b=c("currentTime"),g=c("enabled"),O=function(){function t(t){Object.defineProperty(this,m,{writable:!0,value:void 0}),Object.defineProperty(this,b,{writable:!0,value:void 0}),Object.defineProperty(this,g,{writable:!0,value:void 0}),this.seeking=void 0,f(this,m)[m]=t,f(this,b)[b]=0,f(this,g)[g]=!1,this.seeking=!1,this.t=this.t.bind(this),this.i=this.i.bind(this),this.h=this.h.bind(this),this.enable()}var e=t.prototype;return e.i=function(){this.seeking=!0},e.h=function(){this.seeking=!1},e.t=function(){var t,e;this.seeking||null!=(t=f(this,m)[m])&&t.paused||(f(this,b)[b]=null==(e=f(this,m)[m])?void 0:e.currentTime)},e.enable=function(){var t,e,i;f(this,g)[g]||(null==(t=f(this,m)[m])||t.addEventListener("seeking",this.i),null==(e=f(this,m)[m])||e.addEventListener("seeked",this.h),null==(i=f(this,m)[m])||i.addEventListener("timeupdate",this.t),f(this,g)[g]=!0)},e.disable=function(){var t,e,i;f(this,g)[g]&&(null==(t=f(this,m)[m])||t.removeEventListener("seeking",this.i),null==(e=f(this,m)[m])||e.removeEventListener("seeked",this.h),null==(i=f(this,m)[m])||i.removeEventListener("timeupdate",this.t),f(this,g)[g]=!1)},e.play=function(){var t=this;return new Promise(function(e){var i;e(null==(i=f(t,m)[m])?void 0:i.play())})},e.pause=function(){var t;null==(t=f(this,m)[m])||t.pause()},e.reset=function(){f(this,b)[b]=0,f(this,g)[g]=!1,this.seeking=!1,this.enable()},e.destroy=function(){f(this,b)[b]=0,f(this,g)[g]=!1,this.seeking=!1,this.disable(),f(this,m)[m]=void 0},i(t,[{key:"enabled",get:function(){return f(this,g)[g]}},{key:"currentTime",get:function(){return f(this,b)[b]}},{key:"duration",get:function(){var t;return null==(t=f(this,m)[m])?void 0:t.duration}},{key:"muted",get:function(){var t;return null==(t=f(this,m)[m])?void 0:t.muted}},{key:"volume",get:function(){var t;return null==(t=f(this,m)[m])?void 0:t.volume}}]),t}(),w=function(){function t(){this.delegate=document.createDocumentFragment()}var e=t.prototype;return e.addEventListener=function(){this.delegate.addEventListener.apply(this.delegate,[].slice.call(arguments))},e.dispatchEvent=function(){return this.delegate.dispatchEvent.apply(this.delegate,[].slice.call(arguments))},e.removeEventListener=function(){return this.delegate.removeEventListener.apply(this.delegate,[].slicLine truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVast=e(),define(function(){return t.artplayerPluginVast})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVast=e()}(this,function(){"use strict";var t=null,e=function(){t=null};function i(t,e,i){return e&&function(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}(t.prototype,e),t}function n(){return n=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(t[n]=i[n])}return t},n.apply(this,arguments)}function s(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,o(t,e)}function r(t){return(r=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function o(t,e){return(o=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function a(t,e,i){return(a=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(t){return!1}}()?Reflect.construct:function(t,e,i){var n=[null];n.push.apply(n,e);var s=new(Function.bind.apply(t,n));return i&&o(s,i.prototype),s}).apply(null,arguments)}function d(t){var e="function"==typeof Map?new Map:void 0;return d=function(t){if(null===t||-1===Function.toString.call(t).indexOf("[native code]"))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==e){if(e.has(t))return e.get(t);e.set(t,i)}function i(){return a(t,arguments,r(this).constructor)}return i.prototype=Object.create(t.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),o(i,t)},d(t)}function h(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function u(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,n=new Array(e);i<e;i++)n[i]=t[i];return n}var l=0;function c(t){return"__private_"+l+++"_"+t}function v(t,e){if(!Object.prototype.hasOwnProperty.call(t,e))throw new TypeError("attempted to use private field on non-instance");return t}var f={};f.CustomEvent="function"==typeof CustomEvent?CustomEvent:function(t){return e[t]=new e("").constructor[t],e;function e(t,e){e||(e={});var i=document.createEvent("CustomEvent");return i.initCustomEvent(t,!!e.bubbles,!!e.cancelable,e.detail),i}}("prototype");var p,y,E,A=f.CustomEvent,m=c("mediaElement"),b=c("currentTime"),g=c("enabled"),O=function(){function t(t){Object.defineProperty(this,m,{writable:!0,value:void 0}),Object.defineProperty(this,b,{writable:!0,value:void 0}),Object.defineProperty(this,g,{writable:!0,value:void 0}),this.seeking=void 0,v(this,m)[m]=t,v(this,b)[b]=0,v(this,g)[g]=!1,this.seeking=!1,this.t=this.t.bind(this),this.i=this.i.bind(this),this.h=this.h.bind(this),this.enable()}var e=t.prototype;return e.i=function(){this.seeking=!0},e.h=function(){this.seeking=!1},e.t=function(){var t,e;this.seeking||null!=(t=v(this,m)[m])&&t.paused||(v(this,b)[b]=null==(e=v(this,m)[m])?void 0:e.currentTime)},e.enable=function(){var t,e,i;v(this,g)[g]||(null==(t=v(this,m)[m])||t.addEventListener("seeking",this.i),null==(e=v(this,m)[m])||e.addEventListener("seeked",this.h),null==(i=v(this,m)[m])||i.addEventListener("timeupdate",this.t),v(this,g)[g]=!0)},e.disable=function(){var t,e,i;v(this,g)[g]&&(null==(t=v(this,m)[m])||t.removeEventListener("seeking",this.i),null==(e=v(this,m)[m])||e.removeEventListener("seeked",this.h),null==(i=v(this,m)[m])||i.removeEventListener("timeupdate",this.t),v(this,g)[g]=!1)},e.play=function(){var t=this;return new Promise(function(e){var i;e(null==(i=v(t,m)[m])?void 0:i.play())})},e.pause=function(){var t;null==(t=v(this,m)[m])||t.pause()},e.reset=function(){v(this,b)[b]=0,v(this,g)[g]=!1,this.seeking=!1,this.enable()},e.destroy=function(){v(this,b)[b]=0,v(this,g)[g]=!1,this.seeking=!1,this.disable(),v(this,m)[m]=void 0},i(t,[{key:"enabled",get:function(){return v(this,g)[g]}},{key:"currentTime",get:function(){return v(this,b)[b]}},{key:"duration",get:function(){var t;return null==(t=v(this,m)[m])?void 0:t.duration}},{key:"muted",get:function(){var t;return null==(t=v(this,m)[m])?void 0:t.muted}},{key:"volume",get:function(){var t;return null==(t=v(this,m)[m])?void 0:t.volume}}]),t}(),w=function(){function t(){this.delegate=document.createDocumentFragment()}var e=t.prototype;return e.addEventListener=function(){this.delegate.addEventListener.apply(this.delegate,[].slice.call(arguments))},e.dispatchEvent=function(){return this.delegate.dispatchEvent.apply(this.delegate,[].slice.call(arguments))},e.removeEventListener=function(){return this.delegate.removeEventListener.apply(this.delegate,[].slicLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVast=e(),define(function(){return t.artplayerPluginVast})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVast=e()}(this,function(){"use strict";var t=null,e=function(){t=null};function i(t,e,i){return e&&function(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}(t.prototype,e),t}function n(){return n=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(t[n]=i[n])}return t},n.apply(this,arguments)}function r(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,o(t,e)}function s(t){return(s=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function o(t,e){return(o=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function a(t,e,i){return(a=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(t){return!1}}()?Reflect.construct:function(t,e,i){var n=[null];n.push.apply(n,e);var r=new(Function.bind.apply(t,n));return i&&o(r,i.prototype),r}).apply(null,arguments)}function u(t){var e="function"==typeof Map?new Map:void 0;return u=function(t){if(null===t||-1===Function.toString.call(t).indexOf("[native code]"))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==e){if(e.has(t))return e.get(t);e.set(t,i)}function i(){return a(t,arguments,s(this).constructor)}return i.prototype=Object.create(t.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}),o(i,t)},u(t)}function h(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function d(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,n=new Array(e);i<e;i++)n[i]=t[i];return n}var l=0;function c(t){return"__private_"+l+++"_"+t}function f(t,e){if(!Object.prototype.hasOwnProperty.call(t,e))throw new TypeError("attempted to use private field on non-instance");return t}var p={};p.CustomEvent="function"==typeof CustomEvent?CustomEvent:function(t){return e[t]=new e("").constructor[t],e;function e(t,e){e||(e={});var i=document.createEvent("CustomEvent");return i.initCustomEvent(t,!!e.bubbles,!!e.cancelable,e.detail),i}}("prototype");var v,y,E,A=p.CustomEvent,m=c("mediaElement"),b=c("currentTime"),g=c("enabled"),O=function(){function t(t){Object.defineProperty(this,m,{writable:!0,value:void 0}),Object.defineProperty(this,b,{writable:!0,value:void 0}),Object.defineProperty(this,g,{writable:!0,value:void 0}),this.seeking=void 0,f(this,m)[m]=t,f(this,b)[b]=0,f(this,g)[g]=!1,this.seeking=!1,this.t=this.t.bind(this),this.i=this.i.bind(this),this.h=this.h.bind(this),this.enable()}var e=t.prototype;return e.i=function(){this.seeking=!0},e.h=function(){this.seeking=!1},e.t=function(){var t,e;this.seeking||null!=(t=f(this,m)[m])&&t.paused||(f(this,b)[b]=null==(e=f(this,m)[m])?void 0:e.currentTime)},e.enable=function(){var t,e,i;f(this,g)[g]||(null==(t=f(this,m)[m])||t.addEventListener("seeking",this.i),null==(e=f(this,m)[m])||e.addEventListener("seeked",this.h),null==(i=f(this,m)[m])||i.addEventListener("timeupdate",this.t),f(this,g)[g]=!0)},e.disable=function(){var t,e,i;f(this,g)[g]&&(null==(t=f(this,m)[m])||t.removeEventListener("seeking",this.i),null==(e=f(this,m)[m])||e.removeEventListener("seeked",this.h),null==(i=f(this,m)[m])||i.removeEventListener("timeupdate",this.t),f(this,g)[g]=!1)},e.play=function(){var t=this;return new Promise(function(e){var i;e(null==(i=f(t,m)[m])?void 0:i.play())})},e.pause=function(){var t;null==(t=f(this,m)[m])||t.pause()},e.reset=function(){f(this,b)[b]=0,f(this,g)[g]=!1,this.seeking=!1,this.enable()},e.destroy=function(){f(this,b)[b]=0,f(this,g)[g]=!1,this.seeking=!1,this.disable(),f(this,m)[m]=void 0},i(t,[{key:"enabled",get:function(){return f(this,g)[g]}},{key:"currentTime",get:function(){return f(this,b)[b]}},{key:"duration",get:function(){var t;return null==(t=f(this,m)[m])?void 0:t.duration}},{key:"muted",get:function(){var t;return null==(t=f(this,m)[m])?void 0:t.muted}},{key:"volume",get:function(){var t;return null==(t=f(this,m)[m])?void 0:t.volume}}]),t}(),w=function(){function t(){this.delegate=document.createDocumentFragment()}var e=t.prototype;return e.addEventListener=function(){this.delegate.addEventListener.apply(this.delegate,[].slice.call(arguments))},e.dispatchEvent=function(){return this.delegate.dispatchEvent.apply(this.delegate,[].slice.call(arguments))},e.removeEventListener=function(){return this.delegate.removeEventListener.apply(this.delegate,[].slicLine truncated
+218 -118
View File
@@ -98,15 +98,15 @@ function w(t2, i2) {
return t2;
}
var m = {};
m.CustomEvent = "function" == typeof CustomEvent ? CustomEvent : (function(t2) {
m.CustomEvent = "function" == typeof CustomEvent ? CustomEvent : function(t2) {
return i2[t2] = new i2("").constructor[t2], i2;
function i2(t3, i3) {
i3 || (i3 = {});
var e2 = document.createEvent("CustomEvent");
return e2.initCustomEvent(t3, !!i3.bubbles, !!i3.cancelable, i3.detail), e2;
}
})("prototype");
var p, y, O = m.CustomEvent, j = b("mediaElement"), g = b("currentTime"), A = b("enabled"), k = (function() {
}("prototype");
var p, y, O = m.CustomEvent, j = b("mediaElement"), g = b("currentTime"), A = b("enabled"), k = function() {
function t2(t3) {
Object.defineProperty(this, j, { writable: true, value: void 0 }), Object.defineProperty(this, g, { writable: true, value: void 0 }), Object.defineProperty(this, A, { writable: true, value: void 0 }), this.seeking = void 0, w(this, j)[j] = t3, w(this, g)[g] = 0, w(this, A)[A] = false, this.seeking = false, this.t = this.t.bind(this), this.i = this.i.bind(this), this.h = this.h.bind(this), this.enable();
}
@@ -151,7 +151,7 @@ var p, y, O = m.CustomEvent, j = b("mediaElement"), g = b("currentTime"), A = b(
var t3;
return null == (t3 = w(this, j)[j]) ? void 0 : t3.volume;
} }]), t2;
})(), C = (function() {
}(), C = function() {
function t2() {
this.delegate = document.createDocumentFragment();
}
@@ -163,23 +163,23 @@ var p, y, O = m.CustomEvent, j = b("mediaElement"), g = b("currentTime"), A = b(
}, i2.removeEventListener = function() {
return this.delegate.removeEventListener.apply(this.delegate, [].slice.call(arguments));
}, t2;
})(), P = ["abort", "canplay", "canplaythrough", "durationchange", "emptied", "ended", "error", "loadeddata", "loadedmetadata", "loadstart", "pause", "play", "playing", "progress", "ratechange", "seeked", "seeking", "stalled", "suspend", "timeupdate", "volumechange", "waiting"];
!(function(t2) {
}(), P = ["abort", "canplay", "canplaythrough", "durationchange", "emptied", "ended", "error", "loadeddata", "loadedmetadata", "loadstart", "pause", "play", "playing", "progress", "ratechange", "seeked", "seeking", "stalled", "suspend", "timeupdate", "volumechange", "waiting"];
!function(t2) {
t2.MEDIA_START = "MediaStart", t2.MEDIA_IMPRESSION = "MediaImpression", t2.MEDIA_STOP = "MediaStop", t2.MEDIA_CUE_POINTS_CHANGE = "MediaCuePointsChange", t2.MEDIA_RESUMED = "MediaResumed";
})(p || (p = {})), (function(t2) {
}(p || (p = {})), function(t2) {
t2.AD_ERROR = "AdError", t2.AD_BUFFERING = "AdBuffering", t2.LOADED = "AdLoaded", t2.IMPRESSION = "AdImpression", t2.STARTED = "AdStarted", t2.FIRST_QUARTILE = "AdFirstQuartile", t2.MIDPOINT = "AdMidpoint", t2.THIRD_QUARTILE = "AdThirdQuartile", t2.AD_PROGRESS = "AdProgress", t2.COMPLETE = "AdComplete", t2.CLICK = "AdClick", t2.PAUSED = "AdPaused", t2.RESUMED = "AdResumed", t2.SKIPPED = "AdSkipped", t2.SKIPPABLE_STATE_CHANGED = "AdSkippableStateChanged", t2.VOLUME_CHANGED = "AdVolumeChanged", t2.VOLUME_MUTED = "AdMuted", t2.AD_METADATA = "AdMetadata", t2.AD_BREAK_READY = "AdBreakReady", t2.CONTENT_PAUSE_REQUESTED = "AdContentPauseRequested", t2.CONTENT_RESUME_REQUESTED = "AdContentResumeRequested", t2.ALL_ADS_COMPLETED = "AdAllAdsCompleted", t2.DURATION_CHANGE = "AdDurationChange", t2.INTERACTION = "AdInteraction", t2.LINEAR_CHANGED = "AdLinearChanged", t2.LOG = "AdLog", t2.USER_CLOSE = "AdUserClose", t2.AD_CAN_PLAY = "AdCanPlay", t2.EXPANDED_CHANGED = "AdExpandedChanged", t2.VIEWABLE_IMPRESSION = "AdViewableImpression";
})(y || (y = {}));
}(y || (y = {}));
var R = h({}, y, p), E = function() {
this.disableCustomPlaybackForIOS10Plus = false, this.autoResize = true, this.clickTrackingElement = void 0;
}, T = (function(t2) {
}, T = function(t2) {
function i2() {
var i3;
return (i3 = t2.call.apply(t2, [this].concat([].slice.call(arguments))) || this).errorCode = void 0, i3.innerError = void 0, i3.type = void 0, i3.vastErrorCode = void 0, i3;
}
return r(i2, t2), i2;
})(d(Error));
}(d(Error));
T.ERROR_CODE_ADS_MANAGER_LOADED_TIMEOUT = 9e3, T.ERROR_CODE_REQUEST_ADS_TIMEOUT = 9001;
var S = b("mediaElement"), M = b("adElement"), x = b("customPlayhead"), I = b("adsRenderingSettings"), B = b("ima"), L = b("adDisplayContainer"), D = b("adsManager"), W = b("width"), _ = b("height"), q = b("adsLoader"), F = b("playerOptions"), N = b("resizeObserver"), V = b("currentAd"), G = b("loadedAd"), Q = b("mediaStartTriggered"), U = b("mediaImpressionTriggered"), z = b("mediaInActivation"), Z = b("customPlaybackTimeAdjustedOnEnded"), $ = b("cuePoints"), H = b("adCurrentTime"), J = b("adDuration"), K = b("startAdCallback"), X = b("adsManagerLoadedTimeout"), Y = b("requestAdsTimeout"), tt = b("wasExternallyPaused"), it = b("lastNonZeroAdVolume"), et = b("activatePromise"), nt = (function(t2) {
var S = b("mediaElement"), M = b("adElement"), x = b("customPlayhead"), I = b("adsRenderingSettings"), B = b("ima"), L = b("adDisplayContainer"), D = b("adsManager"), W = b("width"), _ = b("height"), q = b("adsLoader"), F = b("playerOptions"), N = b("resizeObserver"), V = b("currentAd"), G = b("loadedAd"), Q = b("mediaStartTriggered"), U = b("mediaImpressionTriggered"), z = b("mediaInActivation"), Z = b("customPlaybackTimeAdjustedOnEnded"), $ = b("cuePoints"), H = b("adCurrentTime"), J = b("adDuration"), K = b("startAdCallback"), X = b("adsManagerLoadedTimeout"), Y = b("requestAdsTimeout"), tt = b("wasExternallyPaused"), it = b("lastNonZeroAdVolume"), et = b("activatePromise"), nt = function(t2) {
function i2(i3, e3, n2, s2, h2) {
var r2;
void 0 === s2 && (s2 = new i3.AdsRenderingSettings()), void 0 === h2 && (h2 = new E()), r2 = t2.call(this) || this, Object.defineProperty(l(r2), S, { writable: true, value: void 0 }), Object.defineProperty(l(r2), M, { writable: true, value: void 0 }), Object.defineProperty(l(r2), x, { writable: true, value: void 0 }), Object.defineProperty(l(r2), I, { writable: true, value: void 0 }), Object.defineProperty(l(r2), B, { writable: true, value: void 0 }), Object.defineProperty(l(r2), L, { writable: true, value: void 0 }), Object.defineProperty(l(r2), D, { writable: true, value: void 0 }), Object.defineProperty(l(r2), W, { writable: true, value: void 0 }), Object.defineProperty(l(r2), _, { writable: true, value: void 0 }), Object.defineProperty(l(r2), q, { writable: true, value: void 0 }), Object.defineProperty(l(r2), F, { writable: true, value: void 0 }), Object.defineProperty(l(r2), N, { writable: true, value: void 0 }), Object.defineProperty(l(r2), V, { writable: true, value: void 0 }), Object.defineProperty(l(r2), G, { writable: true, value: void 0 }), Object.defineProperty(l(r2), Q, { writable: true, value: false }), Object.defineProperty(l(r2), U, { writable: true, value: false }), Object.defineProperty(l(r2), z, { writable: true, value: false }), Object.defineProperty(l(r2), Z, { writable: true, value: false }), Object.defineProperty(l(r2), $, { writable: true, value: [] }), Object.defineProperty(l(r2), H, { writable: true, value: void 0 }), Object.defineProperty(l(r2), J, { writable: true, value: void 0 }), Object.defineProperty(l(r2), K, { writable: true, value: void 0 }), Object.defineProperty(l(r2), X, { writable: true, value: void 0 }), Object.defineProperty(l(r2), Y, { writable: true, value: void 0 }), Object.defineProperty(l(r2), tt, { writable: true, value: false }), Object.defineProperty(l(r2), it, { writable: true, value: 1 }), Object.defineProperty(l(r2), et, { writable: true, value: Promise.resolve() }), w(l(r2), S)[S] = e3, w(l(r2), M)[M] = n2, w(l(r2), B)[B] = i3, w(l(r2), F)[F] = h2, w(l(r2), I)[I] = s2, w(l(r2), I)[I].restoreCustomPlaybackStateOnAdBreakComplete = true, h2.disableCustomPlaybackForIOS10Plus && !w(l(r2), S)[S].hasAttribute("playsinline") && w(l(r2), S)[S].setAttribute("playsinline", ""), w(l(r2), B)[B].settings.setDisableCustomPlaybackForIOS10Plus(h2.disableCustomPlaybackForIOS10Plus), w(l(r2), x)[x] = new k(w(l(r2), S)[S]), r2.o = r2.o.bind(l(r2)), P.forEach(function(t3) {
@@ -373,16 +373,16 @@ var S = b("mediaElement"), M = b("adElement"), x = b("customPlayhead"), I = b("a
w(t3, U)[U] = false, w(t3, Q)[Q] = false, w(t3, Z)[Z] = false, t3.dispatchEvent(new O(R.MEDIA_STOP));
}, 1);
}, e2.v = function(t3) {
for (var i3, e3 = (function(t4, i4) {
for (var i3, e3 = function(t4, i4) {
var e4 = "undefined" != typeof Symbol && t4[Symbol.iterator] || t4["@@iterator"];
if (e4) return (e4 = e4.call(t4)).next.bind(e4);
if (Array.isArray(t4) || (e4 = (function(t5, i5) {
if (Array.isArray(t4) || (e4 = function(t5, i5) {
if (t5) {
if ("string" == typeof t5) return f(t5, i5);
var e5 = Object.prototype.toString.call(t5).slice(8, -1);
return "Object" === e5 && t5.constructor && (e5 = t5.constructor.name), "Map" === e5 || "Set" === e5 ? Array.from(t5) : "Arguments" === e5 || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(e5) ? f(t5, i5) : void 0;
}
})(t4))) {
}(t4))) {
e4 && (t4 = e4);
var n3 = 0;
return function() {
@@ -390,7 +390,7 @@ var S = b("mediaElement"), M = b("adElement"), x = b("customPlayhead"), I = b("a
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
})(t3); !(i3 = e3()).done; ) {
}(t3); !(i3 = e3()).done; ) {
var n2 = i3.value;
n2.contentBoxSize && 1 === n2.contentBoxSize.length ? (w(this, W)[W] = n2.contentBoxSize[0].inlineSize, w(this, _)[_] = n2.contentBoxSize[0].blockSize) : n2.contentBoxSize && n2.contentBoxSize.inlineSize ? (w(this, W)[W] = n2.contentBoxSize.inlineSize, w(this, _)[_] = n2.contentBoxSize.blockSize) : (w(this, W)[W] = n2.contentRect.width, w(this, _)[_] = n2.contentRect.height);
}
@@ -435,116 +435,216 @@ var S = b("mediaElement"), M = b("adElement"), x = b("customPlayhead"), I = b("a
} }, { key: "cuePoints", get: function() {
return [].concat(w(this, $)[$]);
} }]), i2;
})(C);
}(C);
function createRequest(ima, field, value, config) {
const request = new ima.AdsRequest();
request[field] = value;
const fields = request;
for (const key in config)
fields[key] = config[key];
return request;
}
let nextId = 0;
function createContainer(utils) {
const container = utils.createElement("div");
container.id = `art-vast-${Date.now()}-${++nextId}`;
utils.setStyles(container, {
position: "absolute",
inset: "0",
width: "100%",
height: "100%",
zIndex: "150",
backgroundColor: "black",
display: "none",
pointerEvents: "auto"
});
return container;
}
function createSession(art, ima, utils, closed) {
const { $video, $player } = art.template;
const adsRenderingSettings = new ima.AdsRenderingSettings();
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
adsRenderingSettings.enablePreloading = true;
const playerOptions = new E();
let current = null;
let initializing = false;
let releasing = 0;
const live = (owner) => current === owner && !owner.released && !closed();
function release(owner) {
if (current === owner)
current = null;
owner.released = true;
owner.playing = false;
const player = owner.player;
owner.player = null;
const listeners = owner.listeners.splice(0);
let failed = false;
let failure;
const attempt = (cleanup) => {
try {
cleanup();
} catch (error) {
if (!failed) {
failed = true;
failure = error;
} else {
console.error("VAST cleanup error:", error);
}
}
};
releasing++;
try {
if (player) {
for (const [name, callback] of listeners)
attempt(() => player.removeEventListener(name, callback));
attempt(() => player.destroy());
}
attempt(() => {
if (owner.container.parentNode)
owner.container.parentNode.removeChild(owner.container);
});
} finally {
releasing--;
}
if (failed)
throw failure;
}
function init() {
if (closed() || releasing)
return null;
if (initializing)
throw new Error("VAST initialization is already in progress");
if (current?.player)
return current.player;
initializing = true;
let owner;
try {
owner = { container: createContainer(utils), player: null, listeners: [], playing: false, released: false };
current = owner;
if (!live(owner)) {
release(owner);
return null;
}
$player.appendChild(owner.container);
if (!live(owner)) {
release(owner);
return null;
}
owner.player = new nt(ima, $video, owner.container, adsRenderingSettings, playerOptions);
if (!live(owner)) {
release(owner);
return null;
}
const allocated = owner;
for (const [name, playing] of [
["AdContentPauseRequested", true],
["AdContentResumeRequested", false],
["AdStarted", true],
["AdError", false]
]) {
const callback = (event) => {
if (!live(allocated))
return;
if (name === "AdError")
console.error("VAST Ad Error:", event.detail);
allocated.playing = playing;
allocated.container.style.display = playing ? "block" : "none";
};
owner.listeners.push([name, callback]);
owner.player.addEventListener(name, callback);
if (!live(owner)) {
release(owner);
return null;
}
}
return owner.player;
} catch (error) {
if (owner) {
try {
release(owner);
} catch (cleanupError) {
console.error("VAST cleanup error:", cleanupError);
}
}
throw error;
} finally {
initializing = false;
}
}
function play(field, value, config) {
if (closed() || current?.playing)
return;
const player = init();
const owner = current;
if (!player || !owner)
return;
const request = createRequest(ima, field, value, config);
if (live(owner) && owner.player === player)
player.playAds(request);
}
function destroy() {
if (current)
release(current);
}
const context = {
art,
playUrl: (url, config = {}) => play("adTagUrl", url, config),
playRes: (response, config = {}) => play("adsResponse", response, config),
init,
ima,
adsRenderingSettings,
playerOptions,
get imaPlayer() {
return current?.player || null;
},
get container() {
return current?.container || null;
}
};
return { context, destroy };
}
function artplayerPluginVast(callback) {
return async (art) => {
const { template, constructor } = art;
const { createElement, setStyles } = constructor.utils;
const { $video, $player } = template;
await e();
const google = window.google;
const ima = google.ima;
const adsRenderingSettings = new ima.AdsRenderingSettings();
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
adsRenderingSettings.enablePreloading = true;
const playerOptions = new E();
let isAdPlaying = false;
let imaPlayer = null;
let $container = null;
function createContainer() {
const container = createElement("div");
const id = `art-vast-${Date.now()}`;
container.id = id;
setStyles(container, {
position: "absolute",
inset: "0",
width: "100%",
height: "100%",
zIndex: "150",
backgroundColor: "black",
display: "none",
pointerEvents: "auto"
});
return container;
}
function initPlayer() {
if (imaPlayer)
return imaPlayer;
$container = createContainer();
$player.appendChild($container);
imaPlayer = new nt(ima, $video, $container, adsRenderingSettings, playerOptions);
imaPlayer.addEventListener("AdContentPauseRequested", () => {
isAdPlaying = true;
$container.style.display = "block";
});
imaPlayer.addEventListener("AdContentResumeRequested", () => {
isAdPlaying = false;
$container.style.display = "none";
});
imaPlayer.addEventListener("AdStarted", () => {
isAdPlaying = true;
$container.style.display = "block";
});
imaPlayer.addEventListener("AdError", (event) => {
console.error("VAST Ad Error:", event.detail);
isAdPlaying = false;
$container.style.display = "none";
});
return imaPlayer;
}
function destroyPlayer() {
if (imaPlayer?.destroy)
imaPlayer.destroy();
if ($container?.parentNode)
$container.parentNode.removeChild($container);
$container = null;
imaPlayer = null;
}
function playUrl(url, config = {}) {
if (isAdPlaying)
let disposed = false;
let session;
const closed = () => disposed || art.isDestroy;
const result = { name: "artplayerPluginVast", destroy: () => session?.destroy() };
function dispose() {
if (disposed)
return;
if (!imaPlayer)
initPlayer();
const request = new ima.AdsRequest();
request.adTagUrl = url;
for (const key in config) {
request[key] = config[key];
disposed = true;
try {
art.off("destroy", dispose);
} finally {
session?.destroy();
}
imaPlayer.playAds(request);
}
function playRes(res, config = {}) {
if (isAdPlaying)
return;
if (!imaPlayer)
initPlayer();
const request = new ima.AdsRequest();
request.adsResponse = res;
for (const key in config) {
request[key] = config[key];
if (closed())
return result;
art.on("destroy", dispose);
try {
if (closed())
return result;
const ima = await e();
if (closed())
return result;
const utils = art.constructor.utils;
session = createSession(art, ima, utils, closed);
if (closed()) {
dispose();
return result;
}
imaPlayer.playAds(request);
if (typeof callback === "function")
await callback(session.context);
return result;
} catch (error) {
try {
dispose();
} catch (cleanupError) {
console.error("VAST cleanup error:", cleanupError);
}
throw error;
}
if (typeof callback === "function") {
await callback({
art,
playUrl,
playRes,
init: initPlayer,
ima,
adsRenderingSettings,
playerOptions,
get imaPlayer() {
return imaPlayer;
},
get container() {
return $container;
}
});
}
return {
name: "artplayerPluginVast",
destroy: destroyPlayer
};
};
}
export {
@@ -35,6 +35,7 @@
"legacy": "./dist/artplayer-plugin-vast.legacy.js",
"browserslist": "last 1 Chrome version",
"dependencies": {
"@alugha/ima": "2.1.0",
"@glomex/vast-ima-player": "^1.21.2"
}
}
-130
View File
@@ -1,130 +0,0 @@
import { loadImaSdk, Player, PlayerOptions } from '@glomex/vast-ima-player'
export default function artplayerPluginVast(callback) {
return async (art) => {
const { template, constructor } = art
const { createElement, setStyles } = constructor.utils
const { $video, $player } = template
await loadImaSdk()
const google = window.google
const ima = google.ima
const adsRenderingSettings = new ima.AdsRenderingSettings()
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true
adsRenderingSettings.enablePreloading = true
const playerOptions = new PlayerOptions()
let isAdPlaying = false
let imaPlayer = null
let $container = null
function createContainer() {
const container = createElement('div')
const id = `art-vast-${Date.now()}`
container.id = id
setStyles(container, {
position: 'absolute',
inset: '0',
width: '100%',
height: '100%',
zIndex: '150',
backgroundColor: 'black',
display: 'none',
pointerEvents: 'auto',
})
return container
}
function initPlayer() {
if (imaPlayer)
return imaPlayer
$container = createContainer()
$player.appendChild($container)
imaPlayer = new Player(ima, $video, $container, adsRenderingSettings, playerOptions)
imaPlayer.addEventListener('AdContentPauseRequested', () => {
isAdPlaying = true
$container.style.display = 'block'
})
imaPlayer.addEventListener('AdContentResumeRequested', () => {
isAdPlaying = false
$container.style.display = 'none'
})
imaPlayer.addEventListener('AdStarted', () => {
isAdPlaying = true
$container.style.display = 'block'
})
imaPlayer.addEventListener('AdError', (event) => {
console.error('VAST Ad Error:', event.detail)
isAdPlaying = false
$container.style.display = 'none'
})
return imaPlayer
}
function destroyPlayer() {
if (imaPlayer?.destroy)
imaPlayer.destroy()
if ($container?.parentNode)
$container.parentNode.removeChild($container)
$container = null
imaPlayer = null
}
function playUrl(url, config = {}) {
if (isAdPlaying)
return
if (!imaPlayer)
initPlayer()
const request = new ima.AdsRequest()
request.adTagUrl = url
for (const key in config) {
request[key] = config[key]
}
imaPlayer.playAds(request)
}
function playRes(res, config = {}) {
if (isAdPlaying)
return
if (!imaPlayer)
initPlayer()
const request = new ima.AdsRequest()
request.adsResponse = res
for (const key in config) {
request[key] = config[key]
}
imaPlayer.playAds(request)
}
if (typeof callback === 'function') {
await callback({
art,
playUrl,
playRes,
init: initPlayer,
ima,
adsRenderingSettings,
playerOptions,
get imaPlayer() {
return imaPlayer
},
get container() {
return $container
},
})
}
return {
name: 'artplayerPluginVast',
destroy: destroyPlayer,
}
}
}
@@ -0,0 +1,48 @@
import type Artplayer from 'artplayer'
import type { Callback, Result } from './types'
import { loadImaSdk } from './sdk'
import { createSession } from './session'
export default function artplayerPluginVast(callback?: Callback) {
return async (art: Artplayer): Promise<Result> => {
let disposed = false
let session: ReturnType<typeof createSession> | undefined
const closed = () => disposed || art.isDestroy
const result: Result = { name: 'artplayerPluginVast', destroy: () => session?.destroy() }
function dispose(): void {
if (disposed)
return
disposed = true
try {
art.off('destroy', dispose)
}
finally { session?.destroy() }
}
if (closed())
return result
art.on('destroy', dispose)
try {
if (closed())
return result
const ima = await loadImaSdk()
if (closed())
return result
const utils = (art.constructor as typeof Artplayer).utils
session = createSession(art, ima, utils, closed)
if (closed()) {
dispose()
return result
}
if (typeof callback === 'function')
await callback(session.context)
return result
}
catch (error) {
try {
dispose()
}
catch (cleanupError) { console.error('VAST cleanup error:', cleanupError) }
throw error
}
}
}
+14
View File
@@ -0,0 +1,14 @@
import type { ImaSdk } from '@alugha/ima'
import type { RequestConfig } from './types'
export { loadImaSdk, Player, PlayerOptions } from '@glomex/vast-ima-player'
export function createRequest(ima: ImaSdk, field: 'adTagUrl' | 'adsResponse', value: string, config: RequestConfig) {
const request = new ima.AdsRequest()
request[field] = value
// Preserve the historical SDK extension surface, including inherited config fields.
const fields = request as unknown as Record<string, unknown>
for (const key in config)
fields[key] = config[key]
return request
}
@@ -0,0 +1,164 @@
import type { ImaSdk } from '@alugha/ima'
import type Artplayer from 'artplayer'
import type { Context, RequestConfig, Utilities } from './types'
import { createRequest, Player, PlayerOptions } from './sdk'
import { createContainer } from './view'
interface Owner {
container: HTMLDivElement
player: Player | null
listeners: [string, EventListener][]
playing: boolean
released: boolean
}
export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, closed: () => boolean) {
const { $video, $player } = art.template
const adsRenderingSettings = new ima.AdsRenderingSettings()
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true
adsRenderingSettings.enablePreloading = true
const playerOptions = new PlayerOptions()
let current: Owner | null = null
let initializing = false
let releasing = 0
const live = (owner: Owner) => current === owner && !owner.released && !closed()
function release(owner: Owner): void {
if (current === owner)
current = null
owner.released = true
owner.playing = false
const player = owner.player
owner.player = null
const listeners = owner.listeners.splice(0)
let failed = false
let failure: unknown
const attempt = (cleanup: () => void) => {
try {
cleanup()
}
catch (error) {
if (!failed) {
failed = true
failure = error
}
else {
console.error('VAST cleanup error:', error)
}
}
}
releasing++
try {
if (player) {
for (const [name, callback] of listeners)
attempt(() => player.removeEventListener(name, callback))
attempt(() => player.destroy())
}
attempt(() => {
if (owner.container.parentNode)
owner.container.parentNode.removeChild(owner.container)
})
}
finally {
releasing--
}
if (failed)
throw failure
}
function init(): Player | null {
if (closed() || releasing)
return null
if (initializing)
throw new Error('VAST initialization is already in progress')
if (current?.player)
return current.player
initializing = true
let owner: Owner | undefined
try {
owner = { container: createContainer(utils), player: null, listeners: [], playing: false, released: false }
current = owner
if (!live(owner)) {
release(owner)
return null
}
$player.appendChild(owner.container)
if (!live(owner)) {
release(owner)
return null
}
owner.player = new Player(ima, $video, owner.container, adsRenderingSettings, playerOptions)
if (!live(owner)) {
release(owner)
return null
}
const allocated = owner
for (const [name, playing] of [
['AdContentPauseRequested', true],
['AdContentResumeRequested', false],
['AdStarted', true],
['AdError', false],
] as const) {
const callback: EventListener = (event) => {
if (!live(allocated))
return
if (name === 'AdError')
console.error('VAST Ad Error:', (event as CustomEvent<unknown>).detail)
allocated.playing = playing
allocated.container.style.display = playing ? 'block' : 'none'
}
owner.listeners.push([name, callback])
owner.player.addEventListener(name, callback)
if (!live(owner)) {
release(owner)
return null
}
}
return owner.player
}
catch (error) {
if (owner) {
try {
release(owner)
}
catch (cleanupError) { console.error('VAST cleanup error:', cleanupError) }
}
throw error
}
finally {
initializing = false
}
}
function play(field: 'adTagUrl' | 'adsResponse', value: string, config: RequestConfig): void {
if (closed() || current?.playing)
return
const player = init()
const owner = current
if (!player || !owner)
return
const request = createRequest(ima, field, value, config)
// Request constructors and config accessors may synchronously destroy or replace the session.
if (live(owner) && owner.player === player)
player.playAds(request)
}
function destroy(): void {
if (current)
release(current)
}
const context: Context = {
art,
playUrl: (url, config = {}) => play('adTagUrl', url, config),
playRes: (response, config = {}) => play('adsResponse', response, config),
init,
ima,
adsRenderingSettings,
playerOptions,
get imaPlayer() { return current?.player || null },
get container() { return current?.container || null },
}
return { context, destroy }
}
@@ -0,0 +1,24 @@
import type { google, ImaSdk } from '@alugha/ima'
import type { Player, PlayerOptions } from '@glomex/vast-ima-player'
import type Artplayer from 'artplayer'
export type Utilities = Pick<typeof Artplayer.utils, 'createElement' | 'setStyles'>
export type RequestConfig = Record<string, unknown>
export interface Context {
art: Artplayer
playUrl: (url: string, config?: RequestConfig) => void
playRes: (response: string, config?: RequestConfig) => void
init: () => Player | null
ima: ImaSdk
adsRenderingSettings: google.ima.AdsRenderingSettings
playerOptions: PlayerOptions
readonly imaPlayer: Player | null
readonly container: HTMLDivElement | null
}
export type Callback = (context: Context) => unknown
export interface Result {
name: 'artplayerPluginVast'
destroy: () => void
}
@@ -0,0 +1,19 @@
import type { Utilities } from './types'
let nextId = 0
export function createContainer(utils: Utilities): HTMLDivElement {
const container = utils.createElement('div')
container.id = `art-vast-${Date.now()}-${++nextId}`
utils.setStyles(container, {
position: 'absolute',
inset: '0',
width: '100%',
height: '100%',
zIndex: '150',
backgroundColor: 'black',
display: 'none',
pointerEvents: 'auto',
})
return container
}
@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "allowJs": false },
"include": ["src/**/*.ts", "types/**/*.d.ts"]
}