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
* 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
* 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;
}
let nextId = 0;
function createContainer(utils) {
function createContainer(utils, workspaceMode) {
const container = utils.createElement("div");
container.id = `art-vast-${Date.now()}-${++nextId}`;
container.id = `${workspaceMode ? "art-vast-" : "art-"}${Date.now()}-${++nextId}`;
utils.setStyles(container, {
position: "absolute",
inset: "0",
width: "100%",
height: "100%",
zIndex: "150",
backgroundColor: "black",
display: "none",
pointerEvents: "auto"
display: "none"
});
if (workspaceMode)
utils.setStyles(container, { backgroundColor: "black", pointerEvents: "auto" });
return container;
}
function createSession(art, ima, utils, closed) {
function createSession(art, ima, utils, closed, workspaceMode) {
const { $video, $player } = art.template;
const adsRenderingSettings = new ima.AdsRenderingSettings();
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
adsRenderingSettings.enablePreloading = true;
if (workspaceMode) {
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
adsRenderingSettings.enablePreloading = true;
}
const playerOptions = new E();
let current = null;
let initializing = false;
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();
function release(owner) {
if (current === owner)
@@ -519,7 +543,7 @@ function createSession(art, ima, utils, closed) {
initializing = true;
let owner;
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;
if (!live(owner)) {
release(owner);
@@ -536,12 +560,13 @@ function createSession(art, ima, utils, closed) {
return null;
}
const allocated = owner;
for (const [name, playing] of [
const events = workspaceMode ? [
["AdContentPauseRequested", true],
["AdContentResumeRequested", false],
["AdStarted", true],
["AdError", false]
]) {
] : [];
for (const [name, playing] of events) {
const callback = (event) => {
if (!live(allocated))
return;
@@ -557,6 +582,13 @@ function createSession(art, ima, utils, closed) {
return null;
}
}
if (!workspaceMode) {
Object.defineProperties(context, {
imaPlayer: { value: owner.player },
id: { value: owner.container.id },
$container: { value: owner.container }
});
}
return owner.player;
} catch (error) {
if (owner) {
@@ -572,7 +604,7 @@ function createSession(art, ima, utils, closed) {
}
}
function play(field, value, config) {
if (closed() || current?.playing)
if (closed() || workspaceMode && current?.playing)
return;
const player = init();
const owner = current;
@@ -586,24 +618,13 @@ function createSession(art, ima, utils, closed) {
if (current)
release(current);
}
const context = {
art,
playUrl: (url, config = {}) => play("adTagUrl", url, config),
playRes: (response, config = {}) => play("adsResponse", response, config),
init,
ima,
adsRenderingSettings,
playerOptions,
get imaPlayer() {
return current?.player || null;
},
get container() {
return current?.container || null;
}
};
return { context, destroy };
}
function artplayerPluginVast(callback) {
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) => {
let disposed = false;
let session;
@@ -629,11 +650,16 @@ function artplayerPluginVast(callback) {
if (closed())
return result;
const utils = art.constructor.utils;
session = createSession(art, ima, utils, closed);
session = createSession(art, ima, utils, closed, workspaceMode);
if (closed()) {
dispose();
return result;
}
if (!workspaceMode) {
session.context.init();
if (closed())
return result;
}
if (typeof callback === "function")
await callback(session.context);
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",
"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",
"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": "yarn test:node && yarn test:baseline",
"test:browser": "playwright test",
@@ -66,7 +66,7 @@
"test:dash-types-package": "node refactor/scripts/dash-package-types.mjs",
"test:ads-types-package": "node refactor/scripts/ads-package-types.mjs",
"test:ads-native-visibility": "node refactor/scripts/ads-native-visibility.mjs",
"test:vast": "node --test test/vast-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:impact": "node refactor/scripts/impact.mjs",
"check:contracts": "node refactor/scripts/contracts.mjs",
+43 -19
View File
@@ -1,19 +1,21 @@
# VAST implementation and maintenance
This is the PKG-VAST-03 implementation checkpoint. The published-versus-workspace
initialization conflict remains open; this checkpoint is not release acceptance.
The runtime follows the accepted published-versus-workspace initialization decision.
Public declaration reconciliation and real IMA validation remain separate release gates.
## Module map
- `src/index.ts` owns the core destroy subscription before SDK loading begins,
awaits SDK loading and the user callback, and preserves original rejection values.
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.
- `src/sdk.ts` is the only runtime Glomex import boundary. It also constructs IMA
requests, preserving own/inherited configuration fields and primary-field overrides.
- `src/session.ts` owns one current SDK/container pair, the four ad-event subscriptions,
active-ad request suppression, initialization rollback and explicit recreation.
- `src/view.ts` creates the existing overlay styles and distinct per-module container
- `src/session.ts` owns one current SDK/container pair, initialization rollback and
explicit recreation. Only workspace mode adds four ad-event subscriptions and
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.
- `src/types.ts` describes the implementation context and SDK boundary. Public legacy
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
The checkpoint retains the workspace callback order, lazy initialization, mutable
settings/options identities, default flags, void request methods, and asynchronous
outer attachment. The old `require(...).default(...)` call is restored, while the
current callable export remains callable. This does not recreate the old namespace
object's reflection shape. The `id`/`$container` aliases, eager
published initialization and contradictory public return types are not yet reconciled.
See [the explicit decision](../../refactor/vast-compatibility-decision.md).
Calling `artplayerPluginVast(callback)` preserves npm1.0.0 behavior: the SDK and
container exist before the callback, including when no callback was supplied.
`imaPlayer`, `id` and `$container` are writable/enumerable/configurable data fields.
SDK settings retain their own defaults. The wrapper forwards each explicit request
and leaves ad visibility to the SDK, without adding workspace event listeners.
The former timestamp-only overlay ID now has a monotonic suffix to avoid same-tick
collisions; the existing `art-vast-` prefix and CSS styles remain. IDs are internal
in the workspace context; published ID compatibility is tracked with the above decision.
Passing `{ compatibility: 'workspace-1.2' }` as the second argument retains unpublished
workspace behavior: no player/container until `init`, `playUrl` or `playRes`; callers
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
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
Run `yarn test:vast`, `yarn typecheck`, `yarn build artplayer-plugin-vast`, and
`yarn test:browser vast.spec.js` using the repository pinned toolchain. The node and
Run `yarn test:vast`, `node node_modules/typescript/bin/tsc -p
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
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.
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
import, using an already destroyed host to verify registration without loading ads.
+26
View File
@@ -2,6 +2,32 @@
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
Implementation modules, lifecycle ownership, test commands and pending compatibility
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginVast=e(),define(function(){return t.artplayerPluginVast})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginVast=e()}(this,function(){"use strict";var t=null,e=function(){t=null};function i(t,e,i){return e&&function(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}(t.prototype,e),t}function n(){return n=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(t[n]=i[n])}return t},n.apply(this,arguments)}function 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
* 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;
}
let nextId = 0;
function createContainer(utils) {
function createContainer(utils, workspaceMode) {
const container = utils.createElement("div");
container.id = `art-vast-${Date.now()}-${++nextId}`;
container.id = `${workspaceMode ? "art-vast-" : "art-"}${Date.now()}-${++nextId}`;
utils.setStyles(container, {
position: "absolute",
inset: "0",
width: "100%",
height: "100%",
zIndex: "150",
backgroundColor: "black",
display: "none",
pointerEvents: "auto"
display: "none"
});
if (workspaceMode)
utils.setStyles(container, { backgroundColor: "black", pointerEvents: "auto" });
return container;
}
function createSession(art, ima, utils, closed) {
function createSession(art, ima, utils, closed, workspaceMode) {
const { $video, $player } = art.template;
const adsRenderingSettings = new ima.AdsRenderingSettings();
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
adsRenderingSettings.enablePreloading = true;
if (workspaceMode) {
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
adsRenderingSettings.enablePreloading = true;
}
const playerOptions = new E();
let current = null;
let initializing = false;
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();
function release(owner) {
if (current === owner)
@@ -519,7 +543,7 @@ function createSession(art, ima, utils, closed) {
initializing = true;
let owner;
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;
if (!live(owner)) {
release(owner);
@@ -536,12 +560,13 @@ function createSession(art, ima, utils, closed) {
return null;
}
const allocated = owner;
for (const [name, playing] of [
const events = workspaceMode ? [
["AdContentPauseRequested", true],
["AdContentResumeRequested", false],
["AdStarted", true],
["AdError", false]
]) {
] : [];
for (const [name, playing] of events) {
const callback = (event) => {
if (!live(allocated))
return;
@@ -557,6 +582,13 @@ function createSession(art, ima, utils, closed) {
return null;
}
}
if (!workspaceMode) {
Object.defineProperties(context, {
imaPlayer: { value: owner.player },
id: { value: owner.container.id },
$container: { value: owner.container }
});
}
return owner.player;
} catch (error) {
if (owner) {
@@ -572,7 +604,7 @@ function createSession(art, ima, utils, closed) {
}
}
function play(field, value, config) {
if (closed() || current?.playing)
if (closed() || workspaceMode && current?.playing)
return;
const player = init();
const owner = current;
@@ -586,24 +618,13 @@ function createSession(art, ima, utils, closed) {
if (current)
release(current);
}
const context = {
art,
playUrl: (url, config = {}) => play("adTagUrl", url, config),
playRes: (response, config = {}) => play("adsResponse", response, config),
init,
ima,
adsRenderingSettings,
playerOptions,
get imaPlayer() {
return current?.player || null;
},
get container() {
return current?.container || null;
}
};
return { context, destroy };
}
function artplayerPluginVast(callback) {
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) => {
let disposed = false;
let session;
@@ -629,11 +650,16 @@ function artplayerPluginVast(callback) {
if (closed())
return result;
const utils = art.constructor.utils;
session = createSession(art, ima, utils, closed);
session = createSession(art, ima, utils, closed, workspaceMode);
if (closed()) {
dispose();
return result;
}
if (!workspaceMode) {
session.context.init();
if (closed())
return result;
}
if (typeof callback === "function")
await callback(session.context);
return result;
+12 -3
View File
@@ -1,9 +1,13 @@
import type Artplayer from 'artplayer'
import type { Callback, Result } from './types'
import type { Callback, CompatibilityOptions, Result } from './types'
import { loadImaSdk } from './sdk'
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> => {
let disposed = false
let session: ReturnType<typeof createSession> | undefined
@@ -28,11 +32,16 @@ function artplayerPluginVast(callback?: Callback) {
if (closed())
return result
const utils = (art.constructor as typeof Artplayer).utils
session = createSession(art, ima, utils, closed)
session = createSession(art, ima, utils, closed, workspaceMode)
if (closed()) {
dispose()
return result
}
if (!workspaceMode) {
session.context.init()
if (closed())
return result
}
if (typeof callback === 'function')
await callback(session.context)
return result
+45 -22
View File
@@ -12,16 +12,39 @@ interface Owner {
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 adsRenderingSettings = new ima.AdsRenderingSettings()
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true
adsRenderingSettings.enablePreloading = true
if (workspaceMode) {
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true
adsRenderingSettings.enablePreloading = true
}
const playerOptions = new PlayerOptions()
let current: Owner | null = null
let initializing = false
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()
function release(owner: Owner): void {
@@ -77,7 +100,7 @@ export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, clo
initializing = true
let owner: Owner | undefined
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
if (!live(owner)) {
release(owner)
@@ -94,12 +117,15 @@ export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, clo
return null
}
const allocated = owner
for (const [name, playing] of [
['AdContentPauseRequested', true],
['AdContentResumeRequested', false],
['AdStarted', true],
['AdError', false],
] as const) {
const events = workspaceMode
? [
['AdContentPauseRequested', true],
['AdContentResumeRequested', false],
['AdStarted', true],
['AdError', false],
] as const
: []
for (const [name, playing] of events) {
const callback: EventListener = (event) => {
if (!live(allocated))
return
@@ -115,6 +141,14 @@ export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, clo
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
}
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 {
if (closed() || current?.playing)
if (closed() || (workspaceMode && current?.playing))
return
const player = init()
const owner = current
@@ -149,16 +183,5 @@ export function createSession(art: Artplayer, ima: ImaSdk, utils: Utilities, clo
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 }
}
@@ -5,6 +5,10 @@ import type Artplayer from 'artplayer'
export type Utilities = Pick<typeof Artplayer.utils, 'createElement' | 'setStyles'>
export type RequestConfig = Record<string, unknown>
export interface CompatibilityOptions {
compatibility?: 'workspace-1.2'
}
export interface Context {
art: Artplayer
playUrl: (url: string, config?: RequestConfig) => void
@@ -14,6 +18,8 @@ export interface Context {
adsRenderingSettings: google.ima.AdsRenderingSettings
playerOptions: PlayerOptions
readonly imaPlayer: Player | null
readonly id: string | 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
export function createContainer(utils: Utilities): HTMLDivElement {
export function createContainer(utils: Utilities, workspaceMode: boolean): HTMLDivElement {
const container = utils.createElement('div')
container.id = `art-vast-${Date.now()}-${++nextId}`
container.id = `${workspaceMode ? 'art-vast-' : 'art-'}${Date.now()}-${++nextId}`
utils.setStyles(container, {
position: 'absolute',
inset: '0',
width: '100%',
height: '100%',
zIndex: '150',
backgroundColor: 'black',
display: 'none',
pointerEvents: 'auto',
})
if (workspaceMode)
utils.setStyles(container, { backgroundColor: 'black', pointerEvents: 'auto' })
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 个包及工作区/示例。
状态: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。
@@ -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-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-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 |
@@ -600,7 +600,7 @@
- PKG-ADS-05: [记录](changes/2026-09-12-PKG-ADS-05-browser.md) [记录](baselines/ads-ui-visibility-validation.json)
- PKG-VAST-01: [记录](baselines/vast-contract.md) [记录](baselines/vast-release.json) [记录](changes/2026-09-12-PKG-VAST-01-contract.md) [记录](baselines/vast-contract-validation.json)
- PKG-VAST-02: [记录](changes/2026-09-12-PKG-VAST-02-tests.md) [记录](baselines/vast-behavior-validation.json) [记录](baselines/vast-core.json) [记录](vast-validation.md)
- PKG-VAST-03: [记录](changes/2026-09-12-PKG-VAST-03-lifecycle.md) [记录](baselines/vast-lifecycle-validation.json) [记录](vast-compatibility-decision.md)
- 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-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)
+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 真实模型负载漏显与积压修复
连续播放采样补回主线程卡顿跨过的既有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-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 |
| 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-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 |
+15 -5
View File
@@ -3255,7 +3255,7 @@
"id": "VAST-CONTEXT-01",
"title": "Published VAST callback aliases and eager Player differ from the unpublished lazy workspace",
"confirmation": "reproduced",
"status": "open",
"status": "accepted-with-scope",
"owners": [
"PKG-VAST-02",
"PKG-VAST-03",
@@ -3266,10 +3266,18 @@
"refactor/baselines/vast-release.json",
"refactor/changes/2026-09-12-PKG-VAST-01-contract.md",
"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"
],
"compatibleResolution": "Restore published id/$container access and reconcile eager non-null Player with lazy workspace usage using explicit consumer evidence.",
"closureCriteria": "Published and workspace consumer fixtures cover all callback fields and initialization timing; any irreconcilable behavior has a concrete decision rather than implicit waiver."
"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."
},
{
"id": "VAST-TYPE-01",
@@ -3312,11 +3320,13 @@
"refactor/baselines/vast-core.json",
"refactor/vast-validation.md",
"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.",
"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",
+4 -2
View File
@@ -3086,14 +3086,16 @@
"CORE-10",
"CORE-13"
],
"status": "doing",
"status": "done",
"risk": "H",
"deliverable": "SDK loader、IMA adapter、广告状态和 DOM 清理分离;保持发布 callback 别名,明确初始化冲突;核心终止与显式 destroy 后重建区分",
"acceptance": "结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过",
"evidence": [
"changes/2026-09-12-PKG-VAST-03-lifecycle.md",
"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);已可重跑的
`vast-contract.test.mjs`及`test/vast.test.js`固定以下矛盾:
@@ -14,12 +18,12 @@
用户操作调用`init()`。在同一默认调用中无法同时保留非null和null,以及相反初始化时机。
添加别名或getter也无法同时满足两种值;按callback源码猜测用户版本不可取。
## 推荐的具体处置(尚未实现)
## 已接受并实施的具体处置
优先保持实际npm1.0.0默认行为:回调前初始化、提供id/$container,SDK默认设置保持
发布语义;保留workspace新增的init/container/settings等字段作为扩展。
确实依赖未发布1.2.0惰性行为的代码,拟通过新的可选第二参数明确选择该兼容模式:
确实依赖未发布1.2.0惰性行为的代码,通过新的可选第二参数明确选择该兼容模式:
```js
// npm旧用法保持原样
@@ -35,11 +39,27 @@ artplayerPluginVast((context) => {
```
这意味着依赖未发布1.2.0默认惰性的代码需要增加一个参数。普通npm1.0.0用户保持
原调用;这不是声称两种相反语义完全兼容。第二参数仅为待确认方案,当前生产代码
尚未新增它;类型误声明、CJS.default和分发路径仍各自单独验证。
原调用;这不是声称两种相反语义完全兼容。第二参数已获用户明确授权,生产代码
由PKG-VAST-03落实;公开类型由04协调,CJS.default和完整分发各自单独验证。
## 当前检查点
默认模式同时保持发布版的可写数据字段、SDK默认设置、透明容器和每次显式请求
转发;包装层不订阅工作区新增的四个广告事件,不代替SDK管理广告显隐。
显式工作区模式保留黑色容器、四事件显隐、活动广告期间请求抑制及实时getter。
两者共享已修复的资源所有权、初始化回滚和终止规则。
生命周期与资源修复不依赖该选择,已经推进;默认初始化暂保持工作区行为,不把
PKG-VAST-03或VAST-CONTEXT-01标完成。用户针对Ads的类型推导批准不适用于本决策。
需要用户确认是否按推荐方案优先实际发布默认行为;也可指定其他取舍。
默认模式销毁后保留三个发布数据字段的快照,容器已移除、SDK已释放;扩展的
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 测试维护
当前完成 PKG-VAST-01/02;生产重构由03/04继续。来源见
PKG-VAST-03现已落实用户确认的两种初始化模式;公开声明继续由04协调。来源见
[实际契约](baselines/vast-contract.md)、[VAST/SDK归档](baselines/vast-release.json)
和 [核心5.1.7归档](baselines/vast-core.json)。工作区1.2.0不是npm发布版本。
03检查点:新增`test/vast-lifecycle.test.js`的15项候选修复断言,冻结工作区全部失败、
早期03检查点:新增`test/vast-lifecycle.test.js`的15项候选修复断言,冻结工作区全部失败、
当前5个TS模块全部通过;现有测试共60项Node、117项浏览器通过。覆盖终止/回调失败、
初始化回滚、活动广告重建、旧事件、配置getter和SDK同步重入;完整CI900项通过。
该文件已纳入`yarn test:vast`和`test:unit`。实际资源所有权见
[包内架构](../packages/artplayer-plugin-vast/ARCHITECTURE.md);
[初始化冲突](vast-compatibility-decision.md)未决定,03仍doing。以下缺陷清单保留历史
[初始化冲突](vast-compatibility-decision.md)当时尚未决定。以下缺陷清单保留历史
来源,不代表候选实现仍包含这些已获可控修复证据的问题;真实IMA验收仍待05。
## 文件与执行
PKG-VAST-07补回工厂`.default`自身别名,不改变初始化决策。当前`yarn test:vast`
PKG-VAST-07补回工厂`.default`自身别名,不改变初始化决策。当时`yarn test:vast`
65项通过;实际main/legacy加ESM各4项导出通过。117项三浏览器检查通过,其中候选
异步注册场景使用`.default`,其他场景保留直接调用。浏览器仍替换SDK边界,实际
打包SDK只做无广告的导出/已销毁宿主验证。详见
[变更](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导出。
保持现有callable,不宣称复刻旧namespace对象的反射形状;新文件纳入test:vast/test:unit。
- `test/helpers/vast-sdk.js`:可在Node和浏览器使用的SDK记录器,控制加载、构造、
监听、请求、销毁失败和晚到事件;不实现IMA、广告请求或主片暂停恢复。
- `test/helpers/vast.js`:只在精确的`@glomex/vast-ima-player`导入边界替换SDK。
当前JS/TS入口可打包加载;两套历史源码必须来自哈希验证的npm归档/Git内容。
source与source-workspace是相同编译代码,后者显式传第二参数;published字段
表示行为模式,historical才表示历史代码。浏览器附件记录实际选项,避免混计。
Node宿主仅提供事件和DOM所有权意图,不模拟布局或视频解码。
- `test/vast.test.js`:共享契约、工作区新增功能、独立的历史缺陷观察。
修复时为当前候选增加正确行为断言,保留历史缺陷复现,不能把旧缺陷移入共享契约。
- `test/vast-compatibility.test.js`:对照实际npm源码,验证提前初始化、原数据属性、
SDK默认设置、重复请求、回调拒绝/取消、构造重入、销毁重建及显式模式选择。
与原生命周期文件一起纳入test:vast/test:unit;后者保留工作区专属getter/四事件
故障断言,默认模式由兼容文件和共享/浏览器矩阵覆盖。
- `test/browser/vast.spec.js`:三真实浏览器×三核心,测试异步注册、加载失败、真实
DOM显隐、显式清理重建、主片解码/切源和销毁后晚到初始化。SDK仍是同一受控记录器。
- `refactor/scripts/vast-core.test.mjs`:实际5.1.7 tarball、197成员和发布关联检查。
@@ -54,9 +70,9 @@ VAST-LIFE-01由源码观察提升为受控运行复现,问题如下:
9. 工作区SDK destroy抛错阻止DOM及引用清理。
10. 工作区事件注册失败后留下不完整SDK,下一次init错误地认为它已就绪。
03应建立明确的生命周期/SDK/DOM所有权,并区分可重建的显式广告destroy与终止性的
核心destroy。04处理准确类型、SDK声明依赖和同步误声明;初始化冲突应以发布及
工作区消费者证据明确处置,Ads的专属类型批准不能扩展到VAST。
03已建立生命周期/SDK/DOM所有权,区分可重建的显式广告destroy与终止性的核心destroy。
初始化差异按用户本次独立确认处置;04处理准确类型、SDK声明依赖和同步误声明。
运行时确认没有扩大Ads类型批准范围,也不自动豁免VAST声明兼容。
## 验证边界
@@ -65,4 +81,4 @@ VAST-LIFE-01由源码观察提升为受控运行复现,问题如下:
使用精确注入;Node固定Date.now用于同毫秒ID复现,未改变生产时钟。
只有实际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) {
await page.goto(`/test/player.html?core=${core}`)
await page.addScriptTag({ content: `window.createVastSdk = ${createVastSdk.toString()};` })
await page.evaluate((deferLoad) => {
await page.evaluate(({ deferLoad, options }) => {
window.vastSdk = window.createVastSdk({ deferLoad })
window.vastOptions = options
window.google = { ima: window.vastSdk.ima }
window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true })
}, deferLoad)
}, { deferLoad, options: implementation.options })
await page.addScriptTag({ content: `(() => {
const module = { exports: {} };
const exports = module.exports;
@@ -23,7 +24,7 @@ async function setup(page, core, implementation, testInfo, deferLoad = false) {
${implementation.code}
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) => {
@@ -62,7 +63,7 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
await gate
context.playUrl('/test/ad-tag.xml')
context.playRes('<VAST/>')
})).then(() => {
}, window.vastOptions)).then(() => {
window.vastDone = true
})
}, !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)
expect(await page.evaluate(() => window.vastDone)).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.vastRegistration)
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 failure = new Error('controlled SDK failure')
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)
window.vastSdk.rejectLoad(failure)
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 window.art.plugins.add(window.artplayerPluginVast((context) => {
window.vastContext = context
}))
}, window.vastOptions))
window.vastContext.playUrl('/first.xml')
})
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.vastCalls++
context.playUrl('/late.xml')
}))
}, window.vastOptions))
window.art.destroy()
window.vastSdk.resolveLoad()
})
@@ -173,7 +187,7 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
await setup(page, core, implementation, testInfo, true)
const state = await page.evaluate(async () => {
let calls = 0
const registration = window.art.plugins.add(window.artplayerPluginVast(() => calls++))
const registration = window.art.plugins.add(window.artplayerPluginVast(() => calls++, window.vastOptions))
window.art.destroy()
window.vastSdk.resolveLoad()
await registration
@@ -187,14 +201,21 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
await page.evaluate(async () => {
await window.art.plugins.add(window.artplayerPluginVast((context) => {
window.vastContext = context
}))
}, window.vastOptions))
window.vastContext.playUrl('/one.xml')
const first = window.vastContext.imaPlayer
window.lateVastEvents = [...first.listeners.values()].flatMap(values => [...values])
first.emit('AdStarted')
})
const container = page.locator('[id^="art-vast-"]')
await expect(container).toBeVisible()
const container = page.locator(implementation.published ? '[id^="art-"]' : '[id^="art-vast-"]')
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(() => {
window.art.plugins.artplayerPluginVast.destroy()
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) => {
context.init()
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 }
})
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 frozen = async source => (await transform(source, { loader: 'js', format: 'cjs', target: 'es2020' })).code
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: '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 { 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) {
const { name } = implementation
@@ -14,7 +14,7 @@ for (const implementation of candidates) {
const result = await env.factory((value) => {
context = value
value.playUrl('/first.xml')
})(host.art)
}, implementation.options)(host.art)
const player = context.imaPlayer
host.art.destroy()
result.destroy()
@@ -32,7 +32,7 @@ for (const implementation of candidates) {
const env = vastEnvironment(implementation, { deferLoad: true })
const host = env.host()
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)
host.art.destroy()
env.resolveLoad()
@@ -47,7 +47,7 @@ for (const implementation of candidates) {
const env = vastEnvironment(implementation)
const host = env.host()
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(host.listeners.size, 0)
})
@@ -55,7 +55,7 @@ for (const implementation of candidates) {
test(`${name}: SDK rejection unregisters the pending core listener`, async () => {
const env = vastEnvironment(implementation, { deferLoad: true })
const host = env.host()
const attaching = env.factory()(host.art)
const attaching = env.factory(undefined, implementation.options)(host.art)
const failure = new Error('load failure')
const rejected = assert.rejects(attaching, error => error === failure)
env.rejectLoad(failure)
@@ -73,7 +73,7 @@ for (const implementation of candidates) {
context = value
context.init()
throw failure
})(host.art), error => error === failure)
}, implementation.options)(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)
@@ -94,7 +94,7 @@ for (const implementation of candidates) {
await finish.promise
context.playUrl('/late.xml')
assert.equal(context.init(), null)
})(host.art)
}, implementation.options)(host.art)
await started.promise
host.art.destroy()
finish.resolve()
@@ -113,7 +113,7 @@ for (const implementation of candidates) {
let context
await env.factory((value) => {
context = value
})(host.art)
}, implementation.options)(host.art)
assert.throws(() => context.init(), error => error === failure)
assert.equal(host.parent.children.length, 0)
assert.equal(context.imaPlayer, null)
@@ -133,7 +133,7 @@ for (const implementation of candidates) {
const attach = env.factory((context) => {
context.init()
contexts.push(context)
})
}, implementation.options)
const one = env.host()
const two = env.host()
const [result] = await Promise.all([attach(one.art), attach(two.art)])
@@ -150,7 +150,7 @@ for (const implementation of candidates) {
let context
const result = await env.factory((value) => {
context = value
})(env.host().art)
}, implementation.options)(env.host().art)
context.playUrl('/first.xml')
context.imaPlayer.emit('AdStarted')
result.destroy()
@@ -164,7 +164,7 @@ for (const implementation of candidates) {
let context
const result = await env.factory((value) => {
context = value
})(env.host().art)
}, implementation.options)(env.host().art)
const first = context.init()
const late = [...first.listeners.values()].flatMap(listeners => [...listeners])
result.destroy()
@@ -184,7 +184,7 @@ for (const implementation of candidates) {
let context
const result = await env.factory((value) => {
context = value
})(host.art)
}, implementation.options)(host.art)
const player = context.init()
assert.throws(() => result.destroy(), error => error === failure)
assert.equal(context.container, null)
@@ -202,7 +202,7 @@ for (const implementation of candidates) {
let context
await env.factory((value) => {
context = value
})(host.art)
}, implementation.options)(host.art)
context.playUrl('/cancelled.xml', { get extra() {
host.art.destroy()
return 1
@@ -217,7 +217,7 @@ for (const implementation of candidates) {
let context
const result = await env.factory((value) => {
context = value
})(env.host().art)
}, implementation.options)(env.host().art)
context.playUrl('/stale.xml', { get extra() {
result.destroy()
context.init()
@@ -237,7 +237,7 @@ for (const implementation of candidates) {
let context
await env.factory((value) => {
context = value
})(host.art)
}, implementation.options)(host.art)
env.state[hook] = () => host.art.destroy()
assert.equal(context.init(), null)
assert.equal(env.state.players[0].destroyCalls, 1)
@@ -251,7 +251,7 @@ for (const implementation of candidates) {
let context
const result = await env.factory((value) => {
context = value
})(env.host().art)
}, implementation.options)(env.host().art)
context.init()
env.state.onDestroy = () => {
assert.equal(context.init(), null)
+21 -21
View File
@@ -42,7 +42,7 @@ for (const implementation of implementations) {
calls++
started.resolve(context)
await finish.promise
})(host.art)
}, implementation.options)(host.art)
attaching.then(() => {
settled = true
})
@@ -63,7 +63,7 @@ for (const implementation of implementations) {
const env = vastEnvironment(implementation, { deferLoad: true })
const host = env.host()
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 rejected = assert.rejects(attaching, error => error === failure)
env.rejectLoad(failure)
@@ -75,7 +75,7 @@ for (const implementation of implementations) {
test(`${name}: absent callback remains accepted at runtime`, async () => {
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')
})
@@ -84,7 +84,7 @@ for (const implementation of implementations) {
let context
await env.factory((value) => {
context = value
})(env.host().art)
}, implementation.options)(env.host().art)
assert.equal(context.playUrl('https://example.test/ad.xml'), undefined)
assert.equal(context.playRes('<VAST version="3.0"/>'), undefined)
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 () => {
const env = vastEnvironment(implementation)
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 two = env.host()
await Promise.all([attach(one.art), attach(two.art)])
@@ -117,7 +117,7 @@ for (const implementation of implementations) {
let context
await env.factory((value) => {
context = value
})(env.host().art)
}, implementation.options)(env.host().art)
assert.throws(() => context.playUrl('/ad.xml'), error => error === failure)
env.state.playError = null
context.playUrl('/retry.xml')
@@ -132,7 +132,7 @@ for (const implementation of implementations) {
if (asynchronous)
return Promise.reject(failure)
throw failure
})(env.host().art)
}, implementation.options)(env.host().art)
await assert.rejects(attaching, error => error === failure)
}
})
@@ -146,7 +146,7 @@ for (const implementation of implementations) {
let context
await env.factory((value) => {
context = value
})(host.art)
}, implementation.options)(host.art)
assert.equal(context.imaPlayer, null)
assert.equal(context.container, null)
context.playerOptions.custom = 'caller option'
@@ -166,7 +166,7 @@ for (const implementation of implementations) {
let context
await env.factory((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' })
context.playUrl('/ignored.xml', config)
config.adTagUrl = '/changed.xml'
@@ -183,7 +183,7 @@ for (const implementation of implementations) {
let context
await env.factory((value) => {
context = value
})(env.host().art)
}, implementation.options)(env.host().art)
context.playUrl('/first.xml')
const player = context.imaPlayer
assert.equal(context.container.style.display, 'none')
@@ -211,7 +211,7 @@ for (const implementation of implementations) {
let context
const result = await env.factory((value) => {
context = value
})(host.art)
}, implementation.options)(host.art)
const first = context.init()
assert.equal(result.destroy(), undefined)
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 () => {
const env = vastEnvironment(implementation)
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
host.art.destroy()
assert.equal(player.destroyCalls, 0)
@@ -246,7 +246,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
const attaching = env.factory((context) => {
calls++
initialize(context)
})(host.art)
}, implementation.options)(host.art)
host.art.destroy()
env.resolveLoad()
await attaching
@@ -262,7 +262,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
await assert.rejects(env.factory((context) => {
initialize(context)
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(host.parent.children.length, 1)
})
@@ -276,7 +276,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
started.resolve()
await finish.promise
context.playUrl('/late.xml')
})(host.art)
}, implementation.options)(host.art)
await started.promise
host.art.destroy()
finish.resolve()
@@ -289,7 +289,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
const failure = new Error('constructor failure')
const env = vastEnvironment(implementation, { constructError: failure })
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(env.state.players.length, 0)
})
@@ -298,7 +298,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
const env = vastEnvironment(implementation)
const one = 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)])
assert.equal(one.parent.children[0].id, two.parent.children[0].id)
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
const result = await env.factory((value) => {
context = value
})(env.host().art)
}, implementation.options)(env.host().art)
context.playUrl('/first.xml')
context.imaPlayer.emit('AdStarted')
result.destroy()
@@ -326,7 +326,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
let context
const result = await env.factory((value) => {
context = value
})(env.host().art)
}, implementation.options)(env.host().art)
const first = context.init()
const lateStart = [...first.listeners.get('AdStarted')][0]
result.destroy()
@@ -345,7 +345,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
let context
const result = await env.factory((value) => {
context = value
})(host.art)
}, implementation.options)(host.art)
const player = context.init()
assert.throws(() => result.destroy(), error => error === failure)
assert.equal(context.imaPlayer, player)
@@ -359,7 +359,7 @@ for (const implementation of implementations.filter(item => item.historical)) {
let context
await env.factory((value) => {
context = value
})(host.art)
}, implementation.options)(host.art)
assert.throws(() => context.init(), error => error === failure)
env.state.listenError = null
const player = context.init()