fix(vast): [PKG-VAST-03] preserve npm initialization with explicit workspace mode

This commit is contained in:
Harvey Zhao committed 2026-09-14 22:42:57 +08:00
1 parent 314a1c3163
commit c162d2b3b3
27 files changed
+13189 -191

No files matched your search

+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao * (c) 2017-2026 Harvey Zhao
* Released under the MIT License. * 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 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 !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 d(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function h(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 * (c) 2017-2026 Harvey Zhao
* Released under the MIT License. * 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 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 !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 l(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 h=0;function c(t){return"__private_"+h+++"_"+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
+55 -29
View File
@@ -445,30 +445,54 @@ function createRequest(ima, field, value, config) {
return request; return request;
} }
let nextId = 0; let nextId = 0;
function createContainer(utils) { function createContainer(utils, workspaceMode) {
const container = utils.createElement("div"); const container = utils.createElement("div");
container.id = `art-vast-${Date.now()}-${++nextId}`; container.id = `${workspaceMode ? "art-vast-" : "art-"}${Date.now()}-${++nextId}`;
utils.setStyles(container, { utils.setStyles(container, {
position: "absolute", position: "absolute",
inset: "0", inset: "0",
width: "100%", width: "100%",
height: "100%", height: "100%",
zIndex: "150", zIndex: "150",
backgroundColor: "black", display: "none"
display: "none",
pointerEvents: "auto"
}); });
if (workspaceMode)
utils.setStyles(container, { backgroundColor: "black", pointerEvents: "auto" });
return container; return container;
} }
function createSession(art, ima, utils, closed) { function createSession(art, ima, utils, closed, workspaceMode) {
const { $video, $player } = art.template; const { $video, $player } = art.template;
const adsRenderingSettings = new ima.AdsRenderingSettings(); const adsRenderingSettings = new ima.AdsRenderingSettings();
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; if (workspaceMode) {
adsRenderingSettings.enablePreloading = true; adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
adsRenderingSettings.enablePreloading = true;
}
const playerOptions = new E(); const playerOptions = new E();
let current = null; let current = null;
let initializing = false; let initializing = false;
let releasing = 0; let releasing = 0;
const context = {
art,
playUrl: (url, config = {}) => play("adTagUrl", url, config),
playRes: (response, config = {}) => play("adsResponse", response, config),
init,
ima,
adsRenderingSettings,
playerOptions,
imaPlayer: null,
id: null,
$container: null,
get container() {
return current?.container || null;
}
};
if (workspaceMode) {
Object.defineProperties(context, {
imaPlayer: { get: () => current?.player || null },
id: { get: () => current?.container.id || null },
$container: { get: () => current?.container || null }
});
}
const live = (owner) => current === owner && !owner.released && !closed(); const live = (owner) => current === owner && !owner.released && !closed();
function release(owner) { function release(owner) {
if (current === owner) if (current === owner)
@@ -519,7 +543,7 @@ function createSession(art, ima, utils, closed) {
initializing = true; initializing = true;
let owner; let owner;
try { try {
owner = { container: createContainer(utils), player: null, listeners: [], playing: false, released: false }; owner = { container: createContainer(utils, workspaceMode), player: null, listeners: [], playing: false, released: false };
current = owner; current = owner;
if (!live(owner)) { if (!live(owner)) {
release(owner); release(owner);
@@ -536,12 +560,13 @@ function createSession(art, ima, utils, closed) {
return null; return null;
} }
const allocated = owner; const allocated = owner;
for (const [name, playing] of [ const events = workspaceMode ? [
["AdContentPauseRequested", true], ["AdContentPauseRequested", true],
["AdContentResumeRequested", false], ["AdContentResumeRequested", false],
["AdStarted", true], ["AdStarted", true],
["AdError", false] ["AdError", false]
]) { ] : [];
for (const [name, playing] of events) {
const callback = (event) => { const callback = (event) => {
if (!live(allocated)) if (!live(allocated))
return; return;
@@ -557,6 +582,13 @@ function createSession(art, ima, utils, closed) {
return null; return null;
} }
} }
if (!workspaceMode) {
Object.defineProperties(context, {
imaPlayer: { value: owner.player },
id: { value: owner.container.id },
$container: { value: owner.container }
});
}
return owner.player; return owner.player;
} catch (error) { } catch (error) {
if (owner) { if (owner) {
@@ -572,7 +604,7 @@ function createSession(art, ima, utils, closed) {
} }
} }
function play(field, value, config) { function play(field, value, config) {
if (closed() || current?.playing) if (closed() || workspaceMode && current?.playing)
return; return;
const player = init(); const player = init();
const owner = current; const owner = current;
@@ -586,24 +618,13 @@ function createSession(art, ima, utils, closed) {
if (current) if (current)
release(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 }; return { context, destroy };
} }
function artplayerPluginVast(callback) { function artplayerPluginVast(callback, options = {}) {
const compatibility = options.compatibility;
if (compatibility !== void 0 && compatibility !== "workspace-1.2")
throw new TypeError("Unsupported VAST compatibility mode");
const workspaceMode = compatibility === "workspace-1.2";
return async (art) => { return async (art) => {
let disposed = false; let disposed = false;
let session; let session;
@@ -629,11 +650,16 @@ function artplayerPluginVast(callback) {
if (closed()) if (closed())
return result; return result;
const utils = art.constructor.utils; const utils = art.constructor.utils;
session = createSession(art, ima, utils, closed); session = createSession(art, ima, utils, closed, workspaceMode);
if (closed()) { if (closed()) {
dispose(); dispose();
return result; return result;
} }
if (!workspaceMode) {
session.context.init();
if (closed())
return result;
}
if (typeof callback === "function") if (typeof callback === "function")
await callback(session.context); await callback(session.context);
return result; return result;
+2 -2
View File
@@ -51,7 +51,7 @@
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:llm && yarn build:vconsole && yarn build:site-notices && yarn build:test && yarn build:docs && yarn test:imports", "ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:llm && yarn build:vconsole && yarn build:site-notices && yarn build:test && yarn build:docs && yarn test:imports",
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js", "test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js",
"typecheck": "node scripts/typecheck.mjs", "typecheck": "node scripts/typecheck.mjs",
"test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js test/multiple-subtitles-ass.test.js test/multiple-subtitles-caption.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast-exports.test.js 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/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js", "test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js test/multiple-subtitles-ass.test.js test/multiple-subtitles-caption.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast-exports.test.js test/vast.test.js test/vast-lifecycle.test.js test/vast-compatibility.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/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
"test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs", "test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
"test": "yarn test:node && yarn test:baseline", "test": "yarn test:node && yarn test:baseline",
"test:browser": "playwright test", "test:browser": "playwright test",
@@ -66,7 +66,7 @@
"test:dash-types-package": "node refactor/scripts/dash-package-types.mjs", "test:dash-types-package": "node refactor/scripts/dash-package-types.mjs",
"test:ads-types-package": "node refactor/scripts/ads-package-types.mjs", "test:ads-types-package": "node refactor/scripts/ads-package-types.mjs",
"test:ads-native-visibility": "node refactor/scripts/ads-native-visibility.mjs", "test:ads-native-visibility": "node refactor/scripts/ads-native-visibility.mjs",
"test:vast": "node --test test/vast-exports.test.js test/vast.test.js test/vast-lifecycle.test.js", "test:vast": "node --test test/vast-exports.test.js test/vast.test.js test/vast-lifecycle.test.js test/vast-compatibility.test.js",
"check:commits": "node refactor/scripts/commit-audit.mjs", "check:commits": "node refactor/scripts/commit-audit.mjs",
"check:impact": "node refactor/scripts/impact.mjs", "check:impact": "node refactor/scripts/impact.mjs",
"check:contracts": "node refactor/scripts/contracts.mjs", "check:contracts": "node refactor/scripts/contracts.mjs",
+43 -19
View File
@@ -1,19 +1,21 @@
# VAST implementation and maintenance # VAST implementation and maintenance
This is the PKG-VAST-03 implementation checkpoint. The published-versus-workspace The runtime follows the accepted published-versus-workspace initialization decision.
initialization conflict remains open; this checkpoint is not release acceptance. Public declaration reconciliation and real IMA validation remain separate release gates.
## Module map ## Module map
- `src/index.ts` owns the core destroy subscription before SDK loading begins, - `src/index.ts` owns the core destroy subscription before SDK loading begins,
awaits SDK loading and the user callback, and preserves original rejection values. awaits SDK loading and the user callback, and preserves original rejection values.
Its writable `.default` self-reference retains historical CommonJS default calls It snapshots the optional compatibility mode at factory creation and initializes
the default session before the callback. Its writable `.default` self-reference retains historical CommonJS default calls
alongside the current direct factory. It does not wrap registration or load IMA. alongside the current direct factory. It does not wrap registration or load IMA.
- `src/sdk.ts` is the only runtime Glomex import boundary. It also constructs IMA - `src/sdk.ts` is the only runtime Glomex import boundary. It also constructs IMA
requests, preserving own/inherited configuration fields and primary-field overrides. requests, preserving own/inherited configuration fields and primary-field overrides.
- `src/session.ts` owns one current SDK/container pair, the four ad-event subscriptions, - `src/session.ts` owns one current SDK/container pair, initialization rollback and
active-ad request suppression, initialization rollback and explicit recreation. explicit recreation. Only workspace mode adds four ad-event subscriptions and
- `src/view.ts` creates the existing overlay styles and distinct per-module container active-ad request suppression; default mode delegates those behaviors to the SDK.
- `src/view.ts` creates each mode's historical overlay styles and distinct per-module container
IDs, including multiple instances initialized in the same millisecond. IDs, including multiple instances initialized in the same millisecond.
- `src/types.ts` describes the implementation context and SDK boundary. Public legacy - `src/types.ts` describes the implementation context and SDK boundary. Public legacy
declarations in `types/` remain separate pending PKG-VAST-04 reconciliation. declarations in `types/` remain separate pending PKG-VAST-04 reconciliation.
@@ -50,17 +52,34 @@ it finishes. SDK content pause/resume behavior is not duplicated in this wrapper
## Compatibility and remaining work ## Compatibility and remaining work
The checkpoint retains the workspace callback order, lazy initialization, mutable Calling `artplayerPluginVast(callback)` preserves npm1.0.0 behavior: the SDK and
settings/options identities, default flags, void request methods, and asynchronous container exist before the callback, including when no callback was supplied.
outer attachment. The old `require(...).default(...)` call is restored, while the `imaPlayer`, `id` and `$container` are writable/enumerable/configurable data fields.
current callable export remains callable. This does not recreate the old namespace SDK settings retain their own defaults. The wrapper forwards each explicit request
object's reflection shape. The `id`/`$container` aliases, eager and leaves ad visibility to the SDK, without adding workspace event listeners.
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 Passing `{ compatibility: 'workspace-1.2' }` as the second argument retains unpublished
collisions; the existing `art-vast-` prefix and CSS styles remain. IDs are internal workspace behavior: no player/container until `init`, `playUrl` or `playRes`; callers
in the workspace context; published ID compatibility is tracked with the above decision. can mutate the original settings/options before initialization. Preloading and restore
custom playback state default to true. Four ad events control the black overlay and
active-ad suppression. Resource getters track the current session and become null
on release. The mode is captured once; an unknown value throws TypeError before loading.
Default data fields retain their last allocated values after destroy, as published
snapshots; `container` is a live getter and becomes null. Explicit recreation updates
the data fields to the new session. Public writes to these snapshots do not transfer
resource ownership. Never use a retained disposed SDK as an active session.
Both modes retain void request methods, async registration and SDK/callback rejection.
The old `require(...).default(...)` call and current callable export remain available;
this does not recreate the old namespace object's reflection shape. Contradictory
public return declarations and the new second-argument declaration remain task04.
See [the accepted decision](../../refactor/vast-compatibility-decision.md).
Overlay IDs use the historical `art-` default prefix or `art-vast-` workspace prefix,
with a monotonic suffix to fix same-millisecond collisions. Default styles do not add
workspace's black background or pointer-events rule. Container identity/ownership,
initial display:none and existing position/inset/size/z-index are preserved.
`@alugha/ima@2.1.0` is a direct pinned dependency because Glomex declarations import its `@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 types while Glomex lists it only as a development dependency. Runtime imports continue
@@ -68,11 +87,16 @@ to use Glomex1.21.2. No new direct remote loader or SDK major upgrade was introd
## Validation ## Validation
Run `yarn test:vast`, `yarn typecheck`, `yarn build artplayer-plugin-vast`, and Run `yarn test:vast`, `node node_modules/typescript/bin/tsc -p
`yarn test:browser vast.spec.js` using the repository pinned toolchain. The node and packages/artplayer-plugin-vast/tsconfig.json --noEmit`, `yarn build artplayer-plugin-vast`,
and `yarn test:browser:source test/browser/vast.spec.js --workers=2` using the pinned
toolchain. Do not use the historical `tsc` binary alias for source checking. The node and
browser wrapper tests substitute only the Glomex SDK boundary. They cover current 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 frozen historical sources, real published core5.1.7/5.4.0 and candidate5.4.1 registration, real DOM and
main-video decoding; they do not certify Google IMA ads or final npm distribution. main-video decoding; they do not certify Google IMA ads or final npm distribution.
Candidate default and explicit workspace cases execute identical compiled code with
different options; historical fixtures remain immutable. Compatibility tests compare
callback timing, data descriptors, defaults and request forwarding to actual npm source.
`test/vast-exports.test.js` exercises bundled CommonJS/global/AMD and native ESM `test/vast-exports.test.js` exercises bundled CommonJS/global/AMD and native ESM
import, using an already destroyed host to verify registration without loading ads. import, using an already destroyed host to verify registration without loading ads.
+26
View File
@@ -2,6 +2,32 @@
VAST plugin for ArtPlayer VAST plugin for ArtPlayer
## Initialization compatibility
Existing npm calls keep creating the SDK player before the callback:
```js
artplayerPluginVast(({ imaPlayer, playUrl }) => {
imaPlayer.addEventListener('AdStarted', onAdStarted)
playButton.onclick = () => playUrl(adTagUrl)
})
```
Code using the unpublished workspace 1.2 lazy initialization must select it explicitly:
```js
artplayerPluginVast((context) => {
context.playerOptions.autoResize = false
playButton.onclick = () => context.playUrl(adTagUrl)
}, { compatibility: 'workspace-1.2' })
```
The callback may be asynchronous. Core destruction releases the ad session and prevents
later requests or recreation. Explicit plugin `destroy()` allows a new ad session while
the core is alive. See the architecture guide for mode-specific fields and defaults.
Public TypeScript declarations for the second argument are still being reconciled in
PKG-VAST-04; these are JavaScript usage examples, not completed declaration acceptance.
## Maintenance ## Maintenance
Implementation modules, lifecycle ownership, test commands and pending compatibility Implementation modules, lifecycle ownership, test commands and pending compatibility
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao * (c) 2017-2026 Harvey Zhao
* Released under the MIT License. * 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 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 !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 d(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function h(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 * (c) 2017-2026 Harvey Zhao
* Released under the MIT License. * 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 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 !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 l(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 h=0;function c(t){return"__private_"+h+++"_"+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
+55 -29
View File
@@ -445,30 +445,54 @@ function createRequest(ima, field, value, config) {
return request; return request;
} }
let nextId = 0; let nextId = 0;
function createContainer(utils) { function createContainer(utils, workspaceMode) {
const container = utils.createElement("div"); const container = utils.createElement("div");
container.id = `art-vast-${Date.now()}-${++nextId}`; container.id = `${workspaceMode ? "art-vast-" : "art-"}${Date.now()}-${++nextId}`;
utils.setStyles(container, { utils.setStyles(container, {
position: "absolute", position: "absolute",
inset: "0", inset: "0",
width: "100%", width: "100%",
height: "100%", height: "100%",
zIndex: "150", zIndex: "150",
backgroundColor: "black", display: "none"
display: "none",
pointerEvents: "auto"
}); });
if (workspaceMode)
utils.setStyles(container, { backgroundColor: "black", pointerEvents: "auto" });
return container; return container;
} }
function createSession(art, ima, utils, closed) { function createSession(art, ima, utils, closed, workspaceMode) {
const { $video, $player } = art.template; const { $video, $player } = art.template;
const adsRenderingSettings = new ima.AdsRenderingSettings(); const adsRenderingSettings = new ima.AdsRenderingSettings();
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; if (workspaceMode) {
adsRenderingSettings.enablePreloading = true; adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
adsRenderingSettings.enablePreloading = true;
}
const playerOptions = new E(); const playerOptions = new E();
let current = null; let current = null;
let initializing = false; let initializing = false;
let releasing = 0; let releasing = 0;
const context = {
art,
playUrl: (url, config = {}) => play("adTagUrl", url, config),
playRes: (response, config = {}) => play("adsResponse", response, config),
init,
ima,
adsRenderingSettings,
playerOptions,
imaPlayer: null,
id: null,
$container: null,
get container() {
return current?.container || null;
}
};
if (workspaceMode) {
Object.defineProperties(context, {
imaPlayer: { get: () => current?.player || null },
id: { get: () => current?.container.id || null },
$container: { get: () => current?.container || null }
});
}
const live = (owner) => current === owner && !owner.released && !closed(); const live = (owner) => current === owner && !owner.released && !closed();
function release(owner) { function release(owner) {
if (current === owner) if (current === owner)
@@ -519,7 +543,7 @@ function createSession(art, ima, utils, closed) {
initializing = true; initializing = true;
let owner; let owner;
try { try {
owner = { container: createContainer(utils), player: null, listeners: [], playing: false, released: false }; owner = { container: createContainer(utils, workspaceMode), player: null, listeners: [], playing: false, released: false };
current = owner; current = owner;
if (!live(owner)) { if (!live(owner)) {
release(owner); release(owner);
@@ -536,12 +560,13 @@ function createSession(art, ima, utils, closed) {
return null; return null;
} }
const allocated = owner; const allocated = owner;
for (const [name, playing] of [ const events = workspaceMode ? [
["AdContentPauseRequested", true], ["AdContentPauseRequested", true],
["AdContentResumeRequested", false], ["AdContentResumeRequested", false],
["AdStarted", true], ["AdStarted", true],
["AdError", false] ["AdError", false]
]) { ] : [];
for (const [name, playing] of events) {
const callback = (event) => { const callback = (event) => {
if (!live(allocated)) if (!live(allocated))
return; return;
@@ -557,6 +582,13 @@ function createSession(art, ima, utils, closed) {
return null; return null;
} }
} }
if (!workspaceMode) {
Object.defineProperties(context, {
imaPlayer: { value: owner.player },
id: { value: owner.container.id },
$container: { value: owner.container }
});
}
return owner.player; return owner.player;
} catch (error) { } catch (error) {
if (owner) { if (owner) {
@@ -572,7 +604,7 @@ function createSession(art, ima, utils, closed) {
} }
} }
function play(field, value, config) { function play(field, value, config) {
if (closed() || current?.playing) if (closed() || workspaceMode && current?.playing)
return; return;
const player = init(); const player = init();
const owner = current; const owner = current;
@@ -586,24 +618,13 @@ function createSession(art, ima, utils, closed) {
if (current) if (current)
release(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 }; return { context, destroy };
} }
function artplayerPluginVast(callback) { function artplayerPluginVast(callback, options = {}) {
const compatibility = options.compatibility;
if (compatibility !== void 0 && compatibility !== "workspace-1.2")
throw new TypeError("Unsupported VAST compatibility mode");
const workspaceMode = compatibility === "workspace-1.2";
return async (art) => { return async (art) => {
let disposed = false; let disposed = false;
let session; let session;
@@ -629,11 +650,16 @@ function artplayerPluginVast(callback) {
if (closed()) if (closed())
return result; return result;
const utils = art.constructor.utils; const utils = art.constructor.utils;
session = createSession(art, ima, utils, closed); session = createSession(art, ima, utils, closed, workspaceMode);
if (closed()) { if (closed()) {
dispose(); dispose();
return result; return result;
} }
if (!workspaceMode) {
session.context.init();
if (closed())
return result;
}
if (typeof callback === "function") if (typeof callback === "function")
await callback(session.context); await callback(session.context);
return result; return result;
+12 -3
View File
@@ -1,9 +1,13 @@
import type Artplayer from 'artplayer' import type Artplayer from 'artplayer'
import type { Callback, Result } from './types' import type { Callback, CompatibilityOptions, Result } from './types'
import { loadImaSdk } from './sdk' import { loadImaSdk } from './sdk'
import { createSession } from './session' import { createSession } from './session'
function artplayerPluginVast(callback?: Callback) { function artplayerPluginVast(callback?: Callback, options: CompatibilityOptions = {}) {
const compatibility = options.compatibility
if (compatibility !== undefined && compatibility !== 'workspace-1.2')
throw new TypeError('Unsupported VAST compatibility mode')
const workspaceMode = compatibility === 'workspace-1.2'
return async (art: Artplayer): Promise<Result> => { return async (art: Artplayer): Promise<Result> => {
let disposed = false let disposed = false
let session: ReturnType<typeof createSession> | undefined let session: ReturnType<typeof createSession> | undefined
@@ -28,11 +32,16 @@ function artplayerPluginVast(callback?: Callback) {
if (closed()) if (closed())
return result return result
const utils = (art.constructor as typeof Artplayer).utils const utils = (art.constructor as typeof Artplayer).utils
session = createSession(art, ima, utils, closed) session = createSession(art, ima, utils, closed, workspaceMode)
if (closed()) { if (closed()) {
dispose() dispose()
return result return result
} }
if (!workspaceMode) {
session.context.init()
if (closed())
return result
}
if (typeof callback === 'function') if (typeof callback === 'function')
await callback(session.context) await callback(session.context)
return result return result
+45 -22
View File
@@ -12,16 +12,39 @@ interface Owner {
released: boolean released: boolean
} }
export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, closed: () => boolean) { export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, closed: () => boolean, workspaceMode: boolean) {
const { $video, $player } = art.template const { $video, $player } = art.template
const adsRenderingSettings = new ima.AdsRenderingSettings() const adsRenderingSettings = new ima.AdsRenderingSettings()
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true if (workspaceMode) {
adsRenderingSettings.enablePreloading = true adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true
adsRenderingSettings.enablePreloading = true
}
const playerOptions = new PlayerOptions() const playerOptions = new PlayerOptions()
let current: Owner | null = null let current: Owner | null = null
let initializing = false let initializing = false
let releasing = 0 let releasing = 0
const context: Context = {
art,
playUrl: (url, config = {}) => play('adTagUrl', url, config),
playRes: (response, config = {}) => play('adsResponse', response, config),
init,
ima,
adsRenderingSettings,
playerOptions,
imaPlayer: null,
id: null,
$container: null,
get container() { return current?.container || null },
}
if (workspaceMode) {
Object.defineProperties(context, {
imaPlayer: { get: () => current?.player || null },
id: { get: () => current?.container.id || null },
$container: { get: () => current?.container || null },
})
}
const live = (owner: Owner) => current === owner && !owner.released && !closed() const live = (owner: Owner) => current === owner && !owner.released && !closed()
function release(owner: Owner): void { function release(owner: Owner): void {
@@ -77,7 +100,7 @@ export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, clo
initializing = true initializing = true
let owner: Owner | undefined let owner: Owner | undefined
try { try {
owner = { container: createContainer(utils), player: null, listeners: [], playing: false, released: false } owner = { container: createContainer(utils, workspaceMode), player: null, listeners: [], playing: false, released: false }
current = owner current = owner
if (!live(owner)) { if (!live(owner)) {
release(owner) release(owner)
@@ -94,12 +117,15 @@ export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, clo
return null return null
} }
const allocated = owner const allocated = owner
for (const [name, playing] of [ const events = workspaceMode
['AdContentPauseRequested', true], ? [
['AdContentResumeRequested', false], ['AdContentPauseRequested', true],
['AdStarted', true], ['AdContentResumeRequested', false],
['AdError', false], ['AdStarted', true],
] as const) { ['AdError', false],
] as const
: []
for (const [name, playing] of events) {
const callback: EventListener = (event) => { const callback: EventListener = (event) => {
if (!live(allocated)) if (!live(allocated))
return return
@@ -115,6 +141,14 @@ export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, clo
return null return null
} }
} }
if (!workspaceMode) {
// Published fields are writable data properties, not the workspace getters.
Object.defineProperties(context, {
imaPlayer: { value: owner.player },
id: { value: owner.container.id },
$container: { value: owner.container },
})
}
return owner.player return owner.player
} }
catch (error) { catch (error) {
@@ -132,7 +166,7 @@ export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, clo
} }
function play(field: 'adTagUrl' | 'adsResponse', value: string, config: RequestConfig): void { function play(field: 'adTagUrl' | 'adsResponse', value: string, config: RequestConfig): void {
if (closed() || current?.playing) if (closed() || (workspaceMode && current?.playing))
return return
const player = init() const player = init()
const owner = current const owner = current
@@ -149,16 +183,5 @@ export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, clo
release(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 } return { context, destroy }
} }
@@ -5,6 +5,10 @@ import type Artplayer from 'artplayer'
export type Utilities = Pick<typeof Artplayer.utils, 'createElement' | 'setStyles'> export type Utilities = Pick<typeof Artplayer.utils, 'createElement' | 'setStyles'>
export type RequestConfig = Record<string, unknown> export type RequestConfig = Record<string, unknown>
export interface CompatibilityOptions {
compatibility?: 'workspace-1.2'
}
export interface Context { export interface Context {
art: Artplayer art: Artplayer
playUrl: (url: string, config?: RequestConfig) => void playUrl: (url: string, config?: RequestConfig) => void
@@ -14,6 +18,8 @@ export interface Context {
adsRenderingSettings: google.ima.AdsRenderingSettings adsRenderingSettings: google.ima.AdsRenderingSettings
playerOptions: PlayerOptions playerOptions: PlayerOptions
readonly imaPlayer: Player | null readonly imaPlayer: Player | null
readonly id: string | null
readonly $container: HTMLDivElement | null
readonly container: HTMLDivElement | null readonly container: HTMLDivElement | null
} }
+4 -4
View File
@@ -2,18 +2,18 @@ import type { Utilities } from './types'
let nextId = 0 let nextId = 0
export function createContainer(utils: Utilities): HTMLDivElement { export function createContainer(utils: Utilities, workspaceMode: boolean): HTMLDivElement {
const container = utils.createElement('div') const container = utils.createElement('div')
container.id = `art-vast-${Date.now()}-${++nextId}` container.id = `${workspaceMode ? 'art-vast-' : 'art-'}${Date.now()}-${++nextId}`
utils.setStyles(container, { utils.setStyles(container, {
position: 'absolute', position: 'absolute',
inset: '0', inset: '0',
width: '100%', width: '100%',
height: '100%', height: '100%',
zIndex: '150', zIndex: '150',
backgroundColor: 'black',
display: 'none', display: 'none',
pointerEvents: 'auto',
}) })
if (workspaceMode)
utils.setStyles(container, { backgroundColor: 'black', pointerEvents: 'auto' })
return container return container
} }
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,61 @@
# PKG-VAST-03:按已确认决策完成运行时兼容与资源整理
基线314a1c3163e4c5bc8d1a9f1432726824ea3af7dc;本记录完成早期
[03检查点](2026-09-12-PKG-VAST-03-lifecycle.md)的剩余初始化兼容任务。
2026-09-14用户明确接受“保留 npm 默认行为,显式选择工作区惰性模式”,
[具体决策与迁移示例](../vast-compatibility-decision.md)现在是实现依据。
## 实现及兼容边界
五个TS模块已分离工厂/异步注册、SDK边界、会话资源、DOM样式和内部类型。
默认工厂在callback前创建Player,恢复实际npm1.0.0的id/$container/imaPlayer
可写数据属性与SDK默认设置。默认包装层不添加工作区四事件显隐或活动请求抑制,
透明容器保留发布样式,显式请求继续逐次转发;没有把SDK行为复制到包装层。
可选第二参数`{ compatibility: 'workspace-1.2' }`保留未发布工作区的惰性初始化、
设置/选项原对象、两个默认flags、黑色overlay、四事件显隐、活动广告抑制和资源
实时getter。模式在工厂创建时快照一次,未知值在加载SDK前抛TypeError。
普通已发布JS调用无需变化;依赖未发布默认惰性的代码必须按已批准方案增加选项。
两种模式共享早期修复的core销毁终止、晚到SDK/回调取消、失败回滚、会话身份隔离、
重复清理与显式重建。默认发布数据字段保留已释放资源快照,扩展container getter归零;
后续显式重建更新数据字段,core销毁禁止重建。工作区资源getter随当前会话归零。
ID保留各自art-/art-vast-前缀并增加递增后缀,保留已复现同毫秒碰撞的修复。
公开声明没有在本任务完成:04仍须协调历史同步误声明、回调字段、第二参数和准确
类型入口;本次运行时授权不自动授权其他类型取舍。实际Google IMA由05验收,最终
安装分发由06验收。README明确JS示例及当前声明门槛;未改变包版本或依赖/lockfile。
现有docs/assets/example/vast.js使用默认回调和playUrl,符合npm模式,不需改写。
## 验证与失败记录
完整输入/日志/产物指纹见[验证索引](../baselines/vast-compatibility-validation.json)。
- 新兼容文件初始7项:旧候选2通过5失败,包含默认初始化、旧字段与提前构造失败。
修复后7通过,再补回调失败的二次清理异常、显式重建和选项快照/非法值,最终10通过。
- 当前完整`yarn test:vast`82通过、零跳过;包括旧发布/冻结工作区观察、显式模式及默认
生命周期。该文件进入test:vast/test:unit;工程脚本相关12项测试通过。
- 三桌面引擎×三个核心×四种实现/模式162通过:99候选、63历史。核验异步注册门控、
回调开始前资源、字段描述符、默认值、真实DOM显隐/清理、主片解码及切源。
候选两模式执行同一份编译源码,以选项区分;第三方SDK为明确标记的受控记录器。
本次报告确认核心实际为npm5.1.7、npm5.4.0、候选5.4.1;早期文档将published路径
写成5.4.1不准确,以附件中的来源哈希和实际coreVersion为准,本次更正维护说明。
- 首轮153通过9失败:旧candidate显隐用例沿用工作区art-vast-选择器与四事件显隐,
和已确认的npm默认语义不符。保留原日志/trace;按两种历史行为验证实际容器数、
0/4监听、显隐,再验证重建/销毁。回调失败也按实际容器前缀检测,避免虚假零残留。
增加回调开始时字段/时机断言后162通过;源码lint整理后最终162再次通过。
- 严格TS5.9.3及源/测试lint通过。初次lint6项格式/声明顺序错误已修正并保留日志;
没有引入禁用规则。严格固定工具链检查通过。
- 正常VAST构建三格式成功,dist与docs/compiled对应文件哈希一致。实际main+ESM和
legacy+ESM分别4项导出用例通过:CommonJS/global/AMD和原生ESM,均验证default别名。
这些产物用例使用已销毁宿主,不执行IMA;不能把它们解释为正式SDK广告播放通过。
## 任务边界及回退
PKG-VAST-03完成运行时结构/资源/初始化协调,VAST-CONTEXT-01按用户决策接受限定范围。
VAST-LIFE-01保留实际供应商恢复验证门槛;VAST-TYPE/DIST及04/05/06仍开放。
没有使用VPN例外、模拟广告成功、远端CI或npm发布声明。
本任务独立本地commit包含源、测试、构建、文档、任务及风险索引,提交后核验审计。
回退本提交并重建VAST会恢复此前默认工作区初始化,也撤销本次兼容选择;旧已发布
默认调用的失败将重现。早期资源修复检查点不随本提交回退而消失。
+3 -3
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 262 项,范围 22 个包及工作区/示例。 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 262 项,范围 22 个包及工作区/示例。
状态:todo 48 / doing 21 / blocked 0 / done 193 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 状态:todo 48 / doing 20 / blocked 0 / done 194 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -296,7 +296,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-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-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 | doing | | PKG-VAST-03 | artplayer-plugin-vast<br>整理内部职责与资源 | PKG-VAST-02, CORE-10, CORE-13 | SDK loader、IMA adapter、广告状态和 DOM 清理分离;保持发布 callback 别名,明确初始化冲突;核心终止与显式 destroy 后重建区分 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
| PKG-VAST-04 | artplayer-plugin-vast<br>迁移自有源码和公开类型 | PKG-VAST-03, ENG-04, ENG-06, CORE-07, PKG-VAST-07 | IMA/context/Promise 真实类型,外部 SDK 动态对象限定边界;修复 @alugha/ima 声明依赖链,保留 export= 和 require.default 消费;同步错误声明的兼容决策单列 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo | | PKG-VAST-04 | artplayer-plugin-vast<br>迁移自有源码和公开类型 | PKG-VAST-03, ENG-04, ENG-06, CORE-07, PKG-VAST-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-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 | | 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 |
@@ -600,7 +600,7 @@
- PKG-ADS-05: [记录](changes/2026-09-12-PKG-ADS-05-browser.md) [记录](baselines/ads-ui-visibility-validation.json) - 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-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-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) - PKG-VAST-03: [记录](changes/2026-09-12-PKG-VAST-03-lifecycle.md) [记录](baselines/vast-lifecycle-validation.json) [记录](vast-compatibility-decision.md) [记录](changes/2026-09-14-PKG-VAST-03-compatibility.md) [记录](baselines/vast-compatibility-validation.json)
- PKG-CAST-01: [记录](baselines/chromecast-release.json) [记录](baselines/chromecast-contract.md) [记录](baselines/chromecast-contract-validation.json) [记录](changes/2026-09-13-PKG-CAST-01-contracts.md) - PKG-CAST-01: [记录](baselines/chromecast-release.json) [记录](baselines/chromecast-contract.md) [记录](baselines/chromecast-contract-validation.json) [记录](changes/2026-09-13-PKG-CAST-01-contracts.md)
- PKG-CAST-02: [记录](changes/2026-09-13-PKG-CAST-02-failures.md) [记录](baselines/chromecast-failures-validation.json) - PKG-CAST-02: [记录](changes/2026-09-13-PKG-CAST-02-failures.md) [记录](baselines/chromecast-failures-validation.json)
- PKG-CAST-03: [记录](changes/2026-09-13-PKG-CAST-03-runtime.md) [记录](baselines/chromecast-runtime-validation.json) - PKG-CAST-03: [记录](changes/2026-09-13-PKG-CAST-03-runtime.md) [记录](baselines/chromecast-runtime-validation.json)
+20
View File
@@ -1,5 +1,25 @@
# 进度与证据 # 进度与证据
## PKG-VAST-03 已确认初始化策略与运行时资源整理完成
用户于2026-09-14明确接受npm提前初始化默认、显式workspace-1.2惰性模式。
默认恢复回调前Player、id/$container/imaPlayer可写数据字段及SDK原默认设置;
显式模式保留工作区四事件显隐、活动请求抑制、可变设置和资源实时getter。
两者共享销毁终止、失败回滚、旧会话隔离和显式重建修复;模块地图/迁移JS示例同步。
当前Node82通过,三浏览器162通过(99候选、63历史),工程脚本12通过;严格
TS5.9.3、lint和工具链检查通过。首轮浏览器153通过9失败保留:旧断言误用
工作区选择器/显隐语义,现按历史责任分模式并补充回调起始状态断言,未放宽清理。
实际核心为npm5.1.7/npm5.4.0/候选5.4.1,纠正旧维护说明误称published5.4.1。
正常构建三格式及docs副本一致,实际main+ESM/legacy+ESM各4项导出检查通过。
详见[变更](changes/2026-09-14-PKG-VAST-03-compatibility.md)、
[指纹和结果](baselines/vast-compatibility-validation.json)及
[接受的决策](vast-compatibility-decision.md)。本项完成,262项现194 done、
20 doing、48 todo;初始化风险按用户决定限定接受。SDK仍受控,实际IMA恢复、
公开声明(包括第二参数)、安装分发分别留在05/04/06;未用VPN例外,未push/发布。
本任务独立本地提交,提交后先核验审计;下一步处理VAST公开类型04。
## PKG-DANMUKU-MASK-LOAD-01 真实模型负载漏显与积压修复 ## PKG-DANMUKU-MASK-LOAD-01 真实模型负载漏显与积压修复
连续播放采样补回主线程卡顿跨过的既有wait行,公开readys窗口不变;允许重叠 连续播放采样补回主线程卡顿跨过的既有wait行,公开readys窗口不变;允许重叠
+1 -1
View File
@@ -147,7 +147,7 @@
| ADS-LIFE-01 | resolved / 已复现 | Ads 计时链、重复/过早调用、工厂复用与销毁缺乏资源边界 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 | | ADS-LIFE-01 | resolved / 已复现 | Ads 计时链、重复/过早调用、工厂复用与销毁缺乏资源边界 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| ADS-MEDIA-01 | open / 已复现 | Ads metadata 与 play 拒绝、隐藏页媒体状态和内容恢复仍缺真实验证 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 | | ADS-MEDIA-01 | open / 已复现 | Ads metadata 与 play 拒绝、隐藏页媒体状态和内容恢复仍缺真实验证 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| ADS-UI-01 | resolved / 已复现 | Ads 零等待跳过与初始状态不符,全屏图标/外部状态需核验 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 | | ADS-UI-01 | resolved / 已复现 | Ads 零等待跳过与初始状态不符,全屏图标/外部状态需核验 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| VAST-CONTEXT-01 | open / 已复现 | Published VAST callback aliases and eager Player differ from the unpublished lazy workspace | PKG-VAST-02, PKG-VAST-03, PKG-VAST-04 | | VAST-CONTEXT-01 | accepted-with-scope / 已复现 | Published VAST callback aliases and eager Player differ from the unpublished lazy workspace | PKG-VAST-02, PKG-VAST-03, PKG-VAST-04 |
| VAST-TYPE-01 | open / 已复现 | VAST declares an async factory as synchronous and imports an unavailable SDK type dependency | PKG-VAST-04, PKG-VAST-06 | | VAST-TYPE-01 | open / 已复现 | VAST declares an async factory as synchronous and imports an unavailable SDK type dependency | PKG-VAST-04, PKG-VAST-06 |
| VAST-LIFE-01 | open / 已复现 | VAST SDK and container lack core destruction ownership and pending work cancellation | PKG-VAST-02, PKG-VAST-03, PKG-VAST-05 | | VAST-LIFE-01 | open / 已复现 | VAST SDK and container lack core destruction ownership and pending work cancellation | PKG-VAST-02, PKG-VAST-03, PKG-VAST-05 |
| VAST-DIST-01 | open / 已复现 | Published VAST CommonJS default namespace differs from the current callable distribution | PKG-VAST-04, PKG-VAST-06, PKG-VAST-07 | | VAST-DIST-01 | open / 已复现 | Published VAST CommonJS default namespace differs from the current callable distribution | PKG-VAST-04, PKG-VAST-06, PKG-VAST-07 |
+15 -5
View File
@@ -3255,7 +3255,7 @@
"id": "VAST-CONTEXT-01", "id": "VAST-CONTEXT-01",
"title": "Published VAST callback aliases and eager Player differ from the unpublished lazy workspace", "title": "Published VAST callback aliases and eager Player differ from the unpublished lazy workspace",
"confirmation": "reproduced", "confirmation": "reproduced",
"status": "open", "status": "accepted-with-scope",
"owners": [ "owners": [
"PKG-VAST-02", "PKG-VAST-02",
"PKG-VAST-03", "PKG-VAST-03",
@@ -3266,10 +3266,18 @@
"refactor/baselines/vast-release.json", "refactor/baselines/vast-release.json",
"refactor/changes/2026-09-12-PKG-VAST-01-contract.md", "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",
"refactor/changes/2026-09-14-PKG-VAST-03-compatibility.md",
"refactor/baselines/vast-compatibility-validation.json"
],
"compatibleResolution": "User accepted npm eager default and explicit workspace-1.2 lazy mode. Published callback data fields, SDK defaults, container styles and request forwarding are preserved; workspace extensions are opt-in.",
"closureCriteria": "Published and workspace consumer fixtures cover all callback fields and initialization timing; any irreconcilable behavior has a concrete decision rather than implicit waiver.",
"resolutionEvidence": [
"refactor/changes/2026-09-14-PKG-VAST-03-compatibility.md",
"refactor/baselines/vast-compatibility-validation.json",
"refactor/vast-compatibility-decision.md" "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.", "resolutionRationale": "The mutually exclusive initialization timing is explicitly approved and covered by published/workspace consumer tests. Unpublished lazy callers add the second option; public declarations remain VAST-TYPE-01/task04 and real IMA remains task05."
"closureCriteria": "Published and workspace consumer fixtures cover all callback fields and initialization timing; any irreconcilable behavior has a concrete decision rather than implicit waiver."
}, },
{ {
"id": "VAST-TYPE-01", "id": "VAST-TYPE-01",
@@ -3312,11 +3320,13 @@
"refactor/baselines/vast-core.json", "refactor/baselines/vast-core.json",
"refactor/vast-validation.md", "refactor/vast-validation.md",
"refactor/baselines/vast-lifecycle-validation.json", "refactor/baselines/vast-lifecycle-validation.json",
"refactor/changes/2026-09-12-PKG-VAST-03-lifecycle.md" "refactor/changes/2026-09-12-PKG-VAST-03-lifecycle.md",
"refactor/changes/2026-09-14-PKG-VAST-03-compatibility.md",
"refactor/baselines/vast-compatibility-validation.json"
], ],
"compatibleResolution": "Reproduce pending SDK/callback destruction, errors, duplicate IDs and stale ad events before adding compatible cleanup and state ownership.", "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.", "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. 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." "workspaceState": "Task03 runtime ownership and both approved modes pass82 Node and162 real-browser wrapper checks (99 candidate,63 historical), with controlled SDK only. Eager callback errors/cancellation, rollback and explicit recreation are covered. Risk remains open for actual vendor media restoration in task05; no VPN exception was used."
}, },
{ {
"id": "VAST-DIST-01", "id": "VAST-DIST-01",
+4 -2
View File
@@ -3086,14 +3086,16 @@
"CORE-10", "CORE-10",
"CORE-13" "CORE-13"
], ],
"status": "doing", "status": "done",
"risk": "H", "risk": "H",
"deliverable": "SDK loader、IMA adapter、广告状态和 DOM 清理分离;保持发布 callback 别名,明确初始化冲突;核心终止与显式 destroy 后重建区分", "deliverable": "SDK loader、IMA adapter、广告状态和 DOM 清理分离;保持发布 callback 别名,明确初始化冲突;核心终止与显式 destroy 后重建区分",
"acceptance": "结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过", "acceptance": "结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过",
"evidence": [ "evidence": [
"changes/2026-09-12-PKG-VAST-03-lifecycle.md", "changes/2026-09-12-PKG-VAST-03-lifecycle.md",
"baselines/vast-lifecycle-validation.json", "baselines/vast-lifecycle-validation.json",
"vast-compatibility-decision.md" "vast-compatibility-decision.md",
"changes/2026-09-14-PKG-VAST-03-compatibility.md",
"baselines/vast-compatibility-validation.json"
] ]
}, },
{ {
+29 -9
View File
@@ -1,4 +1,8 @@
# VAST 初始化兼容决策:待用户选择 # VAST 初始化兼容决策:已确认并完成运行时实施
2026-09-14用户明确接受:“保留 npm 默认行为,显式选择工作区惰性模式”。
以下推荐方案现在是实施依据,不再等待此项授权。生命周期、类型、实际IMA与
分发仍须分别验证;本确认不豁免这些门槛,也不授权发布。
来源:[真实发布契约](baselines/vast-contract.md);已可重跑的 来源:[真实发布契约](baselines/vast-contract.md);已可重跑的
`vast-contract.test.mjs`及`test/vast.test.js`固定以下矛盾: `vast-contract.test.mjs`及`test/vast.test.js`固定以下矛盾:
@@ -14,12 +18,12 @@
用户操作调用`init()`。在同一默认调用中无法同时保留非null和null,以及相反初始化时机。 用户操作调用`init()`。在同一默认调用中无法同时保留非null和null,以及相反初始化时机。
添加别名或getter也无法同时满足两种值;按callback源码猜测用户版本不可取。 添加别名或getter也无法同时满足两种值;按callback源码猜测用户版本不可取。
## 推荐的具体处置(尚未实现) ## 已接受并实施的具体处置
优先保持实际npm1.0.0默认行为:回调前初始化、提供id/$container,SDK默认设置保持 优先保持实际npm1.0.0默认行为:回调前初始化、提供id/$container,SDK默认设置保持
发布语义;保留workspace新增的init/container/settings等字段作为扩展。 发布语义;保留workspace新增的init/container/settings等字段作为扩展。
确实依赖未发布1.2.0惰性行为的代码,拟通过新的可选第二参数明确选择该兼容模式: 确实依赖未发布1.2.0惰性行为的代码,通过新的可选第二参数明确选择该兼容模式:
```js ```js
// npm旧用法保持原样 // npm旧用法保持原样
@@ -35,11 +39,27 @@ artplayerPluginVast((context) => {
``` ```
这意味着依赖未发布1.2.0默认惰性的代码需要增加一个参数。普通npm1.0.0用户保持 这意味着依赖未发布1.2.0默认惰性的代码需要增加一个参数。普通npm1.0.0用户保持
原调用;这不是声称两种相反语义完全兼容。第二参数仅为待确认方案,当前生产代码 原调用;这不是声称两种相反语义完全兼容。第二参数已获用户明确授权,生产代码
尚未新增它;类型误声明、CJS.default和分发路径仍各自单独验证。 由PKG-VAST-03落实;公开类型由04协调,CJS.default和完整分发各自单独验证。
## 当前检查点 默认模式同时保持发布版的可写数据字段、SDK默认设置、透明容器和每次显式请求
转发;包装层不订阅工作区新增的四个广告事件,不代替SDK管理广告显隐。
显式工作区模式保留黑色容器、四事件显隐、活动广告期间请求抑制及实时getter。
两者共享已修复的资源所有权、初始化回滚和终止规则。
生命周期与资源修复不依赖该选择,已经推进;默认初始化暂保持工作区行为,不把 默认模式销毁后保留三个发布数据字段的快照,容器已移除、SDK已释放;扩展的
PKG-VAST-03或VAST-CONTEXT-01标完成。用户针对Ads的类型推导批准不适用于本决策。 container getter返回null。显式重建会更新三个数据字段;核心销毁则禁止重建。
需要用户确认是否按推荐方案优先实际发布默认行为;也可指定其他取舍。 工作区模式所有资源getter随当前session归零。默认ID保留art-前缀,工作区保留
art-vast-;两者增加单模块递增后缀修复同毫秒ID碰撞,不恢复已复现的重复ID缺陷。
兼容选项在工厂调用时读取一次,后续修改原options不改变已创建的注册函数。
未知compatibility值抛TypeError,发生在加载SDK之前。公开声明尚由04负责,
本步骤的JS示例不是宣称新的第二参数已完成TypeScript消费验收。
## 当前验收边界
PKG-VAST-03已落实默认模式、显式工作区模式与发布字段,82项Node、162项三浏览器
受控SDK检查通过;初始化风险按本次用户决定限定接受。详见
[完成记录](changes/2026-09-14-PKG-VAST-03-compatibility.md)。实际IMA播放和公开声明
尚未完成。此前Ads的类型推导批准与本次VAST运行时决定分别记录;未来类型协调
继续以实际发布声明为基线。
+24 -8
View File
@@ -1,34 +1,50 @@
# VAST 测试维护 # VAST 测试维护
当前完成 PKG-VAST-01/02;生产重构由03/04继续。来源见 PKG-VAST-03现已落实用户确认的两种初始化模式;公开声明继续由04协调。来源见
[实际契约](baselines/vast-contract.md)、[VAST/SDK归档](baselines/vast-release.json) [实际契约](baselines/vast-contract.md)、[VAST/SDK归档](baselines/vast-release.json)
和 [核心5.1.7归档](baselines/vast-core.json)。工作区1.2.0不是npm发布版本。 和 [核心5.1.7归档](baselines/vast-core.json)。工作区1.2.0不是npm发布版本。
03检查点:新增`test/vast-lifecycle.test.js`的15项候选修复断言,冻结工作区全部失败、 早期03检查点:新增`test/vast-lifecycle.test.js`的15项候选修复断言,冻结工作区全部失败、
当前5个TS模块全部通过;现有测试共60项Node、117项浏览器通过。覆盖终止/回调失败、 当前5个TS模块全部通过;现有测试共60项Node、117项浏览器通过。覆盖终止/回调失败、
初始化回滚、活动广告重建、旧事件、配置getter和SDK同步重入;完整CI900项通过。 初始化回滚、活动广告重建、旧事件、配置getter和SDK同步重入;完整CI900项通过。
该文件已纳入`yarn test:vast`和`test:unit`。实际资源所有权见 该文件已纳入`yarn test:vast`和`test:unit`。实际资源所有权见
[包内架构](../packages/artplayer-plugin-vast/ARCHITECTURE.md); [包内架构](../packages/artplayer-plugin-vast/ARCHITECTURE.md);
[初始化冲突](vast-compatibility-decision.md)未决定,03仍doing。以下缺陷清单保留历史 [初始化冲突](vast-compatibility-decision.md)当时尚未决定。以下缺陷清单保留历史
来源,不代表候选实现仍包含这些已获可控修复证据的问题;真实IMA验收仍待05。 来源,不代表候选实现仍包含这些已获可控修复证据的问题;真实IMA验收仍待05。
## 文件与执行 ## 文件与执行
PKG-VAST-07补回工厂`.default`自身别名,不改变初始化决策。当前`yarn test:vast` PKG-VAST-07补回工厂`.default`自身别名,不改变初始化决策。当时`yarn test:vast`
65项通过;实际main/legacy加ESM各4项导出通过。117项三浏览器检查通过,其中候选 65项通过;实际main/legacy加ESM各4项导出通过。117项三浏览器检查通过,其中候选
异步注册场景使用`.default`,其他场景保留直接调用。浏览器仍替换SDK边界,实际 异步注册场景使用`.default`,其他场景保留直接调用。浏览器仍替换SDK边界,实际
打包SDK只做无广告的导出/已销毁宿主验证。详见 打包SDK只做无广告的导出/已销毁宿主验证。详见
[变更](changes/2026-09-14-PKG-VAST-07-default-alias.md)。 [变更](changes/2026-09-14-PKG-VAST-07-default-alias.md)。
2026-09-14用户确认默认保留npm语义、显式选择workspace惰性模式。当前Node82项
通过,三浏览器×三核心×四插件实现/模式162项通过(99候选,63历史)。主片解码
是真实浏览器行为,SDK仍受控。首次新增7项Node中5项在原候选失败,修复后新增
10项全过;首轮浏览器153过9失败来自默认模式仍被旧断言当作工作区显隐,原报告
保留。修正为各自历史前缀、监听数和显隐责任,并补充回调开始时资源/数据字段
检查;未删除清理/重建断言。最终源码格式整理后重跑,完整证据见
[03完成记录](changes/2026-09-14-PKG-VAST-03-compatibility.md)。
版本以实际附件为准:npm核心5.1.7/5.4.0、候选5.4.1;早期文档把published路径
写为5.4.1不准确,本轮报告已按来源及实际coreVersion纠正。
- `test/vast-exports.test.js`:源码构建或环境变量指定的实际main/legacy/ESM导出。 - `test/vast-exports.test.js`:源码构建或环境变量指定的实际main/legacy/ESM导出。
保持现有callable,不宣称复刻旧namespace对象的反射形状;新文件纳入test:vast/test:unit。 保持现有callable,不宣称复刻旧namespace对象的反射形状;新文件纳入test:vast/test:unit。
- `test/helpers/vast-sdk.js`:可在Node和浏览器使用的SDK记录器,控制加载、构造、 - `test/helpers/vast-sdk.js`:可在Node和浏览器使用的SDK记录器,控制加载、构造、
监听、请求、销毁失败和晚到事件;不实现IMA、广告请求或主片暂停恢复。 监听、请求、销毁失败和晚到事件;不实现IMA、广告请求或主片暂停恢复。
- `test/helpers/vast.js`:只在精确的`@glomex/vast-ima-player`导入边界替换SDK。 - `test/helpers/vast.js`:只在精确的`@glomex/vast-ima-player`导入边界替换SDK。
当前JS/TS入口可打包加载;两套历史源码必须来自哈希验证的npm归档/Git内容。 当前JS/TS入口可打包加载;两套历史源码必须来自哈希验证的npm归档/Git内容。
source与source-workspace是相同编译代码,后者显式传第二参数;published字段
表示行为模式,historical才表示历史代码。浏览器附件记录实际选项,避免混计。
Node宿主仅提供事件和DOM所有权意图,不模拟布局或视频解码。 Node宿主仅提供事件和DOM所有权意图,不模拟布局或视频解码。
- `test/vast.test.js`:共享契约、工作区新增功能、独立的历史缺陷观察。 - `test/vast.test.js`:共享契约、工作区新增功能、独立的历史缺陷观察。
修复时为当前候选增加正确行为断言,保留历史缺陷复现,不能把旧缺陷移入共享契约。 修复时为当前候选增加正确行为断言,保留历史缺陷复现,不能把旧缺陷移入共享契约。
- `test/vast-compatibility.test.js`:对照实际npm源码,验证提前初始化、原数据属性、
SDK默认设置、重复请求、回调拒绝/取消、构造重入、销毁重建及显式模式选择。
与原生命周期文件一起纳入test:vast/test:unit;后者保留工作区专属getter/四事件
故障断言,默认模式由兼容文件和共享/浏览器矩阵覆盖。
- `test/browser/vast.spec.js`:三真实浏览器×三核心,测试异步注册、加载失败、真实 - `test/browser/vast.spec.js`:三真实浏览器×三核心,测试异步注册、加载失败、真实
DOM显隐、显式清理重建、主片解码/切源和销毁后晚到初始化。SDK仍是同一受控记录器。 DOM显隐、显式清理重建、主片解码/切源和销毁后晚到初始化。SDK仍是同一受控记录器。
- `refactor/scripts/vast-core.test.mjs`:实际5.1.7 tarball、197成员和发布关联检查。 - `refactor/scripts/vast-core.test.mjs`:实际5.1.7 tarball、197成员和发布关联检查。
@@ -54,9 +70,9 @@ VAST-LIFE-01由源码观察提升为受控运行复现,问题如下:
9. 工作区SDK destroy抛错阻止DOM及引用清理。 9. 工作区SDK destroy抛错阻止DOM及引用清理。
10. 工作区事件注册失败后留下不完整SDK,下一次init错误地认为它已就绪。 10. 工作区事件注册失败后留下不完整SDK,下一次init错误地认为它已就绪。
03应建立明确的生命周期/SDK/DOM所有权,并区分可重建的显式广告destroy与终止性的 03已建立生命周期/SDK/DOM所有权,区分可重建的显式广告destroy与终止性的核心destroy。
核心destroy。04处理准确类型、SDK声明依赖和同步误声明;初始化冲突应以发布及 初始化差异按用户本次独立确认处置;04处理准确类型、SDK声明依赖和同步误声明。
工作区消费者证据明确处置,Ads的专属类型批准不能扩展到VAST。 运行时确认没有扩大Ads类型批准范围,也不自动豁免VAST声明兼容。
## 验证边界 ## 验证边界
@@ -65,4 +81,4 @@ VAST-LIFE-01由源码观察提升为受控运行复现,问题如下:
使用精确注入;Node固定Date.now用于同毫秒ID复现,未改变生产时钟。 使用精确注入;Node固定Date.now用于同毫秒ID复现,未改变生产时钟。
只有实际VAST外部脚本持续因VPN无法加载时,可按用户授权记录并跳过该网络验证; 只有实际VAST外部脚本持续因VPN无法加载时,可按用户授权记录并跳过该网络验证;
它不豁免类型、生命周期或其他包。到02为止未用该例外。03~06和SDK-07继续开放。 它不豁免类型、生命周期或其他包。本次未用该例外。04~06和SDK-07继续开放。
+34 -13
View File
@@ -8,11 +8,12 @@ const implementations = await vastImplementations()
async function setup(page, core, implementation, testInfo, deferLoad = false) { async function setup(page, core, implementation, testInfo, deferLoad = false) {
await page.goto(`/test/player.html?core=${core}`) await page.goto(`/test/player.html?core=${core}`)
await page.addScriptTag({ content: `window.createVastSdk = ${createVastSdk.toString()};` }) await page.addScriptTag({ content: `window.createVastSdk = ${createVastSdk.toString()};` })
await page.evaluate((deferLoad) => { await page.evaluate(({ deferLoad, options }) => {
window.vastSdk = window.createVastSdk({ deferLoad }) window.vastSdk = window.createVastSdk({ deferLoad })
window.vastOptions = options
window.google = { ima: window.vastSdk.ima } window.google = { ima: window.vastSdk.ima }
window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true }) window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true })
}, deferLoad) }, { deferLoad, options: implementation.options })
await page.addScriptTag({ content: `(() => { await page.addScriptTag({ content: `(() => {
const module = { exports: {} }; const module = { exports: {} };
const exports = module.exports; const exports = module.exports;
@@ -23,7 +24,7 @@ async function setup(page, core, implementation, testInfo, deferLoad = false) {
${implementation.code} ${implementation.code}
window.artplayerPluginVast = module.exports.default || module.exports; window.artplayerPluginVast = module.exports.default || module.exports;
})();` }) })();` })
await testInfo.attach('vast-inputs', { contentType: 'application/json', body: JSON.stringify({ core, implementation: implementation.name, sha256: hash(implementation.code), sdk: 'controlled wrapper recorder, no IMA execution' }) }) await testInfo.attach('vast-inputs', { contentType: 'application/json', body: JSON.stringify({ core, implementation: implementation.name, options: implementation.options || {}, sha256: hash(implementation.code), sdk: 'controlled wrapper recorder, no IMA execution' }) })
} }
test.afterEach(async ({ page }, testInfo) => { test.afterEach(async ({ page }, testInfo) => {
@@ -62,7 +63,7 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
await gate await gate
context.playUrl('/test/ad-tag.xml') context.playUrl('/test/ad-tag.xml')
context.playRes('<VAST/>') context.playRes('<VAST/>')
})).then(() => { }, window.vastOptions)).then(() => {
window.vastDone = true window.vastDone = true
}) })
}, !implementation.historical) }, !implementation.historical)
@@ -71,6 +72,19 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
await expect.poll(() => page.evaluate(() => window.vastCalls)).toBe(1) await expect.poll(() => page.evaluate(() => window.vastCalls)).toBe(1)
expect(await page.evaluate(() => window.vastDone)).toBe(false) expect(await page.evaluate(() => window.vastDone)).toBe(false)
expect(await page.evaluate(() => Object.hasOwn(window.art.plugins, 'artplayerPluginVast'))).toBe(false) expect(await page.evaluate(() => Object.hasOwn(window.art.plugins, 'artplayerPluginVast'))).toBe(false)
const beforeRequests = await page.evaluate(() => {
const context = window.vastContext
const player = window.vastSdk.state.players[0]
return {
players: window.vastSdk.state.players.length,
allocated: context.imaPlayer !== null,
publishedAliases: !!player && context.$container === player.args[2] && context.id === player.args[2].id,
writablePlayer: Object.getOwnPropertyDescriptor(context, 'imaPlayer').writable === true,
preloading: player?.args[3].enablePreloading === true,
background: player?.args[2].style.backgroundColor || '',
}
})
expect(beforeRequests).toEqual({ players: implementation.published ? 1 : 0, allocated: !!implementation.published, publishedAliases: !!implementation.published, writablePlayer: !!implementation.published, preloading: false, background: '' })
await page.evaluate(() => window.finishVastCallback()) await page.evaluate(() => window.finishVastCallback())
await page.evaluate(() => window.vastRegistration) await page.evaluate(() => window.vastRegistration)
const result = await page.evaluate(() => ({ const result = await page.evaluate(() => ({
@@ -88,7 +102,7 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
const result = await page.evaluate(async () => { const result = await page.evaluate(async () => {
const failure = new Error('controlled SDK failure') const failure = new Error('controlled SDK failure')
let calls = 0 let calls = 0
const registration = window.art.plugins.add(window.artplayerPluginVast(() => calls++)) const registration = window.art.plugins.add(window.artplayerPluginVast(() => calls++, window.vastOptions))
const observed = registration.catch(error => error === failure) const observed = registration.catch(error => error === failure)
window.vastSdk.rejectLoad(failure) window.vastSdk.rejectLoad(failure)
return { sameFailure: await observed, calls, players: window.vastSdk.state.players.length, registered: Object.hasOwn(window.art.plugins, 'artplayerPluginVast') } return { sameFailure: await observed, calls, players: window.vastSdk.state.players.length, registered: Object.hasOwn(window.art.plugins, 'artplayerPluginVast') }
@@ -109,7 +123,7 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
await page.evaluate(async () => { await page.evaluate(async () => {
await window.art.plugins.add(window.artplayerPluginVast((context) => { await window.art.plugins.add(window.artplayerPluginVast((context) => {
window.vastContext = context window.vastContext = context
})) }, window.vastOptions))
window.vastContext.playUrl('/first.xml') window.vastContext.playUrl('/first.xml')
}) })
const container = page.locator('[id^="art-vast-"]') const container = page.locator('[id^="art-vast-"]')
@@ -154,7 +168,7 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
window.vastRegistration = window.art.plugins.add(window.artplayerPluginVast((context) => { window.vastRegistration = window.art.plugins.add(window.artplayerPluginVast((context) => {
window.vastCalls++ window.vastCalls++
context.playUrl('/late.xml') context.playUrl('/late.xml')
})) }, window.vastOptions))
window.art.destroy() window.art.destroy()
window.vastSdk.resolveLoad() window.vastSdk.resolveLoad()
}) })
@@ -173,7 +187,7 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
await setup(page, core, implementation, testInfo, true) await setup(page, core, implementation, testInfo, true)
const state = await page.evaluate(async () => { const state = await page.evaluate(async () => {
let calls = 0 let calls = 0
const registration = window.art.plugins.add(window.artplayerPluginVast(() => calls++)) const registration = window.art.plugins.add(window.artplayerPluginVast(() => calls++, window.vastOptions))
window.art.destroy() window.art.destroy()
window.vastSdk.resolveLoad() window.vastSdk.resolveLoad()
await registration await registration
@@ -187,14 +201,21 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
await page.evaluate(async () => { await page.evaluate(async () => {
await window.art.plugins.add(window.artplayerPluginVast((context) => { await window.art.plugins.add(window.artplayerPluginVast((context) => {
window.vastContext = context window.vastContext = context
})) }, window.vastOptions))
window.vastContext.playUrl('/one.xml') window.vastContext.playUrl('/one.xml')
const first = window.vastContext.imaPlayer const first = window.vastContext.imaPlayer
window.lateVastEvents = [...first.listeners.values()].flatMap(values => [...values]) window.lateVastEvents = [...first.listeners.values()].flatMap(values => [...values])
first.emit('AdStarted') first.emit('AdStarted')
}) })
const container = page.locator('[id^="art-vast-"]') const container = page.locator(implementation.published ? '[id^="art-"]' : '[id^="art-vast-"]')
await expect(container).toBeVisible() await expect(container).toHaveCount(1)
expect(await page.evaluate(() => window.lateVastEvents.length)).toBe(implementation.published ? 0 : 4)
// The published wrapper delegates overlay visibility to the real SDK.
// This recorder does not implement that SDK behavior.
if (implementation.published)
await expect(container).toBeHidden()
else
await expect(container).toBeVisible()
await page.evaluate(() => { await page.evaluate(() => {
window.art.plugins.artplayerPluginVast.destroy() window.art.plugins.artplayerPluginVast.destroy()
window.vastContext.playUrl('/two.xml') window.vastContext.playUrl('/two.xml')
@@ -218,11 +239,11 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
const registration = window.art.plugins.add(window.artplayerPluginVast((context) => { const registration = window.art.plugins.add(window.artplayerPluginVast((context) => {
context.init() context.init()
return Promise.reject(failure) return Promise.reject(failure)
})) }, window.vastOptions))
return { sameError: await registration.catch(error => error === failure), destroys: window.vastSdk.state.players[0].destroyCalls, coreDestroyed: window.art.isDestroy } 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 }) expect(result).toEqual({ sameError: true, destroys: 1, coreDestroyed: false })
await expect(page.locator('[id^="art-vast-"]')).toHaveCount(0) await expect(page.locator(implementation.published ? '[id^="art-"]' : '[id^="art-vast-"]')).toHaveCount(0)
}) })
} }
} }
+2 -1
View File
@@ -12,7 +12,8 @@ export async function vastImplementations() {
const { outputFiles } = await build({ entryPoints: [resolveSource('packages/artplayer-plugin-vast/src/index')], bundle: true, write: false, format: 'cjs', platform: 'browser', external: ['@glomex/vast-ima-player'] }) const { outputFiles } = await build({ entryPoints: [resolveSource('packages/artplayer-plugin-vast/src/index')], bundle: true, write: false, format: 'cjs', platform: 'browser', external: ['@glomex/vast-ima-player'] })
const frozen = async source => (await transform(source, { loader: 'js', format: 'cjs', target: 'es2020' })).code const frozen = async source => (await transform(source, { loader: 'js', format: 'cjs', target: 'es2020' })).code
return [ return [
{ name: 'source', code: outputFiles[0].text }, { name: 'source', published: true, code: outputFiles[0].text },
{ name: 'source-workspace', options: { compatibility: 'workspace-1.2' }, code: outputFiles[0].text },
{ name: 'published-1.0.0-source', historical: true, published: true, code: await frozen(readMember(archive, 'package/src/index.js').toString()) }, { name: 'published-1.0.0-source', historical: true, published: true, code: await frozen(readMember(archive, 'package/src/index.js').toString()) },
{ name: 'frozen-workspace-1.2.0', historical: true, code: await frozen(contract.sources.get('packages/artplayer-plugin-vast/src/index.js')) }, { name: 'frozen-workspace-1.2.0', historical: true, code: await frozen(contract.sources.get('packages/artplayer-plugin-vast/src/index.js')) },
] ]
+181
View File
@@ -0,0 +1,181 @@
import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- Published and candidate callback contracts use controlled SDKs.
import test from 'node:test'
import { deferred, vastEnvironment, vastImplementations } from './helpers/vast.js'
const implementations = await vastImplementations()
const source = implementations.find(item => item.name === 'source')
for (const implementation of implementations.filter(item => item.name === 'source' || item.published)) {
test(`${implementation.name}: npm callback receives an allocated SDK and original data fields without changing SDK defaults`, async () => {
const env = vastEnvironment(implementation)
const host = env.host()
let context
await env.factory((value) => {
context = value
assert.equal(env.state.players.length, 1)
assert.equal(value.imaPlayer, env.state.players[0])
assert.equal(value.$container, host.parent.children[0])
assert.equal(value.id, value.$container.id)
assert.match(value.id, /^art-\d/)
value.imaPlayer.addEventListener('custom', () => {})
for (const key of ['imaPlayer', 'id', '$container']) {
const descriptor = Object.getOwnPropertyDescriptor(value, key)
assert.equal(descriptor.get, undefined)
assert.equal(descriptor.writable, true)
assert.equal(descriptor.enumerable, true)
assert.equal(descriptor.configurable, true)
}
})(host.art)
const player = env.state.players[0]
assert.equal(player.args[3].enablePreloading, undefined)
assert.equal(player.args[3].restoreCustomPlaybackStateOnAdBreakComplete, undefined)
assert.equal(context.$container.style.backgroundColor, undefined)
assert.equal(context.$container.style.pointerEvents, undefined)
context.playUrl('/first.xml')
player.emit('AdStarted')
context.playUrl('/second.xml')
assert.equal(player.requests.length, 2, 'The published wrapper forwards both explicit requests')
})
}
test('VAST explicit workspace mode retains lazy initialization and caller option identities', async () => {
const env = vastEnvironment(source)
const host = env.host()
let context
await env.factory((value) => {
context = value
assert.equal(value.imaPlayer, null)
assert.equal(value.container, null)
assert.equal(env.state.players.length, 0)
value.playerOptions.custom = 'before initialization'
}, { compatibility: 'workspace-1.2' })(host.art)
const player = context.init()
assert.equal(player.args[3], context.adsRenderingSettings)
assert.equal(player.args[4], context.playerOptions)
assert.equal(player.args[4].custom, 'before initialization')
assert.equal(player.args[3].enablePreloading, true)
assert.equal(player.args[3].restoreCustomPlaybackStateOnAdBreakComplete, true)
assert.match(context.container.id, /^art-vast-/)
context.playUrl('/first.xml')
player.emit('AdStarted')
context.playUrl('/suppressed.xml')
assert.equal(player.requests.length, 1)
host.art.destroy()
assert.equal(player.destroyCalls, 1)
assert.equal(context.imaPlayer, null)
})
test('VAST default initializes even without a callback and releases on core destruction', async () => {
const env = vastEnvironment(source)
const host = env.host()
await env.factory()(host.art)
assert.equal(env.state.players.length, 1)
host.art.destroy()
assert.equal(env.state.players[0].destroyCalls, 1)
assert.equal(host.parent.children.length, 0)
})
test('VAST eager constructor failure rejects registration before callback and removes owned DOM', async () => {
const failure = new Error('eager allocation failed')
const env = vastEnvironment(source, { constructError: failure })
const host = env.host()
let called = false
await assert.rejects(env.factory(() => {
called = true
})(host.art), error => error === failure)
assert.equal(called, false)
assert.equal(host.parent.children.length, 0)
assert.equal(host.listeners.get('destroy').size, 0)
})
test('VAST eager allocation reentered by core destruction never invokes callback', async () => {
const env = vastEnvironment(source)
const host = env.host()
env.state.onConstruct = () => host.art.destroy()
let called = false
await env.factory(() => {
called = true
})(host.art)
assert.equal(called, false)
assert.equal(env.state.players[0].destroyCalls, 1)
assert.equal(host.parent.children.length, 0)
})
test('VAST eager callback cancellation releases the live SDK without rewriting its published snapshots', async () => {
const env = vastEnvironment(source)
const host = env.host()
const gate = deferred()
const started = deferred()
let context
const pending = env.factory(async (value) => {
context = value
started.resolve()
await gate.promise
value.playUrl('/too-late.xml')
})(host.art)
await started.promise
const player = context.imaPlayer
assert(player)
const container = context.$container
host.art.destroy()
gate.resolve()
await pending
assert.equal(context.imaPlayer, player)
assert.equal(context.$container, container)
assert.equal(container.parentNode, null)
assert.equal(player.destroyCalls, 1)
assert.equal(player.requests.length, 0)
assert.equal(context.init(), null)
})
test('VAST default callback rejection releases allocation and keeps the original failure', async () => {
for (const destroyError of [null, new Error('secondary cleanup')]) {
const env = vastEnvironment(source, { destroyError })
const host = env.host()
const failure = new Error('callback failed')
let context
await assert.rejects(env.factory((value) => {
context = value
return Promise.reject(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)
}
})
test('VAST default explicit destroy and recreation update aliases without reusing disposed resources', async () => {
const env = vastEnvironment(source)
const host = env.host()
let context
const result = await env.factory((value) => {
context = value
})(host.art)
const first = context.imaPlayer
const firstId = context.id
result.destroy()
result.destroy()
assert.equal(context.imaPlayer, first)
assert.equal(first.destroyCalls, 1)
context.playUrl('/fresh.xml')
assert.notEqual(context.imaPlayer, first)
assert.notEqual(context.id, firstId)
assert.equal(context.$container, context.container)
assert.equal(context.imaPlayer.requests[0].adTagUrl, '/fresh.xml')
assert.equal(host.parent.children.length, 1)
host.art.destroy()
assert.deepEqual(env.state.players.map(player => player.destroyCalls), [1, 1])
})
test('VAST rejects an unknown mode before loading the SDK and snapshots a valid mode once', async () => {
const env = vastEnvironment(source)
assert.throws(() => env.factory(undefined, { compatibility: 'typo' }), /Unsupported VAST compatibility mode/)
assert.equal(env.state.loads, 0)
const options = { compatibility: 'workspace-1.2' }
const attach = env.factory(undefined, options)
options.compatibility = 'changed later'
await attach(env.host().art)
assert.equal(env.state.players.length, 0)
})
+16 -16
View File
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict'
import test from 'node:test' import test from 'node:test'
import { deferred, vastEnvironment, vastImplementations } from './helpers/vast.js' import { deferred, vastEnvironment, vastImplementations } from './helpers/vast.js'
const candidates = (await vastImplementations()).filter(item => !item.historical) const candidates = (await vastImplementations()).filter(item => !item.historical && !item.published)
for (const implementation of candidates) { for (const implementation of candidates) {
const { name } = implementation const { name } = implementation
@@ -14,7 +14,7 @@ for (const implementation of candidates) {
const result = await env.factory((value) => { const result = await env.factory((value) => {
context = value context = value
value.playUrl('/first.xml') value.playUrl('/first.xml')
})(host.art) }, implementation.options)(host.art)
const player = context.imaPlayer const player = context.imaPlayer
host.art.destroy() host.art.destroy()
result.destroy() result.destroy()
@@ -32,7 +32,7 @@ for (const implementation of candidates) {
const env = vastEnvironment(implementation, { deferLoad: true }) const env = vastEnvironment(implementation, { deferLoad: true })
const host = env.host() const host = env.host()
let calls = 0 let calls = 0
const attaching = env.factory(() => calls++)(host.art) const attaching = env.factory(() => calls++, implementation.options)(host.art)
assert.equal(host.listeners.get('destroy').size, 1) assert.equal(host.listeners.get('destroy').size, 1)
host.art.destroy() host.art.destroy()
env.resolveLoad() env.resolveLoad()
@@ -47,7 +47,7 @@ for (const implementation of candidates) {
const env = vastEnvironment(implementation) const env = vastEnvironment(implementation)
const host = env.host() const host = env.host()
host.art.destroy() host.art.destroy()
await env.factory(() => assert.fail('Unexpected callback'))(host.art) await env.factory(() => assert.fail('Unexpected callback'), implementation.options)(host.art)
assert.equal(env.state.loads, 0) assert.equal(env.state.loads, 0)
assert.equal(host.listeners.size, 0) assert.equal(host.listeners.size, 0)
}) })
@@ -55,7 +55,7 @@ for (const implementation of candidates) {
test(`${name}: SDK rejection unregisters the pending core listener`, async () => { test(`${name}: SDK rejection unregisters the pending core listener`, async () => {
const env = vastEnvironment(implementation, { deferLoad: true }) const env = vastEnvironment(implementation, { deferLoad: true })
const host = env.host() const host = env.host()
const attaching = env.factory()(host.art) const attaching = env.factory(undefined, implementation.options)(host.art)
const failure = new Error('load failure') const failure = new Error('load failure')
const rejected = assert.rejects(attaching, error => error === failure) const rejected = assert.rejects(attaching, error => error === failure)
env.rejectLoad(failure) env.rejectLoad(failure)
@@ -73,7 +73,7 @@ for (const implementation of candidates) {
context = value context = value
context.init() context.init()
throw failure throw failure
})(host.art), error => error === failure) }, implementation.options)(host.art), error => error === failure)
assert.equal(env.state.players[0].destroyCalls, 1) assert.equal(env.state.players[0].destroyCalls, 1)
assert.equal(host.parent.children.length, 0) assert.equal(host.parent.children.length, 0)
assert.equal(host.listeners.get('destroy').size, 0) assert.equal(host.listeners.get('destroy').size, 0)
@@ -94,7 +94,7 @@ for (const implementation of candidates) {
await finish.promise await finish.promise
context.playUrl('/late.xml') context.playUrl('/late.xml')
assert.equal(context.init(), null) assert.equal(context.init(), null)
})(host.art) }, implementation.options)(host.art)
await started.promise await started.promise
host.art.destroy() host.art.destroy()
finish.resolve() finish.resolve()
@@ -113,7 +113,7 @@ for (const implementation of candidates) {
let context let context
await env.factory((value) => { await env.factory((value) => {
context = value context = value
})(host.art) }, implementation.options)(host.art)
assert.throws(() => context.init(), error => error === failure) assert.throws(() => context.init(), error => error === failure)
assert.equal(host.parent.children.length, 0) assert.equal(host.parent.children.length, 0)
assert.equal(context.imaPlayer, null) assert.equal(context.imaPlayer, null)
@@ -133,7 +133,7 @@ for (const implementation of candidates) {
const attach = env.factory((context) => { const attach = env.factory((context) => {
context.init() context.init()
contexts.push(context) contexts.push(context)
}) }, implementation.options)
const one = env.host() const one = env.host()
const two = env.host() const two = env.host()
const [result] = await Promise.all([attach(one.art), attach(two.art)]) const [result] = await Promise.all([attach(one.art), attach(two.art)])
@@ -150,7 +150,7 @@ for (const implementation of candidates) {
let context let context
const result = await env.factory((value) => { const result = await env.factory((value) => {
context = value context = value
})(env.host().art) }, implementation.options)(env.host().art)
context.playUrl('/first.xml') context.playUrl('/first.xml')
context.imaPlayer.emit('AdStarted') context.imaPlayer.emit('AdStarted')
result.destroy() result.destroy()
@@ -164,7 +164,7 @@ for (const implementation of candidates) {
let context let context
const result = await env.factory((value) => { const result = await env.factory((value) => {
context = value context = value
})(env.host().art) }, implementation.options)(env.host().art)
const first = context.init() const first = context.init()
const late = [...first.listeners.values()].flatMap(listeners => [...listeners]) const late = [...first.listeners.values()].flatMap(listeners => [...listeners])
result.destroy() result.destroy()
@@ -184,7 +184,7 @@ for (const implementation of candidates) {
let context let context
const result = await env.factory((value) => { const result = await env.factory((value) => {
context = value context = value
})(host.art) }, implementation.options)(host.art)
const player = context.init() const player = context.init()
assert.throws(() => result.destroy(), error => error === failure) assert.throws(() => result.destroy(), error => error === failure)
assert.equal(context.container, null) assert.equal(context.container, null)
@@ -202,7 +202,7 @@ for (const implementation of candidates) {
let context let context
await env.factory((value) => { await env.factory((value) => {
context = value context = value
})(host.art) }, implementation.options)(host.art)
context.playUrl('/cancelled.xml', { get extra() { context.playUrl('/cancelled.xml', { get extra() {
host.art.destroy() host.art.destroy()
return 1 return 1
@@ -217,7 +217,7 @@ for (const implementation of candidates) {
let context let context
const result = await env.factory((value) => { const result = await env.factory((value) => {
context = value context = value
})(env.host().art) }, implementation.options)(env.host().art)
context.playUrl('/stale.xml', { get extra() { context.playUrl('/stale.xml', { get extra() {
result.destroy() result.destroy()
context.init() context.init()
@@ -237,7 +237,7 @@ for (const implementation of candidates) {
let context let context
await env.factory((value) => { await env.factory((value) => {
context = value context = value
})(host.art) }, implementation.options)(host.art)
env.state[hook] = () => host.art.destroy() env.state[hook] = () => host.art.destroy()
assert.equal(context.init(), null) assert.equal(context.init(), null)
assert.equal(env.state.players[0].destroyCalls, 1) assert.equal(env.state.players[0].destroyCalls, 1)
@@ -251,7 +251,7 @@ for (const implementation of candidates) {
let context let context
const result = await env.factory((value) => { const result = await env.factory((value) => {
context = value context = value
})(env.host().art) }, implementation.options)(env.host().art)
context.init() context.init()
env.state.onDestroy = () => { env.state.onDestroy = () => {
assert.equal(context.init(), null) assert.equal(context.init(), null)
+21 -21
View File
@@ -42,7 +42,7 @@ for (const implementation of implementations) {
calls++ calls++
started.resolve(context) started.resolve(context)
await finish.promise await finish.promise
})(host.art) }, implementation.options)(host.art)
attaching.then(() => { attaching.then(() => {
settled = true settled = true
}) })
@@ -63,7 +63,7 @@ for (const implementation of implementations) {
const env = vastEnvironment(implementation, { deferLoad: true }) const env = vastEnvironment(implementation, { deferLoad: true })
const host = env.host() const host = env.host()
let calls = 0 let calls = 0
const attaching = env.factory(() => calls++)(host.art) const attaching = env.factory(() => calls++, implementation.options)(host.art)
const failure = new Error('SDK unavailable') const failure = new Error('SDK unavailable')
const rejected = assert.rejects(attaching, error => error === failure) const rejected = assert.rejects(attaching, error => error === failure)
env.rejectLoad(failure) env.rejectLoad(failure)
@@ -75,7 +75,7 @@ for (const implementation of implementations) {
test(`${name}: absent callback remains accepted at runtime`, async () => { test(`${name}: absent callback remains accepted at runtime`, async () => {
const env = vastEnvironment(implementation) const env = vastEnvironment(implementation)
const result = await env.factory()(env.host().art) const result = await env.factory(undefined, implementation.options)(env.host().art)
assert.equal(result.name, 'artplayerPluginVast') assert.equal(result.name, 'artplayerPluginVast')
}) })
@@ -84,7 +84,7 @@ for (const implementation of implementations) {
let context let context
await env.factory((value) => { await env.factory((value) => {
context = value context = value
})(env.host().art) }, implementation.options)(env.host().art)
assert.equal(context.playUrl('https://example.test/ad.xml'), undefined) assert.equal(context.playUrl('https://example.test/ad.xml'), undefined)
assert.equal(context.playRes('<VAST version="3.0"/>'), undefined) assert.equal(context.playRes('<VAST version="3.0"/>'), undefined)
const [player] = env.state.players const [player] = env.state.players
@@ -97,7 +97,7 @@ for (const implementation of implementations) {
test(`${name}: one attach factory creates independent contexts, players and requests`, async () => { test(`${name}: one attach factory creates independent contexts, players and requests`, async () => {
const env = vastEnvironment(implementation) const env = vastEnvironment(implementation)
const contexts = [] const contexts = []
const attach = env.factory(context => contexts.push(context)) const attach = env.factory(context => contexts.push(context), implementation.options)
const one = env.host() const one = env.host()
const two = env.host() const two = env.host()
await Promise.all([attach(one.art), attach(two.art)]) await Promise.all([attach(one.art), attach(two.art)])
@@ -117,7 +117,7 @@ for (const implementation of implementations) {
let context let context
await env.factory((value) => { await env.factory((value) => {
context = value context = value
})(env.host().art) }, implementation.options)(env.host().art)
assert.throws(() => context.playUrl('/ad.xml'), error => error === failure) assert.throws(() => context.playUrl('/ad.xml'), error => error === failure)
env.state.playError = null env.state.playError = null
context.playUrl('/retry.xml') context.playUrl('/retry.xml')
@@ -132,7 +132,7 @@ for (const implementation of implementations) {
if (asynchronous) if (asynchronous)
return Promise.reject(failure) return Promise.reject(failure)
throw failure throw failure
})(env.host().art) }, implementation.options)(env.host().art)
await assert.rejects(attaching, error => error === failure) await assert.rejects(attaching, error => error === failure)
} }
}) })
@@ -146,7 +146,7 @@ for (const implementation of implementations) {
let context let context
await env.factory((value) => { await env.factory((value) => {
context = value context = value
})(host.art) }, implementation.options)(host.art)
assert.equal(context.imaPlayer, null) assert.equal(context.imaPlayer, null)
assert.equal(context.container, null) assert.equal(context.container, null)
context.playerOptions.custom = 'caller option' context.playerOptions.custom = 'caller option'
@@ -166,7 +166,7 @@ for (const implementation of implementations) {
let context let context
await env.factory((value) => { await env.factory((value) => {
context = value context = value
})(env.host().art) }, implementation.options)(env.host().art)
const config = Object.assign(Object.create({ inherited: 'legacy field' }), { adTagUrl: '/override.xml', adsResponse: 'override response' }) const config = Object.assign(Object.create({ inherited: 'legacy field' }), { adTagUrl: '/override.xml', adsResponse: 'override response' })
context.playUrl('/ignored.xml', config) context.playUrl('/ignored.xml', config)
config.adTagUrl = '/changed.xml' config.adTagUrl = '/changed.xml'
@@ -183,7 +183,7 @@ for (const implementation of implementations) {
let context let context
await env.factory((value) => { await env.factory((value) => {
context = value context = value
})(env.host().art) }, implementation.options)(env.host().art)
context.playUrl('/first.xml') context.playUrl('/first.xml')
const player = context.imaPlayer const player = context.imaPlayer
assert.equal(context.container.style.display, 'none') assert.equal(context.container.style.display, 'none')
@@ -211,7 +211,7 @@ for (const implementation of implementations) {
let context let context
const result = await env.factory((value) => { const result = await env.factory((value) => {
context = value context = value
})(host.art) }, implementation.options)(host.art)
const first = context.init() const first = context.init()
assert.equal(result.destroy(), undefined) assert.equal(result.destroy(), undefined)
result.destroy() result.destroy()
@@ -232,7 +232,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
test(`${name} historical: core destroy leaves the SDK allocated`, async () => { test(`${name} historical: core destroy leaves the SDK allocated`, async () => {
const env = vastEnvironment(implementation) const env = vastEnvironment(implementation)
const host = env.host() const host = env.host()
await env.factory(context => initialize(context))(host.art) await env.factory(context => initialize(context), implementation.options)(host.art)
const [player] = env.state.players const [player] = env.state.players
host.art.destroy() host.art.destroy()
assert.equal(player.destroyCalls, 0) assert.equal(player.destroyCalls, 0)
@@ -246,7 +246,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
const attaching = env.factory((context) => { const attaching = env.factory((context) => {
calls++ calls++
initialize(context) initialize(context)
})(host.art) }, implementation.options)(host.art)
host.art.destroy() host.art.destroy()
env.resolveLoad() env.resolveLoad()
await attaching await attaching
@@ -262,7 +262,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
await assert.rejects(env.factory((context) => { await assert.rejects(env.factory((context) => {
initialize(context) initialize(context)
return Promise.reject(failure) return Promise.reject(failure)
})(host.art), error => error === failure) }, implementation.options)(host.art), error => error === failure)
assert.equal(env.state.players[0].destroyCalls, 0) assert.equal(env.state.players[0].destroyCalls, 0)
assert.equal(host.parent.children.length, 1) assert.equal(host.parent.children.length, 1)
}) })
@@ -276,7 +276,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
started.resolve() started.resolve()
await finish.promise await finish.promise
context.playUrl('/late.xml') context.playUrl('/late.xml')
})(host.art) }, implementation.options)(host.art)
await started.promise await started.promise
host.art.destroy() host.art.destroy()
finish.resolve() finish.resolve()
@@ -289,7 +289,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
const failure = new Error('constructor failure') const failure = new Error('constructor failure')
const env = vastEnvironment(implementation, { constructError: failure }) const env = vastEnvironment(implementation, { constructError: failure })
const host = env.host() const host = env.host()
await assert.rejects(env.factory(context => initialize(context))(host.art), error => error === failure) await assert.rejects(env.factory(context => initialize(context), implementation.options)(host.art), error => error === failure)
assert.equal(host.parent.children.length, 1) assert.equal(host.parent.children.length, 1)
assert.equal(env.state.players.length, 0) assert.equal(env.state.players.length, 0)
}) })
@@ -298,7 +298,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
const env = vastEnvironment(implementation) const env = vastEnvironment(implementation)
const one = env.host() const one = env.host()
const two = env.host() const two = env.host()
const attach = env.factory(context => initialize(context)) const attach = env.factory(context => initialize(context), implementation.options)
await Promise.all([attach(one.art), attach(two.art)]) await Promise.all([attach(one.art), attach(two.art)])
assert.equal(one.parent.children[0].id, two.parent.children[0].id) assert.equal(one.parent.children[0].id, two.parent.children[0].id)
assert.notEqual(one.parent.children[0], two.parent.children[0]) assert.notEqual(one.parent.children[0], two.parent.children[0])
@@ -312,7 +312,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
let context let context
const result = await env.factory((value) => { const result = await env.factory((value) => {
context = value context = value
})(env.host().art) }, implementation.options)(env.host().art)
context.playUrl('/first.xml') context.playUrl('/first.xml')
context.imaPlayer.emit('AdStarted') context.imaPlayer.emit('AdStarted')
result.destroy() result.destroy()
@@ -326,7 +326,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
let context let context
const result = await env.factory((value) => { const result = await env.factory((value) => {
context = value context = value
})(env.host().art) }, implementation.options)(env.host().art)
const first = context.init() const first = context.init()
const lateStart = [...first.listeners.get('AdStarted')][0] const lateStart = [...first.listeners.get('AdStarted')][0]
result.destroy() result.destroy()
@@ -345,7 +345,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
let context let context
const result = await env.factory((value) => { const result = await env.factory((value) => {
context = value context = value
})(host.art) }, implementation.options)(host.art)
const player = context.init() const player = context.init()
assert.throws(() => result.destroy(), error => error === failure) assert.throws(() => result.destroy(), error => error === failure)
assert.equal(context.imaPlayer, player) assert.equal(context.imaPlayer, player)
@@ -359,7 +359,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
let context let context
await env.factory((value) => { await env.factory((value) => {
context = value context = value
})(host.art) }, implementation.options)(host.art)
assert.throws(() => context.init(), error => error === failure) assert.throws(() => context.init(), error => error === failure)
env.state.listenError = null env.state.listenError = null
const player = context.init() const player = context.init()