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

+1 -1
View File
@@ -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 {
+2 -2
View File
@@ -51,7 +51,7 @@
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js",
"typecheck": "node scripts/typecheck.mjs",
"test:unit": "node --test test/vast.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js",
"test:unit": "node --test test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js",
"test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
"test": "yarn test:node && yarn test:baseline",
"test:browser": "playwright test",
@@ -65,7 +65,7 @@
"test:dash-types-package": "node refactor/scripts/dash-package-types.mjs",
"test:ads-types-package": "node refactor/scripts/ads-package-types.mjs",
"test:ads-native-visibility": "node refactor/scripts/ads-native-visibility.mjs",
"test:vast": "node --test test/vast.test.js"
"test:vast": "node --test test/vast.test.js test/vast-lifecycle.test.js"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
@@ -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"]
}
@@ -0,0 +1,188 @@
{
"schemaVersion": 1,
"task": "PKG-VAST-03",
"status": "doing",
"sourceCommit": "d5e1fead",
"checks": [
{
"command": "node --test refactor/.cache/vast03-before.test.mjs",
"result": "15 expected failures on frozen workspace source; identical candidate assertions",
"tests": 15,
"failed": 15,
"passed": 0,
"log": {
"file": "refactor/.cache/vast03-before.log",
"sha256": "05bddd211578ae5086ce5b974d7598a1d0024bda69794026d730f8da02149058"
},
"fixture": {
"file": "refactor/.cache/vast03-before.test.mjs",
"sha256": "505d9a87e16138283ec2823b394eac6f0e640718a6cbf50af8a2dcfb3d611f49"
}
},
{
"command": "node --test test/vast.test.js test/vast-lifecycle.test.js",
"result": "pass",
"tests": 60,
"candidateLifecycle": 15,
"log": {
"file": "refactor/.cache/vast03-lifecycle-node-final.log",
"sha256": "b1a22029079b702954cce52fc63a57203baa38bcfefbb0b7d60c4a7dc4c43408"
}
},
{
"command": "yarn build artplayer-plugin-vast",
"result": "pass",
"formats": [
"main",
"legacy",
"esm"
],
"log": {
"file": "refactor/.cache/vast03-build.log",
"sha256": "c32e975b99fdf2d88397ec528c2d4b954dab4cfdaedbf83c0f0f8f38672a9e65"
}
},
{
"command": "yarn test:browser vast.spec.js",
"result": "pass",
"tests": 117,
"stats": {
"startTime": "2026-09-12T01:27:19.631Z",
"duration": 49123.307,
"expected": 117,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"scope": "Actual core 5.1.7/5.4.1/candidate, actual DOM/main media, controlled Glomex boundary in wrapper source builds. Not final bundled SDK runtime acceptance.",
"log": {
"file": "refactor/.cache/vast03-browser.log",
"sha256": "848ea969ac9f30776817284eb488fdf9d660ae98bcd01d32dbaf8f70e04fdfb5"
},
"report": {
"file": "refactor/.cache/vast03-browser-report.json",
"sha256": "fd58f0b1ebcd1d2ec209bfd94e29f7387ef55d9ffdba8e145d7a22f72fbb079c"
},
"results": "refactor/.cache/vast03-browser-results"
},
{
"command": "yarn ci:check",
"result": "pass",
"tests": {
"unit": 832,
"engineering": 14,
"baseline": 54,
"total": 900
},
"sourceTS": 274,
"coreDeclarations": 37,
"log": {
"file": "refactor/.cache/vast03-ci.log",
"sha256": "5e6bb801f578792debcd88ff0341922dd053f358b9f78f2d51839063132c0deb"
}
}
],
"pending": [
"Published versus workspace initialization decision",
"Published id/$container aliases",
"Public declaration and CommonJS namespace reconciliation in04",
"Real IMA and package distribution validation in05/06"
],
"inputs": [
{
"file": "package.json",
"sha256LF": "fce3d3a49ba5bb24fd1ae2191e4e13b679395cf50df5d26b355904dd9b31caf2"
},
{
"file": "yarn.lock",
"sha256LF": "9bd01cf6394ece742aecb0ecc2a2aa563012de2c16f28141ed03dd37e17f8717"
},
{
"file": "packages/artplayer-plugin-vast/package.json",
"sha256LF": "dd4c196a8c94b0037bc156e428300b030d40b6f015f3ad165ef48aa733149052"
},
{
"file": "packages/artplayer-plugin-vast/tsconfig.json",
"sha256LF": "712a3fa32ed57692d16bf854a0e77ffaecd04236efa0f557905aab7291e0aef6"
},
{
"file": "packages/artplayer-plugin-vast/src/index.ts",
"sha256LF": "c17455be188ecb23161729234faa1b4309a3b60f08465509e873fee2f1dd21d6"
},
{
"file": "packages/artplayer-plugin-vast/src/sdk.ts",
"sha256LF": "aaff7c1e4a31ff99261b045648663caa46f66b5a25ad129e5fc929facaf1ff89"
},
{
"file": "packages/artplayer-plugin-vast/src/session.ts",
"sha256LF": "472ad363276711dd79c375e5cdfe44cc42dd7f781a04974442f2599998c8cbc2"
},
{
"file": "packages/artplayer-plugin-vast/src/types.ts",
"sha256LF": "0042303fd4206467724a7c5abda633436ac30a3d1139b24609e1a7dd1e890b23"
},
{
"file": "packages/artplayer-plugin-vast/src/view.ts",
"sha256LF": "c36008cdf2e6b7818b166deb4842ce8d405272486e2c07b6226e8e2905640328"
},
{
"file": "test/helpers/vast-sdk.js",
"sha256LF": "f8bc792ef16da9396bafefaf89f1b53b6254bf263d0c3019b87ee188eac35bd1"
},
{
"file": "test/helpers/vast.js",
"sha256LF": "ea985ca97946645a8b6fc76c9d69ec4fc12f959ab175402cd34e93bc81c14ce9"
},
{
"file": "test/vast.test.js",
"sha256LF": "b02c12c0440540f4f13929bbdcb3607621a42fdc6f29debbc912189c3aa025a6"
},
{
"file": "test/vast-lifecycle.test.js",
"sha256LF": "5b6eb97ee038755046fdc31b4fa12e4a3e20903deafc208d6378f7e5de61dfbc"
},
{
"file": "test/browser/vast.spec.js",
"sha256LF": "7cef5364837e3e0b4818ecfa954c1e4214ac6627be78ee85bd32cdb479b37f62"
}
],
"artifacts": [
{
"file": "packages/artplayer-plugin-vast/dist/artplayer-plugin-vast.js",
"sha256": "874803ec726f04649bb636c1376de9e038754310ab0016a22da44f189b7a5ce0"
},
{
"file": "packages/artplayer-plugin-vast/dist/artplayer-plugin-vast.legacy.js",
"sha256": "7d4e0fdb95e6881f6b6379a7c36599fb5087cf9122253718a955931077c9f420"
},
{
"file": "packages/artplayer-plugin-vast/dist/artplayer-plugin-vast.mjs",
"sha256": "7650fb0d42e8bb7049d201859187d28f69b1868ac7e25539a7575ee50ae1b2d7"
}
],
"dependency": {
"package": "@alugha/ima",
"version": "2.1.0",
"license": "MIT",
"purpose": "Public Glomex SDK declaration dependency; runtime loader remains existing Glomex implementation",
"installation": {
"file": "refactor/.cache/vast03-dependency.log",
"sha256": "ad5ace8a5a596d521f65ab454793d3e3d2cce9b774310ce9082e232fd8d1bf95"
},
"consumerTypes": {
"source": "import vast from \"artplayer-plugin-vast\"; vast(({imaPlayer})=>{void imaPlayer});",
"results": [
{
"compiler": "5.9.3",
"mode": "node10-commonjs",
"diagnostics": []
},
{
"compiler": "4.3.5",
"mode": "node10-commonjs",
"diagnostics": []
}
]
}
}
}
@@ -0,0 +1,37 @@
# PKG-VAST-03 生命周期实现检查点
起点d5e1fead。原index.js迁为5个严格TS模块:入口、SDK/request边界、session所有权、
DOM视图及内部类型。实际职责/依赖/时序见
[包内架构](../../packages/artplayer-plugin-vast/ARCHITECTURE.md)。
核心destroy在SDK加载前订阅;加载或callback完成后不会再创建终止实例的广告。
session销毁时先使旧owner失效,再逐项释放监听/SDK/DOM,异常不阻断剩余清理;
构造和事件注册失败回滚;旧事件不能污染新session;活动广告显式destroy后可重建。
构造/订阅/销毁重入及request配置getter引发销毁/替换也有身份检查。
15项候选生命周期断言配合45项已有行为/历史观察,60项Node通过;另在冻结工作区
执行同一候选断言记录修复前失败。三格式正式构建成功,117项真实浏览器测试通过,
包含三核心/三引擎的新增终止、回调失败、旧事件与显式重建路径。SDK边界仍受控,
不代表正式bundle中的实际IMA执行;该部分留给05/06。
直接固定依赖@alugha/ima2.1.0(MIT),补齐Glomex声明引用但未作为运行依赖提供的
类型链;保持Glomex1.21.2与其实际SDK加载器,新增依赖没有直接运行时import。
Yarn仅新增该依赖和相应锁条目;包的tsconfig纳入严格检查,未用skipLibCheck掩盖错误。
初次lint发现单行try格式不符后修正,没有通过忽略整个文件避开检查。
**本任务仍doing**:[初始化决策](../vast-compatibility-decision.md) 展示npm发布版
eager与未发布工作区lazy的不可同时满足条件。当前默认行为暂保持工作区,尚未恢复
发布id/$container,因此不能声称03兼容验收完成。拟定方案默认保持npm1.0.0行为、
另提供显式工作区兼容选择;尚未实现,等待用户确认。Ads批准不扩展到VAST。
公开类型同步误声明、历史CJS.default入口仍由04处理。新ID增加同毫秒区分后缀,保留
workspace art-vast-前缀;发布ID规则与上面的初始化决策一起处理。SDK-07及VAST相关
风险继续open,候选可控修复证据与实际供应商/发布验收分开记录。
执行结果、输入和产物指纹见 [验证记录](../baselines/vast-lifecycle-validation.json)。
本批作为03未完成检查点独立本地提交;不push/tag/publish。回退该提交恢复旧VAST
源码、依赖、构建输出和测试接入;01/02不可变基线与历史缺陷证据保留。
最终完整本地CI900项通过(832单元、14工程、54基线),274个生产TS检查,37份核心
声明无漂移;15个候选断言在旧工作区均失败,修复后全部通过。TS5.9.3/4.3.5当前
最小公开消费者的缺失类型错误均消除,但同步返回误声明仍开放。
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 217 项,范围 22 个包及工作区/示例。
状态:todo 132 / doing 5 / blocked 0 / done 80 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 131 / doing 6 / blocked 0 / done 80 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -269,7 +269,7 @@
| --- | --- | --- | --- | --- | --- | --- |
| PKG-VAST-01 | artplayer-plugin-vast<br>核对包契约与历史用法 | BASE-05 | 异步工厂、callback context、playUrl/playRes/init 与 SDK 版本;实际 npm 1.0.0 与未发布 1.2.0、SDK 1.21.0/1.21.2 的60成员归档和发布关联核心5.1.7 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
| PKG-VAST-02 | artplayer-plugin-vast<br>建立特有行为与错误测试 | PKG-VAST-01, ENG-03, ENG-05 | SDK 失败、重复初始化、广告事件、内容恢复和销毁竞态;发布 id/$container/eager 与工作区 lazy/config 覆盖、callback拒绝、加载中销毁、同毫秒多实例与晚到事件 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-VAST-03 | artplayer-plugin-vast<br>整理内部职责与资源 | PKG-VAST-02, CORE-10, CORE-13 | SDK loader、IMA adapter、广告状态和 DOM 清理分离;保持发布 callback 别名,明确初始化冲突;核心终止与显式 destroy 后重建区分 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo |
| PKG-VAST-03 | artplayer-plugin-vast<br>整理内部职责与资源 | PKG-VAST-02, CORE-10, CORE-13 | SDK loader、IMA adapter、广告状态和 DOM 清理分离;保持发布 callback 别名,明确初始化冲突;核心终止与显式 destroy 后重建区分 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | doing |
| PKG-VAST-04 | artplayer-plugin-vast<br>迁移自有源码和公开类型 | PKG-VAST-03, ENG-04, ENG-06, CORE-07 | IMA/context/Promise 真实类型,外部 SDK 动态对象限定边界;修复 @alugha/ima 声明依赖链,保留 export= 和 require.default 消费;同步错误声明的兼容决策单列 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
| PKG-VAST-05 | artplayer-plugin-vast<br>验证新旧核心和组合 | PKG-VAST-04, CORE-22 | 官方测试广告或受控 SDK 环境,错误后主视频状态恢复;核验真实5.1.7/5.4.1/候选核心,VAST VPN外部脚本例外逐项记录,不将受控SDK当作实际IMA验收 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
| PKG-VAST-06 | artplayer-plugin-vast<br>验证分发并同步文档 | PKG-VAST-05, ENG-07 | vast.js、SDK 资源、声明消费和独立版本记录;核验实际发布 namespace default、历史深路径、ESM和准确声明,版本按政策2.0.0由REL-09统一处理 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
@@ -491,3 +491,4 @@
- PKG-ADS-05: [记录](changes/2026-09-12-PKG-ADS-05-browser.md) [记录](baselines/ads-ui-visibility-validation.json)
- PKG-VAST-01: [记录](baselines/vast-contract.md) [记录](baselines/vast-release.json) [记录](changes/2026-09-12-PKG-VAST-01-contract.md) [记录](baselines/vast-contract-validation.json)
- PKG-VAST-02: [记录](changes/2026-09-12-PKG-VAST-02-tests.md) [记录](baselines/vast-behavior-validation.json) [记录](baselines/vast-core.json) [记录](vast-validation.md)
- PKG-VAST-03: [记录](changes/2026-09-12-PKG-VAST-03-lifecycle.md) [记录](baselines/vast-lifecycle-validation.json) [记录](vast-compatibility-decision.md)
+12
View File
@@ -1,5 +1,17 @@
# 进度与证据
## 当前实施:VAST-03 TS拆分与生命周期修复
原入口拆成5个严格TS模块,处理核心终止、显式广告重建、SDK/DOM回滚与晚到/重入回调。
15项候选生命周期断言在旧工作区全部失败,修复后60项Node通过;三格式构建成功,
117项三核心/三引擎浏览器wrapper测试通过。完整CI900项通过、274个生产TS严格检查。
补齐@alugha/ima2.1.0类型依赖,两个编译器不再出现SDK缺失类型错误。
初始化行为存在真实发布/工作区冲突,已给出 [具体取舍](vast-compatibility-decision.md),
确认前仍保持工作区默认,03保持doing;Google IMA及公开类型/分发完整验收未完成。
当前217项:80 done、6 doing、131 todo。此批独立提交未完成检查点,见
[实施记录](changes/2026-09-12-PKG-VAST-03-lifecycle.md) 和
[验证证据](baselines/vast-lifecycle-validation.json)。
## 最新完成:VAST-02 行为与生命周期回归
45项Node测试通过,覆盖当前源码及两套冻结历史源码;实际核心5.1.7的197成员已归档。
+12 -6
View File
@@ -702,7 +702,7 @@
"PKG-VAST-05"
],
"evidence": [
"packages/artplayer-plugin-vast/src/index.js",
"packages/artplayer-plugin-vast/src/sdk.ts",
"refactor/baselines/vast-contract.md",
"refactor/baselines/vast-release.json",
"refactor/changes/2026-09-12-PKG-VAST-01-contract.md",
@@ -3181,7 +3181,8 @@
"refactor/baselines/vast-contract.md",
"refactor/baselines/vast-release.json",
"refactor/changes/2026-09-12-PKG-VAST-01-contract.md",
"refactor/baselines/vast-contract-validation.json"
"refactor/baselines/vast-contract-validation.json",
"refactor/vast-compatibility-decision.md"
],
"compatibleResolution": "Restore published id/$container access and reconcile eager non-null Player with lazy workspace usage using explicit consumer evidence.",
"closureCriteria": "Published and workspace consumer fixtures cover all callback fields and initialization timing; any irreconcilable behavior has a concrete decision rather than implicit waiver."
@@ -3199,10 +3200,13 @@
"refactor/baselines/vast-contract.md",
"refactor/baselines/vast-release.json",
"refactor/changes/2026-09-12-PKG-VAST-01-contract.md",
"refactor/baselines/vast-contract-validation.json"
"refactor/baselines/vast-contract-validation.json",
"refactor/baselines/vast-lifecycle-validation.json",
"refactor/changes/2026-09-12-PKG-VAST-03-lifecycle.md"
],
"compatibleResolution": "Provide accurate Promise/context/IMA types and a self-contained installed declaration chain while checking legacy export= and consumers.",
"closureCriteria": "Strict isolated package consumers pass without skipLibCheck; synchronous Result inference differences are reproduced and resolved or separately approved. Ads approval does not apply."
"closureCriteria": "Strict isolated package consumers pass without skipLibCheck; synchronous Result inference differences are reproduced and resolved or separately approved. Ads approval does not apply.",
"workspaceState": "Direct @alugha/ima2.1.0 removes the two TS2307 diagnostics in TS5.9.3 and4.3.5; public synchronous Result mismatch and isolated packaged declaration validation remain04/06."
},
{
"id": "VAST-LIFE-01",
@@ -3222,11 +3226,13 @@
"refactor/changes/2026-09-12-PKG-VAST-02-tests.md",
"refactor/baselines/vast-behavior-validation.json",
"refactor/baselines/vast-core.json",
"refactor/vast-validation.md"
"refactor/vast-validation.md",
"refactor/baselines/vast-lifecycle-validation.json",
"refactor/changes/2026-09-12-PKG-VAST-03-lifecycle.md"
],
"compatibleResolution": "Reproduce pending SDK/callback destruction, errors, duplicate IDs and stale ad events before adding compatible cleanup and state ownership.",
"closureCriteria": "Tests prove cleanup during load/callback rejection/core destruction and distinguish explicit plugin recreation from terminal core destruction; vendor media restoration is separately verified.",
"workspaceState": "45 Node tests include 16 frozen historical observations across 10 lifecycle/error categories. Real browser core destruction plus late SDK resolution leaves a newly allocated recorder in the detached player across 5.1.7/5.4.1/candidate. Google IMA allocation itself is not simulated as evidence; exact wrapper SDK boundary is controlled. All production fixes remain PKG-VAST-03 work."
"workspaceState": "45 Node tests include 16 frozen historical observations across 10 lifecycle/error categories. Real browser core destruction plus late SDK resolution leaves a newly allocated recorder in the detached player across 5.1.7/5.4.1/candidate. Google IMA allocation itself is not simulated as evidence; exact wrapper SDK boundary is controlled. All production fixes remain PKG-VAST-03 work. PKG-VAST-03 checkpoint: 15 candidate lifecycle assertions fail on frozen workspace and pass after TS ownership changes; 117 real browser wrapper checks pass with controlled SDK. Risk stays open pending full compatibility and actual vendor acceptance."
},
{
"id": "VAST-DIST-01",
+6 -2
View File
@@ -2692,11 +2692,15 @@
"CORE-10",
"CORE-13"
],
"status": "todo",
"status": "doing",
"risk": "H",
"deliverable": "SDK loader、IMA adapter、广告状态和 DOM 清理分离;保持发布 callback 别名,明确初始化冲突;核心终止与显式 destroy 后重建区分",
"acceptance": "结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过",
"evidence": []
"evidence": [
"changes/2026-09-12-PKG-VAST-03-lifecycle.md",
"baselines/vast-lifecycle-validation.json",
"vast-compatibility-decision.md"
]
},
{
"id": "PKG-VAST-04",
+12 -1
View File
@@ -165,6 +165,7 @@
{
"name": "artplayer-plugin-vast",
"declaredRuntimeDependencies": {
"@alugha/ima": "2.1.0",
"@glomex/vast-ima-player": "^1.21.2"
},
"resolvedRuntimeDependencies": [
@@ -175,6 +176,14 @@
"installedVersion": "1.21.2",
"manifestLicense": "Apache-2.0",
"licenseEvidence": "Installed package manifest claim; bundled notice and transitive/license review remain with the owning package."
},
{
"name": "@alugha/ima",
"range": "2.1.0",
"lockedVersion": "2.1.0",
"installedVersion": "2.1.0",
"manifestLicense": "MIT",
"licenseEvidence": "Installed package manifest and LICENSE.md; direct dependency supplies the public declaration chain referenced by Glomex."
}
],
"developmentDependencies": {}
@@ -1118,7 +1127,9 @@
"PKG-VAST-05"
],
"sourceFiles": [
"packages/artplayer-plugin-vast/src/index.js"
"packages/artplayer-plugin-vast/src/index.ts",
"packages/artplayer-plugin-vast/src/sdk.ts",
"packages/artplayer-plugin-vast/src/session.ts"
],
"contract": "@glomex/vast-ima-player loadImaSdk loads external IMA; preserve event/content/ad state and teardown, do not equate mocks with vendor service acceptance.",
"validationStatus": "Source integration observed; complete real SDK/service/device matrix not yet verified.",
+45
View File
@@ -0,0 +1,45 @@
# VAST 初始化兼容决策:待用户选择
来源:[真实发布契约](baselines/vast-contract.md);已可重跑的
`vast-contract.test.mjs`及`test/vast.test.js`固定以下矛盾:
| 同一次 callback 开始时 | npm1.0.0 | 未发布工作区1.2.0 |
| --- | --- | --- |
| imaPlayer | 已构造、非null | null,直到init/playUrl/playRes |
| DOM | 已挂载$container,并提供id | 不存在容器,container getter为null |
| 设置 | IMA默认值 | 默认启用preloading及restoreCustomPlaybackStateOnAdBreakComplete |
实际发布用户可以直接写 `({ imaPlayer }) => imaPlayer.addEventListener(...)`。
工作区用户可以先判断`context.imaPlayer === null`并修改`playerOptions`,然后等待
用户操作调用`init()`。在同一默认调用中无法同时保留非null和null,以及相反初始化时机。
添加别名或getter也无法同时满足两种值;按callback源码猜测用户版本不可取。
## 推荐的具体处置(尚未实现)
优先保持实际npm1.0.0默认行为:回调前初始化、提供id/$container,SDK默认设置保持
发布语义;保留workspace新增的init/container/settings等字段作为扩展。
确实依赖未发布1.2.0惰性行为的代码,拟通过新的可选第二参数明确选择该兼容模式:
```js
// npm旧用法保持原样
artplayerPluginVast(({ imaPlayer, $container, id }) => {
imaPlayer.addEventListener('AdStarted', onAdStarted)
})
// 未发布工作区1.2.0的初始化时机与默认设置保留在显式模式
artplayerPluginVast((context) => {
context.playerOptions.autoResize = false
button.onclick = () => context.playUrl(adTag)
}, { compatibility: 'workspace-1.2' })
```
这意味着依赖未发布1.2.0默认惰性的代码需要增加一个参数。普通npm1.0.0用户保持
原调用;这不是声称两种相反语义完全兼容。第二参数仅为待确认方案,当前生产代码
尚未新增它;类型误声明、CJS.default和分发路径仍各自单独验证。
## 当前检查点
生命周期与资源修复不依赖该选择,已经推进;默认初始化暂保持工作区行为,不把
PKG-VAST-03或VAST-CONTEXT-01标完成。用户针对Ads的类型推导批准不适用于本决策。
需要用户确认是否按推荐方案优先实际发布默认行为;也可指定其他取舍。
+8
View File
@@ -4,6 +4,14 @@
[实际契约](baselines/vast-contract.md)、[VAST/SDK归档](baselines/vast-release.json)
和 [核心5.1.7归档](baselines/vast-core.json)。工作区1.2.0不是npm发布版本。
03检查点:新增`test/vast-lifecycle.test.js`的15项候选修复断言,冻结工作区全部失败、
当前5个TS模块全部通过;现有测试共60项Node、117项浏览器通过。覆盖终止/回调失败、
初始化回滚、活动广告重建、旧事件、配置getter和SDK同步重入;完整CI900项通过。
该文件已纳入`yarn test:vast`和`test:unit`。实际资源所有权见
[包内架构](../packages/artplayer-plugin-vast/ARCHITECTURE.md);
[初始化冲突](vast-compatibility-decision.md)未决定,03仍doing。以下缺陷清单保留历史
来源,不代表候选实现仍包含这些已获可控修复证据的问题;真实IMA验收仍待05。
## 文件与执行
- `test/helpers/vast-sdk.js`:可在Node和浏览器使用的SDK记录器,控制加载、构造、
+57
View File
@@ -165,5 +165,62 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
}))).toEqual({ calls: 1, players: 1, destroys: 0, connected: false, url: '/late.xml' })
})
}
else {
test(`${label}: candidate suppresses late SDK initialization after real core destruction`, async ({ page }, testInfo) => {
await setup(page, core, implementation, testInfo, true)
const state = await page.evaluate(async () => {
let calls = 0
const registration = window.art.plugins.add(window.artplayerPluginVast(() => calls++))
window.art.destroy()
window.vastSdk.resolveLoad()
await registration
return { calls, players: window.vastSdk.state.players.length }
})
expect(state).toEqual({ calls: 0, players: 0 })
})
test(`${label}: candidate isolates stale events and terminates recreated ads on core destroy`, async ({ page }, testInfo) => {
await setup(page, core, implementation, testInfo)
await page.evaluate(async () => {
await window.art.plugins.add(window.artplayerPluginVast((context) => {
window.vastContext = context
}))
window.vastContext.playUrl('/one.xml')
const first = window.vastContext.imaPlayer
window.lateVastEvents = [...first.listeners.values()].flatMap(values => [...values])
first.emit('AdStarted')
})
const container = page.locator('[id^="art-vast-"]')
await expect(container).toBeVisible()
await page.evaluate(() => {
window.art.plugins.artplayerPluginVast.destroy()
window.vastContext.playUrl('/two.xml')
for (const callback of window.lateVastEvents) callback({ detail: 'late' })
})
await expect(container).toBeHidden()
expect(await page.evaluate(() => window.vastSdk.state.players.map(player => player.requests.length))).toEqual([1, 1])
await page.evaluate(() => {
window.art.destroy()
window.vastContext.playUrl('/after-core-destroy.xml')
})
expect(await page.evaluate(() => window.vastContext.init())).toBe(null)
expect(await page.evaluate(() => window.vastSdk.state.players.map(player => player.destroyCalls))).toEqual([1, 1])
await expect(container).toHaveCount(0)
})
test(`${label}: candidate callback rejection cleans allocated SDK and DOM without destroying the core`, async ({ page }, testInfo) => {
await setup(page, core, implementation, testInfo)
const result = await page.evaluate(async () => {
const failure = new Error('callback rejected')
const registration = window.art.plugins.add(window.artplayerPluginVast((context) => {
context.init()
return Promise.reject(failure)
}))
return { sameError: await registration.catch(error => error === failure), destroys: window.vastSdk.state.players[0].destroyCalls, coreDestroyed: window.art.isDestroy }
})
expect(result).toEqual({ sameError: true, destroys: 1, coreDestroyed: false })
await expect(page.locator('[id^="art-vast-"]')).toHaveCount(0)
})
}
}
}
+3
View File
@@ -24,6 +24,7 @@ export function createVastSdk(options = {}) {
this.listeners = new Map()
this.destroyCalls = 0
state.players.push(this)
state.onConstruct?.(this)
}
addEventListener(name, callback) {
@@ -32,6 +33,7 @@ export function createVastSdk(options = {}) {
if (!this.listeners.has(name))
this.listeners.set(name, new Set())
this.listeners.get(name).add(callback)
state.onListen?.(this)
}
removeEventListener(name, callback) {
@@ -52,6 +54,7 @@ export function createVastSdk(options = {}) {
this.destroyCalls++
if (state.destroyError)
throw state.destroyError
state.onDestroy?.(this)
this.listeners.clear()
}
},
+267
View File
@@ -0,0 +1,267 @@
import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- Candidate SDK lifecycle acceptance uses Node's runner.
import test from 'node:test'
import { deferred, vastEnvironment, vastImplementations } from './helpers/vast.js'
const candidates = (await vastImplementations()).filter(item => !item.historical)
for (const implementation of candidates) {
const { name } = implementation
test(`${name}: core destruction releases SDK and makes escaped methods inert`, async () => {
const env = vastEnvironment(implementation)
const host = env.host()
let context
const result = await env.factory((value) => {
context = value
value.playUrl('/first.xml')
})(host.art)
const player = context.imaPlayer
host.art.destroy()
result.destroy()
assert.equal(context.init(), null)
context.playUrl('/late.xml')
context.playRes('late response')
assert.equal(player.destroyCalls, 1)
assert.equal(player.requests.length, 1)
assert.equal(host.parent.children.length, 0)
assert.equal(context.imaPlayer, null)
assert.equal(context.container, null)
})
test(`${name}: destruction while SDK is loading suppresses callback and allocation`, async () => {
const env = vastEnvironment(implementation, { deferLoad: true })
const host = env.host()
let calls = 0
const attaching = env.factory(() => calls++)(host.art)
assert.equal(host.listeners.get('destroy').size, 1)
host.art.destroy()
env.resolveLoad()
const result = await attaching
result.destroy()
assert.equal(calls, 0)
assert.equal(env.state.players.length, 0)
assert.equal(host.parent.children.length, 0)
})
test(`${name}: already destroyed hosts do not load SDK or subscribe`, async () => {
const env = vastEnvironment(implementation)
const host = env.host()
host.art.destroy()
await env.factory(() => assert.fail('Unexpected callback'))(host.art)
assert.equal(env.state.loads, 0)
assert.equal(host.listeners.size, 0)
})
test(`${name}: SDK rejection unregisters the pending core listener`, async () => {
const env = vastEnvironment(implementation, { deferLoad: true })
const host = env.host()
const attaching = env.factory()(host.art)
const failure = new Error('load failure')
const rejected = assert.rejects(attaching, error => error === failure)
env.rejectLoad(failure)
await rejected
assert.equal(host.listeners.get('destroy').size, 0)
})
test(`${name}: callback rejection reclaims its SDK and preserves the original error over cleanup failure`, async () => {
const failure = new Error('callback failure')
for (const destroyError of [null, new Error('cleanup failure')]) {
const env = vastEnvironment(implementation, { destroyError })
const host = env.host()
let context
await assert.rejects(env.factory((value) => {
context = value
context.init()
throw failure
})(host.art), error => error === failure)
assert.equal(env.state.players[0].destroyCalls, 1)
assert.equal(host.parent.children.length, 0)
assert.equal(host.listeners.get('destroy').size, 0)
assert.equal(context.init(), null)
if (destroyError)
assert.equal(env.state.errors[0][1], destroyError)
}
})
test(`${name}: a callback resumed after core destroy cannot request or recreate ads`, async () => {
const env = vastEnvironment(implementation)
const host = env.host()
const started = deferred()
const finish = deferred()
const attaching = env.factory(async (context) => {
context.init()
started.resolve()
await finish.promise
context.playUrl('/late.xml')
assert.equal(context.init(), null)
})(host.art)
await started.promise
host.art.destroy()
finish.resolve()
await attaching
assert.equal(env.state.players.length, 1)
assert.equal(env.state.players[0].destroyCalls, 1)
assert.equal(env.state.players[0].requests.length, 0)
assert.equal(host.parent.children.length, 0)
})
test(`${name}: constructor and event-registration failures roll back and allow explicit retry`, async () => {
for (const field of ['constructError', 'listenError']) {
const failure = new Error(field)
const env = vastEnvironment(implementation, { [field]: failure })
const host = env.host()
let context
await env.factory((value) => {
context = value
})(host.art)
assert.throws(() => context.init(), error => error === failure)
assert.equal(host.parent.children.length, 0)
assert.equal(context.imaPlayer, null)
assert.equal(context.container, null)
if (field === 'listenError')
assert.equal(env.state.players[0].destroyCalls, 1)
env.state[field] = null
const player = context.init()
assert.equal(player.listeners.size, 4)
assert.equal(host.parent.children.length, 1)
}
})
test(`${name}: same-millisecond instances use distinct IDs and clean up independently`, async () => {
const env = vastEnvironment(implementation)
const contexts = []
const attach = env.factory((context) => {
context.init()
contexts.push(context)
})
const one = env.host()
const two = env.host()
const [result] = await Promise.all([attach(one.art), attach(two.art)])
assert.notEqual(contexts[0].container.id, contexts[1].container.id)
result.destroy()
assert.equal(one.parent.children.length, 0)
assert.equal(two.parent.children.length, 1)
contexts[1].playUrl('/other.xml')
assert.equal(contexts[1].imaPlayer.requests.length, 1)
})
test(`${name}: explicit destroy during an active ad clears suppression for a new session`, async () => {
const env = vastEnvironment(implementation)
let context
const result = await env.factory((value) => {
context = value
})(env.host().art)
context.playUrl('/first.xml')
context.imaPlayer.emit('AdStarted')
result.destroy()
context.playUrl('/second.xml')
assert.equal(env.state.players.length, 2)
assert.equal(context.imaPlayer.requests[0].adTagUrl, '/second.xml')
})
test(`${name}: saved old SDK callbacks are inert both after cleanup and after recreation`, async () => {
const env = vastEnvironment(implementation)
let context
const result = await env.factory((value) => {
context = value
})(env.host().art)
const first = context.init()
const late = [...first.listeners.values()].flatMap(listeners => [...listeners])
result.destroy()
for (const callback of late) assert.doesNotThrow(() => callback({ detail: 'late' }))
context.init()
for (const callback of late) callback({ detail: 'late' })
assert.equal(context.container.style.display, 'none')
context.playUrl('/fresh.xml')
assert.equal(context.imaPlayer.requests.length, 1)
assert.equal(env.state.errors.length, 0)
})
test(`${name}: SDK destroy failure still clears references and DOM and is not retried accidentally`, async () => {
const failure = new Error('destroy failure')
const env = vastEnvironment(implementation, { destroyError: failure })
const host = env.host()
let context
const result = await env.factory((value) => {
context = value
})(host.art)
const player = context.init()
assert.throws(() => result.destroy(), error => error === failure)
assert.equal(context.container, null)
assert.equal(context.imaPlayer, null)
assert.equal(host.parent.children.length, 0)
assert.doesNotThrow(() => result.destroy())
assert.equal(player.destroyCalls, 1)
env.state.destroyError = null
assert.notEqual(context.init(), player)
})
test(`${name}: a request config accessor destroying the core prevents playAds`, async () => {
const env = vastEnvironment(implementation)
const host = env.host()
let context
await env.factory((value) => {
context = value
})(host.art)
context.playUrl('/cancelled.xml', { get extra() {
host.art.destroy()
return 1
} })
assert.equal(env.state.players[0].requests.length, 0)
assert.equal(env.state.players[0].destroyCalls, 1)
assert.equal(host.parent.children.length, 0)
})
test(`${name}: a request accessor replacing the session cannot dispatch through its stale player`, async () => {
const env = vastEnvironment(implementation)
let context
const result = await env.factory((value) => {
context = value
})(env.host().art)
context.playUrl('/stale.xml', { get extra() {
result.destroy()
context.init()
return 1
} })
assert.equal(env.state.players.length, 2)
assert.equal(env.state.players[0].requests.length, 0)
assert.equal(env.state.players[1].requests.length, 0)
context.playUrl('/fresh.xml')
assert.equal(env.state.players[1].requests[0].adTagUrl, '/fresh.xml')
})
test(`${name}: destruction reentered from SDK construction or subscription reclaims late resources`, async () => {
for (const hook of ['onConstruct', 'onListen']) {
const env = vastEnvironment(implementation)
const host = env.host()
let context
await env.factory((value) => {
context = value
})(host.art)
env.state[hook] = () => host.art.destroy()
assert.equal(context.init(), null)
assert.equal(env.state.players[0].destroyCalls, 1)
assert.equal(host.parent.children.length, 0)
assert.equal(context.imaPlayer, null)
}
})
test(`${name}: SDK destroy reentrancy cannot create a replacement until cleanup finishes`, async () => {
const env = vastEnvironment(implementation)
let context
const result = await env.factory((value) => {
context = value
})(env.host().art)
context.init()
env.state.onDestroy = () => {
assert.equal(context.init(), null)
context.playUrl('/during-cleanup.xml')
}
result.destroy()
assert.equal(env.state.players.length, 1)
env.state.onDestroy = null
context.playUrl('/after-cleanup.xml')
assert.equal(env.state.players.length, 2)
assert.equal(context.imaPlayer.requests.length, 1)
})
}
+5
View File
@@ -155,6 +155,11 @@
dependencies:
"@algolia/client-common" "5.48.0"
"@alugha/ima@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@alugha/ima/-/ima-2.1.0.tgz#f9569fe6b955be7f2472df195527d63b210d34a1"
integrity sha512-gGMWR9QlSJQR4G8p8nYRR/NHSzCCSLq7Q82Xr0BK154nNCfhykpjqr/hGVZdpFaPecyxEMDP3aSp2dzM421KRA==
"@antfu/eslint-config@5.4.1":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@antfu/eslint-config/-/eslint-config-5.4.1.tgz#1a2897a0d3c89f6a73e08d54ba92c2b456b1fd11"