eb-player 2.0.20 → 2.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/ebplayer.bundle.js +586 -138
- package/dist/build/ebplayer.bundle.js.map +1 -1
- package/dist/build/types/src/core/command-handler.d.ts +50 -0
- package/dist/build/types/src/core/command-handler.d.ts.map +1 -0
- package/dist/build/types/src/core/config.d.ts +187 -0
- package/dist/build/types/src/core/config.d.ts.map +1 -0
- package/dist/build/types/src/core/engine-state-sync.d.ts +38 -0
- package/dist/build/types/src/core/engine-state-sync.d.ts.map +1 -0
- package/dist/build/types/src/core/event-bus.d.ts +91 -0
- package/dist/build/types/src/core/event-bus.d.ts.map +1 -0
- package/dist/build/types/src/core/fsm.d.ts +30 -0
- package/dist/build/types/src/core/fsm.d.ts.map +1 -0
- package/dist/build/types/src/core/i18n.d.ts +51 -0
- package/dist/build/types/src/core/i18n.d.ts.map +1 -0
- package/dist/build/types/src/core/index.d.ts +23 -0
- package/dist/build/types/src/core/index.d.ts.map +1 -0
- package/dist/build/types/src/core/lifecycle.d.ts +96 -0
- package/dist/build/types/src/core/lifecycle.d.ts.map +1 -0
- package/dist/build/types/src/core/player-state.d.ts +15 -0
- package/dist/build/types/src/core/player-state.d.ts.map +1 -0
- package/dist/build/types/src/core/types.d.ts +85 -0
- package/dist/build/types/src/core/types.d.ts.map +1 -0
- package/dist/build/types/src/eb-player-standalone.d.ts +23 -0
- package/dist/build/types/src/eb-player-standalone.d.ts.map +1 -0
- package/dist/build/types/src/eb-player.d.ts +56 -0
- package/dist/build/types/src/eb-player.d.ts.map +1 -0
- package/dist/build/types/src/engines/abr/dash.d.ts +56 -0
- package/dist/build/types/src/engines/abr/dash.d.ts.map +1 -0
- package/dist/build/types/src/engines/abr/hls.d.ts +105 -0
- package/dist/build/types/src/engines/abr/hls.d.ts.map +1 -0
- package/dist/build/types/src/engines/base-engine.d.ts +72 -0
- package/dist/build/types/src/engines/base-engine.d.ts.map +1 -0
- package/dist/build/types/src/engines/cdn-loader.d.ts +20 -0
- package/dist/build/types/src/engines/cdn-loader.d.ts.map +1 -0
- package/dist/build/types/src/engines/cdn-token-manager.d.ts +137 -0
- package/dist/build/types/src/engines/cdn-token-manager.d.ts.map +1 -0
- package/dist/build/types/src/engines/dash.d.ts +95 -0
- package/dist/build/types/src/engines/dash.d.ts.map +1 -0
- package/dist/build/types/src/engines/drm.d.ts +54 -0
- package/dist/build/types/src/engines/drm.d.ts.map +1 -0
- package/dist/build/types/src/engines/hls-discontinuity-patch.d.ts +43 -0
- package/dist/build/types/src/engines/hls-discontinuity-patch.d.ts.map +1 -0
- package/dist/build/types/src/engines/hls.d.ts +53 -0
- package/dist/build/types/src/engines/hls.d.ts.map +1 -0
- package/dist/build/types/src/engines/index.d.ts +26 -0
- package/dist/build/types/src/engines/index.d.ts.map +1 -0
- package/dist/build/types/src/engines/ios/hls.d.ts +20 -0
- package/dist/build/types/src/engines/ios/hls.d.ts.map +1 -0
- package/dist/build/types/src/engines/poster/hls.d.ts +35 -0
- package/dist/build/types/src/engines/poster/hls.d.ts.map +1 -0
- package/dist/build/types/src/engines/retry/dash.d.ts +51 -0
- package/dist/build/types/src/engines/retry/dash.d.ts.map +1 -0
- package/dist/build/types/src/engines/retry/hls.d.ts +47 -0
- package/dist/build/types/src/engines/retry/hls.d.ts.map +1 -0
- package/dist/build/types/src/engines/snapshot/dash.d.ts +55 -0
- package/dist/build/types/src/engines/snapshot/dash.d.ts.map +1 -0
- package/dist/build/types/src/engines/snapshot/hls.d.ts +118 -0
- package/dist/build/types/src/engines/snapshot/hls.d.ts.map +1 -0
- package/dist/build/types/src/engines/stall-watchdog.d.ts +41 -0
- package/dist/build/types/src/engines/stall-watchdog.d.ts.map +1 -0
- package/dist/build/types/src/integrations/ads-manager.d.ts +79 -0
- package/dist/build/types/src/integrations/ads-manager.d.ts.map +1 -0
- package/dist/build/types/src/integrations/chromecast-manager.d.ts +50 -0
- package/dist/build/types/src/integrations/chromecast-manager.d.ts.map +1 -0
- package/dist/build/types/src/integrations/epg-manager.d.ts +22 -0
- package/dist/build/types/src/integrations/epg-manager.d.ts.map +1 -0
- package/dist/build/types/src/integrations/index.d.ts +7 -0
- package/dist/build/types/src/integrations/index.d.ts.map +1 -0
- package/dist/build/types/src/integrations/intro-stream-manager.d.ts +62 -0
- package/dist/build/types/src/integrations/intro-stream-manager.d.ts.map +1 -0
- package/dist/build/types/src/integrations/p2p-manager.d.ts +42 -0
- package/dist/build/types/src/integrations/p2p-manager.d.ts.map +1 -0
- package/dist/build/types/src/integrations/playlist-manager.d.ts +21 -0
- package/dist/build/types/src/integrations/playlist-manager.d.ts.map +1 -0
- package/dist/build/types/src/skin/bars/bottom-bar.d.ts +16 -0
- package/dist/build/types/src/skin/bars/bottom-bar.d.ts.map +1 -0
- package/dist/build/types/src/skin/bars/middle-bar.d.ts +16 -0
- package/dist/build/types/src/skin/bars/middle-bar.d.ts.map +1 -0
- package/dist/build/types/src/skin/bars/top-bar.d.ts +15 -0
- package/dist/build/types/src/skin/bars/top-bar.d.ts.map +1 -0
- package/dist/build/types/src/skin/base-component.d.ts +69 -0
- package/dist/build/types/src/skin/base-component.d.ts.map +1 -0
- package/dist/build/types/src/skin/brand/forja-playlist-bar.d.ts +15 -0
- package/dist/build/types/src/skin/brand/forja-playlist-bar.d.ts.map +1 -0
- package/dist/build/types/src/skin/brand/snrt-radio-carousel.d.ts +16 -0
- package/dist/build/types/src/skin/brand/snrt-radio-carousel.d.ts.map +1 -0
- package/dist/build/types/src/skin/component-registry.d.ts +16 -0
- package/dist/build/types/src/skin/component-registry.d.ts.map +1 -0
- package/dist/build/types/src/skin/controllers/auto-hide.d.ts +27 -0
- package/dist/build/types/src/skin/controllers/auto-hide.d.ts.map +1 -0
- package/dist/build/types/src/skin/controllers/keyboard.d.ts +29 -0
- package/dist/build/types/src/skin/controllers/keyboard.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/cast-button.d.ts +15 -0
- package/dist/build/types/src/skin/controls/cast-button.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/channel-name.d.ts +16 -0
- package/dist/build/types/src/skin/controls/channel-name.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/forward-button.d.ts +15 -0
- package/dist/build/types/src/skin/controls/forward-button.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/fullscreen-button.d.ts +30 -0
- package/dist/build/types/src/skin/controls/fullscreen-button.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/info-button.d.ts +10 -0
- package/dist/build/types/src/skin/controls/info-button.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/live-sync-button.d.ts +20 -0
- package/dist/build/types/src/skin/controls/live-sync-button.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/pip-button.d.ts +21 -0
- package/dist/build/types/src/skin/controls/pip-button.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/play-pause-button.d.ts +14 -0
- package/dist/build/types/src/skin/controls/play-pause-button.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/rewind-button.d.ts +15 -0
- package/dist/build/types/src/skin/controls/rewind-button.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/seekbar.d.ts +48 -0
- package/dist/build/types/src/skin/controls/seekbar.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/settings-panel.d.ts +53 -0
- package/dist/build/types/src/skin/controls/settings-panel.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/share-button.d.ts +11 -0
- package/dist/build/types/src/skin/controls/share-button.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/time-display.d.ts +19 -0
- package/dist/build/types/src/skin/controls/time-display.d.ts.map +1 -0
- package/dist/build/types/src/skin/controls/volume-control.d.ts +21 -0
- package/dist/build/types/src/skin/controls/volume-control.d.ts.map +1 -0
- package/dist/build/types/src/skin/icons/icons-modern.d.ts +10 -0
- package/dist/build/types/src/skin/icons/icons-modern.d.ts.map +1 -0
- package/dist/build/types/src/skin/icons/icons.d.ts +10 -0
- package/dist/build/types/src/skin/icons/icons.d.ts.map +1 -0
- package/dist/build/types/src/skin/icons/sprite.d.ts +39 -0
- package/dist/build/types/src/skin/icons/sprite.d.ts.map +1 -0
- package/dist/build/types/src/skin/index.d.ts +24 -0
- package/dist/build/types/src/skin/index.d.ts.map +1 -0
- package/dist/build/types/src/skin/overlays/error-message.d.ts +14 -0
- package/dist/build/types/src/skin/overlays/error-message.d.ts.map +1 -0
- package/dist/build/types/src/skin/overlays/info-overlay.d.ts +15 -0
- package/dist/build/types/src/skin/overlays/info-overlay.d.ts.map +1 -0
- package/dist/build/types/src/skin/overlays/loading-spinner.d.ts +17 -0
- package/dist/build/types/src/skin/overlays/loading-spinner.d.ts.map +1 -0
- package/dist/build/types/src/skin/overlays/preroll-overlay.d.ts +39 -0
- package/dist/build/types/src/skin/overlays/preroll-overlay.d.ts.map +1 -0
- package/dist/build/types/src/skin/overlays/socials-overlay.d.ts +17 -0
- package/dist/build/types/src/skin/overlays/socials-overlay.d.ts.map +1 -0
- package/dist/build/types/src/skin/overlays/toast-notification.d.ts +18 -0
- package/dist/build/types/src/skin/overlays/toast-notification.d.ts.map +1 -0
- package/dist/build/types/src/skin/skin-root.d.ts +88 -0
- package/dist/build/types/src/skin/skin-root.d.ts.map +1 -0
- package/dist/build/types/src/utils/chapters.d.ts +16 -0
- package/dist/build/types/src/utils/chapters.d.ts.map +1 -0
- package/dist/build/types/src/utils/format-duration.d.ts +9 -0
- package/dist/build/types/src/utils/format-duration.d.ts.map +1 -0
- package/dist/build/types/src/utils/format-wall-clock.d.ts +6 -0
- package/dist/build/types/src/utils/format-wall-clock.d.ts.map +1 -0
- package/dist/build/types/src/utils/settings-helpers.d.ts +41 -0
- package/dist/build/types/src/utils/settings-helpers.d.ts.map +1 -0
- package/package.json +34 -33
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.EBPlayer = {}));
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
|
-
var __EB_PLAYER_VERSION__ = "2.0.
|
|
7
|
+
var __EB_PLAYER_VERSION__ = "2.0.21";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Finite State Machine for player playback state transitions.
|
|
@@ -883,7 +883,7 @@
|
|
|
883
883
|
* Copyright 2017 Google LLC
|
|
884
884
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
885
885
|
*/
|
|
886
|
-
const t=globalThis,i=t=>t,s=t.trustedTypes,e=s?s.createPolicy("lit-html",{createHTML:t=>t}):void 0,h="$lit$",o=`lit$${Math.random().toFixed(9).slice(2)}$`,n="?"+o,r=`<${n}>`,l=document,c=()=>l.createComment(""),a=t=>null===t||"object"!=typeof t&&"function"!=typeof t,u=Array.isArray,d=t=>u(t)||"function"==typeof t?.[Symbol.iterator],f="[ \t\n\f\r]",v=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_=/-->/g,m=/>/g,p=RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),g=/'/g,$=/"/g,y=/^(?:script|style|textarea|title)$/i,x=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),b=x(1),w=x(2),E=Symbol.for("lit-noChange"),A=Symbol.for("lit-nothing"),C=new WeakMap,P=l.createTreeWalker(l,129);function V(t,i){if(!u(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==e?e.createHTML(i):i}const N=(t,i)=>{const s=t.length-1,e=[];let n,l=2===i?"<svg>":3===i?"<math>":"",c=v;for(let i=0;i<s;i++){const s=t[i];let a,u,d=-1,f=0;for(;f<s.length&&(c.lastIndex=f,u=c.exec(s),null!==u);)f=c.lastIndex,c===v?"!--"===u[1]?c=_:void 0!==u[1]?c=m:void 0!==u[2]?(y.test(u[2])&&(n=RegExp("</"+u[2],"g")),c=p):void 0!==u[3]&&(c=p):c===p?">"===u[0]?(c=n??v,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?p:'"'===u[3]?$:g):c===$||c===g?c=p:c===_||c===m?c=v:(c=p,n=void 0);const x=c===p&&t[i+1].startsWith("/>")?" ":"";l+=c===v?s+r:d>=0?(e.push(a),s.slice(0,d)+h+s.slice(d)+o+x):s+o+(-2===d?i:x);}return [V(t,l+(t[s]||"<?>")+(2===i?"</svg>":3===i?"</math>":"")),e]};class S{constructor({strings:t,_$litType$:i},e){let r;this.parts=[];let l=0,a=0;const u=t.length-1,d=this.parts,[f,v]=N(t,i);if(this.el=S.createElement(f,e),P.currentNode=this.el.content,2===i||3===i){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes);}for(;null!==(r=P.nextNode())&&d.length<u;){if(1===r.nodeType){if(r.hasAttributes())for(const t of r.getAttributeNames())if(t.endsWith(h)){const i=v[a++],s=r.getAttribute(t).split(o),e=/([.?@])?(.*)/.exec(i);d.push({type:1,index:l,name:e[2],strings:s,ctor:"."===e[1]?I:"?"===e[1]?L:"@"===e[1]?z:H}),r.removeAttribute(t);}else t.startsWith(o)&&(d.push({type:6,index:l}),r.removeAttribute(t));if(y.test(r.tagName)){const t=r.textContent.split(o),i=t.length-1;if(i>0){r.textContent=s?s.emptyScript:"";for(let s=0;s<i;s++)r.append(t[s],c()),P.nextNode(),d.push({type:2,index:++l});r.append(t[i],c());}}}else if(8===r.nodeType)if(r.data===n)d.push({type:2,index:l});else {let t=-1;for(;-1!==(t=r.data.indexOf(o,t+1));)d.push({type:7,index:l}),t+=o.length-1;}l++;}}static createElement(t,i){const s=l.createElement("template");return s.innerHTML=t,s}}function M(t,i,s=t,e){if(i===E)return i;let h=void 0!==e?s._$Co?.[e]:s._$Cl;const o=a(i)?void 0:i._$litDirective$;return h?.constructor!==o&&(h?._$AO?.(false),void 0===o?h=void 0:(h=new o(t),h._$AT(t,s,e)),void 0!==e?(s._$Co??=[])[e]=h:s._$Cl=h),void 0!==h&&(i=M(t,h._$AS(t,i.values),h,e)),i}class R{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:i},parts:s}=this._$AD,e=(t?.creationScope??l).importNode(i,true);P.currentNode=e;let h=P.nextNode(),o=0,n=0,r=s[0];for(;void 0!==r;){if(o===r.index){let i;2===r.type?i=new k(h,h.nextSibling,this,t):1===r.type?i=new r.ctor(h,r.name,r.strings,this,t):6===r.type&&(i=new Z(h,this,t)),this._$AV.push(i),r=s[++n];}o!==r?.index&&(h=P.nextNode(),o++);}return P.currentNode=l,e}p(t){let i=0;for(const s of this._$AV) void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++;}}class k{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,i,s,e){this.type=2,this._$AH=A,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cv=e?.isConnected??true;}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===t?.nodeType&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=M(this,t,i),a(t)?t===A||null==t||""===t?(this._$AH!==A&&this._$AR(),this._$AH=A):t!==this._$AH&&t!==E&&this._(t):void 0!==t._$litType$?this.$(t):void 0!==t.nodeType?this.T(t):d(t)?this.k(t):this._(t);}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t));}_(t){this._$AH!==A&&a(this._$AH)?this._$AA.nextSibling.data=t:this.T(l.createTextNode(t)),this._$AH=t;}$(t){const{values:i,_$litType$:s}=t,e="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=S.createElement(V(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===e)this._$AH.p(i);else {const t=new R(e,this),s=t.u(this.options);t.p(i),this.T(s),this._$AH=t;}}_$AC(t){let i=C.get(t.strings);return void 0===i&&C.set(t.strings,i=new S(t)),i}k(t){u(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const h of t)e===i.length?i.push(s=new k(this.O(c()),this.O(c()),this,this.options)):s=i[e],s._$AI(h),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e);}_$AR(t=this._$AA.nextSibling,s){for(this._$AP?.(false,true,s);t!==this._$AB;){const s=i(t).nextSibling;i(t).remove(),t=s;}}setConnected(t){ void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t));}}class H{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,i,s,e,h){this.type=1,this._$AH=A,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=h,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=A;}_$AI(t,i=this,s,e){const h=this.strings;let o=false;if(void 0===h)t=M(this,t,i,0),o=!a(t)||t!==this._$AH&&t!==E,o&&(this._$AH=t);else {const e=t;let n,r;for(t=h[0],n=0;n<h.length-1;n++)r=M(this,e[s+n],i,n),r===E&&(r=this._$AH[n]),o||=!a(r)||r!==this._$AH[n],r===A?t=A:t!==A&&(t+=(r??"")+h[n+1]),this._$AH[n]=r;}o&&!e&&this.j(t);}j(t){t===A?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"");}}class I extends H{constructor(){super(...arguments),this.type=3;}j(t){this.element[this.name]=t===A?void 0:t;}}class L extends H{constructor(){super(...arguments),this.type=4;}j(t){this.element.toggleAttribute(this.name,!!t&&t!==A);}}class z extends H{constructor(t,i,s,e,h){super(t,i,s,e,h),this.type=5;}_$AI(t,i=this){if((t=M(this,t,i,0)??A)===E)return;const s=this._$AH,e=t===A&&s!==A||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,h=t!==A&&(s===A||e);e&&this.element.removeEventListener(this.name,this,s),h&&this.element.addEventListener(this.name,this,t),this._$AH=t;}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t);}}class Z{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s;}get _$AU(){return this._$AM._$AU}_$AI(t){M(this,t);}}const B=t.litHtmlPolyfillSupport;B?.(S,k),(t.litHtmlVersions??=[]).push("3.3.
|
|
886
|
+
const t=globalThis,i=t=>t,s=t.trustedTypes,e=s?s.createPolicy("lit-html",{createHTML:t=>t}):void 0,h="$lit$",o=`lit$${Math.random().toFixed(9).slice(2)}$`,n="?"+o,r=`<${n}>`,l=document,c=()=>l.createComment(""),a=t=>null===t||"object"!=typeof t&&"function"!=typeof t,u=Array.isArray,d=t=>u(t)||"function"==typeof t?.[Symbol.iterator],f="[ \t\n\f\r]",v=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_=/-->/g,m=/>/g,p=RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),g=/'/g,$=/"/g,y=/^(?:script|style|textarea|title)$/i,x=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),b=x(1),w=x(2),E=Symbol.for("lit-noChange"),A=Symbol.for("lit-nothing"),C=new WeakMap,P=l.createTreeWalker(l,129);function V(t,i){if(!u(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==e?e.createHTML(i):i}const N=(t,i)=>{const s=t.length-1,e=[];let n,l=2===i?"<svg>":3===i?"<math>":"",c=v;for(let i=0;i<s;i++){const s=t[i];let a,u,d=-1,f=0;for(;f<s.length&&(c.lastIndex=f,u=c.exec(s),null!==u);)f=c.lastIndex,c===v?"!--"===u[1]?c=_:void 0!==u[1]?c=m:void 0!==u[2]?(y.test(u[2])&&(n=RegExp("</"+u[2],"g")),c=p):void 0!==u[3]&&(c=p):c===p?">"===u[0]?(c=n??v,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?p:'"'===u[3]?$:g):c===$||c===g?c=p:c===_||c===m?c=v:(c=p,n=void 0);const x=c===p&&t[i+1].startsWith("/>")?" ":"";l+=c===v?s+r:d>=0?(e.push(a),s.slice(0,d)+h+s.slice(d)+o+x):s+o+(-2===d?i:x);}return [V(t,l+(t[s]||"<?>")+(2===i?"</svg>":3===i?"</math>":"")),e]};class S{constructor({strings:t,_$litType$:i},e){let r;this.parts=[];let l=0,a=0;const u=t.length-1,d=this.parts,[f,v]=N(t,i);if(this.el=S.createElement(f,e),P.currentNode=this.el.content,2===i||3===i){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes);}for(;null!==(r=P.nextNode())&&d.length<u;){if(1===r.nodeType){if(r.hasAttributes())for(const t of r.getAttributeNames())if(t.endsWith(h)){const i=v[a++],s=r.getAttribute(t).split(o),e=/([.?@])?(.*)/.exec(i);d.push({type:1,index:l,name:e[2],strings:s,ctor:"."===e[1]?I:"?"===e[1]?L:"@"===e[1]?z:H}),r.removeAttribute(t);}else t.startsWith(o)&&(d.push({type:6,index:l}),r.removeAttribute(t));if(y.test(r.tagName)){const t=r.textContent.split(o),i=t.length-1;if(i>0){r.textContent=s?s.emptyScript:"";for(let s=0;s<i;s++)r.append(t[s],c()),P.nextNode(),d.push({type:2,index:++l});r.append(t[i],c());}}}else if(8===r.nodeType)if(r.data===n)d.push({type:2,index:l});else {let t=-1;for(;-1!==(t=r.data.indexOf(o,t+1));)d.push({type:7,index:l}),t+=o.length-1;}l++;}}static createElement(t,i){const s=l.createElement("template");return s.innerHTML=t,s}}function M(t,i,s=t,e){if(i===E)return i;let h=void 0!==e?s._$Co?.[e]:s._$Cl;const o=a(i)?void 0:i._$litDirective$;return h?.constructor!==o&&(h?._$AO?.(false),void 0===o?h=void 0:(h=new o(t),h._$AT(t,s,e)),void 0!==e?(s._$Co??=[])[e]=h:s._$Cl=h),void 0!==h&&(i=M(t,h._$AS(t,i.values),h,e)),i}class R{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:i},parts:s}=this._$AD,e=(t?.creationScope??l).importNode(i,true);P.currentNode=e;let h=P.nextNode(),o=0,n=0,r=s[0];for(;void 0!==r;){if(o===r.index){let i;2===r.type?i=new k(h,h.nextSibling,this,t):1===r.type?i=new r.ctor(h,r.name,r.strings,this,t):6===r.type&&(i=new Z(h,this,t)),this._$AV.push(i),r=s[++n];}o!==r?.index&&(h=P.nextNode(),o++);}return P.currentNode=l,e}p(t){let i=0;for(const s of this._$AV) void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++;}}class k{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,i,s,e){this.type=2,this._$AH=A,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cv=e?.isConnected??true;}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===t?.nodeType&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=M(this,t,i),a(t)?t===A||null==t||""===t?(this._$AH!==A&&this._$AR(),this._$AH=A):t!==this._$AH&&t!==E&&this._(t):void 0!==t._$litType$?this.$(t):void 0!==t.nodeType?this.T(t):d(t)?this.k(t):this._(t);}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t));}_(t){this._$AH!==A&&a(this._$AH)?this._$AA.nextSibling.data=t:this.T(l.createTextNode(t)),this._$AH=t;}$(t){const{values:i,_$litType$:s}=t,e="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=S.createElement(V(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===e)this._$AH.p(i);else {const t=new R(e,this),s=t.u(this.options);t.p(i),this.T(s),this._$AH=t;}}_$AC(t){let i=C.get(t.strings);return void 0===i&&C.set(t.strings,i=new S(t)),i}k(t){u(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const h of t)e===i.length?i.push(s=new k(this.O(c()),this.O(c()),this,this.options)):s=i[e],s._$AI(h),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e);}_$AR(t=this._$AA.nextSibling,s){for(this._$AP?.(false,true,s);t!==this._$AB;){const s=i(t).nextSibling;i(t).remove(),t=s;}}setConnected(t){ void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t));}}class H{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,i,s,e,h){this.type=1,this._$AH=A,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=h,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=A;}_$AI(t,i=this,s,e){const h=this.strings;let o=false;if(void 0===h)t=M(this,t,i,0),o=!a(t)||t!==this._$AH&&t!==E,o&&(this._$AH=t);else {const e=t;let n,r;for(t=h[0],n=0;n<h.length-1;n++)r=M(this,e[s+n],i,n),r===E&&(r=this._$AH[n]),o||=!a(r)||r!==this._$AH[n],r===A?t=A:t!==A&&(t+=(r??"")+h[n+1]),this._$AH[n]=r;}o&&!e&&this.j(t);}j(t){t===A?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"");}}class I extends H{constructor(){super(...arguments),this.type=3;}j(t){this.element[this.name]=t===A?void 0:t;}}class L extends H{constructor(){super(...arguments),this.type=4;}j(t){this.element.toggleAttribute(this.name,!!t&&t!==A);}}class z extends H{constructor(t,i,s,e,h){super(t,i,s,e,h),this.type=5;}_$AI(t,i=this){if((t=M(this,t,i,0)??A)===E)return;const s=this._$AH,e=t===A&&s!==A||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,h=t!==A&&(s===A||e);e&&this.element.removeEventListener(this.name,this,s),h&&this.element.addEventListener(this.name,this,t),this._$AH=t;}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t);}}class Z{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s;}get _$AU(){return this._$AM._$AU}_$AI(t){M(this,t);}}const B=t.litHtmlPolyfillSupport;B?.(S,k),(t.litHtmlVersions??=[]).push("3.3.3");const D=(t,i,s)=>{const e=i;let h=e._$litPart$;if(void 0===h){const t=null;e._$litPart$=h=new k(i.insertBefore(c(),t),t,void 0,{});}return h._$AI(t),h};
|
|
887
887
|
|
|
888
888
|
/**
|
|
889
889
|
* Abstract base class for all skin components.
|
|
@@ -3881,15 +3881,37 @@
|
|
|
3881
3881
|
}
|
|
3882
3882
|
|
|
3883
3883
|
const IMA_SDK_URL = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js';
|
|
3884
|
+
/**
|
|
3885
|
+
* G-07-9: backstop for an intro that never signals completion (all three
|
|
3886
|
+
* IntroStreamManager completion triggers lost, e.g. a swallowed throw inside
|
|
3887
|
+
* its init() that lifecycle.ts only logs). Chosen to exceed any plausible
|
|
3888
|
+
* intro asset so it can never truncate a real intro — it converts an
|
|
3889
|
+
* infinite hang into a bounded one.
|
|
3890
|
+
*/
|
|
3891
|
+
const INTRO_DEFERRAL_TIMEOUT_MS = 120000;
|
|
3892
|
+
/**
|
|
3893
|
+
* G-07-11: bound from ad-leg entry to the AdsManager being handed back by
|
|
3894
|
+
* the loader. It spans the gesture wait, loadScript, displayContainer
|
|
3895
|
+
* initialization, requestAds and the ads response, and stops there —
|
|
3896
|
+
* creative fetch, buffering and render are IMA's and are not charged to it.
|
|
3897
|
+
* Must stay below MAIN_OPEN_FALLBACK_MS (eb-player.ts) so this more precise
|
|
3898
|
+
* mechanism resolves first.
|
|
3899
|
+
*/
|
|
3900
|
+
const AD_LEG_TIMEOUT_MS = 20000;
|
|
3884
3901
|
/**
|
|
3885
3902
|
* AdsManager manages the IMA SDK preroll ad lifecycle.
|
|
3886
3903
|
*
|
|
3887
|
-
* -
|
|
3888
|
-
*
|
|
3889
|
-
*
|
|
3904
|
+
* - Defers IMA SDK loading until an intro stream (if configured) completes,
|
|
3905
|
+
* bounded by INTRO_DEFERRAL_TIMEOUT_MS (G-07-9)
|
|
3906
|
+
* - Observes a user gesture on the player root starting before that
|
|
3907
|
+
* deferral, so a click during the intro satisfies the browser autoplay
|
|
3908
|
+
* gesture requirement without a second click after the intro (G-07-11)
|
|
3909
|
+
* - Guarantees the ad leg completes exactly once, bounded by
|
|
3910
|
+
* AD_LEG_TIMEOUT_MS until the loader hands back an AdsManager (G-07-11)
|
|
3890
3911
|
* - Sets state.adPlaying based on IMA lifecycle events
|
|
3891
|
-
* - Emits 'ad-complete' on TypedEventBus
|
|
3892
|
-
* - Cleans up adsManager.destroy() and displayContainer.destroy() on abort
|
|
3912
|
+
* - Emits 'ad-complete' on TypedEventBus exactly once per instance
|
|
3913
|
+
* - Cleans up adsManager.destroy() and displayContainer.destroy() on abort,
|
|
3914
|
+
* and on any completion path that never obtained an AdsManager
|
|
3893
3915
|
*/
|
|
3894
3916
|
class AdsManager {
|
|
3895
3917
|
constructor() {
|
|
@@ -3899,111 +3921,270 @@
|
|
|
3899
3921
|
/**
|
|
3900
3922
|
* Initialize the IMA SDK preroll ad system.
|
|
3901
3923
|
* - Skips if config.ad is undefined
|
|
3902
|
-
* -
|
|
3903
|
-
*
|
|
3924
|
+
* - Defers initialization while config.preroll is set, until
|
|
3925
|
+
* 'intro-stream-complete' fires or INTRO_DEFERRAL_TIMEOUT_MS elapses
|
|
3926
|
+
* (G-07-9)
|
|
3927
|
+
* - If !config.autoplay: observes a click anywhere in the player before
|
|
3928
|
+
* the deferral, and consumes it after (required by browser autoplay
|
|
3929
|
+
* policies to avoid muted/blocked display contexts)
|
|
3930
|
+
* - The ad leg always completes exactly once, bounded by
|
|
3931
|
+
* AD_LEG_TIMEOUT_MS until the loader hands back an AdsManager (G-07-11)
|
|
3904
3932
|
* - Loads IMA SDK, creates AdDisplayContainer, AdsLoader, and AdsRequest
|
|
3905
3933
|
* - Wires ad lifecycle events to PlayerState and TypedEventBus
|
|
3906
3934
|
*/
|
|
3907
3935
|
async init(config, state, bus, video, adsContainer, signal) {
|
|
3908
3936
|
if (!config.ad)
|
|
3909
3937
|
return;
|
|
3910
|
-
//
|
|
3911
|
-
//
|
|
3912
|
-
//
|
|
3913
|
-
//
|
|
3914
|
-
//
|
|
3915
|
-
|
|
3938
|
+
// G-07-11: single idempotent completion funnel. Every path that ends the
|
|
3939
|
+
// ad leg — the watchdog, a rejected/failed SDK load, a loader or manager
|
|
3940
|
+
// AD_ERROR, or a normal ALL_ADS_COMPLETED — routes through this closure,
|
|
3941
|
+
// guarded so 'ad-complete' can never fire twice and main can never open
|
|
3942
|
+
// twice.
|
|
3943
|
+
let introDeferralTimer = null;
|
|
3944
|
+
let adLegTimer = null;
|
|
3945
|
+
let adLegCompleted = false;
|
|
3946
|
+
const localController = new AbortController();
|
|
3947
|
+
signal.addEventListener('abort', () => localController.abort(), { once: true });
|
|
3948
|
+
const clearIntroDeferralTimer = () => {
|
|
3949
|
+
if (introDeferralTimer !== null) {
|
|
3950
|
+
clearTimeout(introDeferralTimer);
|
|
3951
|
+
introDeferralTimer = null;
|
|
3952
|
+
}
|
|
3953
|
+
};
|
|
3954
|
+
const clearAdLegTimer = () => {
|
|
3955
|
+
if (adLegTimer !== null) {
|
|
3956
|
+
clearTimeout(adLegTimer);
|
|
3957
|
+
adLegTimer = null;
|
|
3958
|
+
}
|
|
3959
|
+
};
|
|
3960
|
+
const completeAdLeg = () => {
|
|
3961
|
+
if (adLegCompleted)
|
|
3962
|
+
return;
|
|
3963
|
+
adLegCompleted = true;
|
|
3964
|
+
clearIntroDeferralTimer();
|
|
3965
|
+
clearAdLegTimer();
|
|
3966
|
+
// T-07-23: release only when nothing can be rendering. imaAdsManager
|
|
3967
|
+
// is assigned in exactly one place (below, after getAdsManager()
|
|
3968
|
+
// returns), so null here means the loader never handed one back —
|
|
3969
|
+
// watchdog expiry, an IMA-setup rejection, or a loader-level AD_ERROR
|
|
3970
|
+
// before any manager. ALL_ADS_COMPLETED and a manager-level AD_ERROR
|
|
3971
|
+
// keep dispose-time teardown instead (see releaseImaResources below).
|
|
3972
|
+
if (this.imaAdsManager === null) {
|
|
3973
|
+
this.releaseImaResources();
|
|
3974
|
+
}
|
|
3975
|
+
state.adPlaying = false;
|
|
3976
|
+
bus.emit('ad-complete');
|
|
3977
|
+
localController.abort();
|
|
3978
|
+
};
|
|
3979
|
+
// Abort hygiene (G-07-11): unconditional and idempotent, registered
|
|
3980
|
+
// once so it fires no matter which stage init() was in when the signal
|
|
3981
|
+
// aborted. releaseImaResources() is itself null-guarded, so this is a
|
|
3982
|
+
// no-op on any path that already released or never acquired IMA
|
|
3983
|
+
// resources.
|
|
3984
|
+
signal.addEventListener('abort', () => {
|
|
3985
|
+
clearIntroDeferralTimer();
|
|
3986
|
+
clearAdLegTimer();
|
|
3987
|
+
this.releaseImaResources();
|
|
3988
|
+
}, { once: true });
|
|
3989
|
+
// G-07-11: gesture observation starts here, before the intro-deferral
|
|
3990
|
+
// await below, so a click that lands during the intro is already
|
|
3991
|
+
// captured by the time the post-intro await consumes it (the
|
|
3992
|
+
// sequential-await interaction: fixing G-07-9 alone would otherwise
|
|
3993
|
+
// install this listener only after the intro, behind the very click
|
|
3994
|
+
// that started it).
|
|
3995
|
+
const gesturePromise = config.autoplay ? null : this.waitForUserGesture(video, signal);
|
|
3996
|
+
// G-07-9: the decision is derived from configuration, not from the
|
|
3997
|
+
// PlayerState intro-playing flag. An async function body runs
|
|
3998
|
+
// synchronously up to its first await, so reading that flag here would
|
|
3999
|
+
// always observe its createPlayerState() default — IntroStreamManager
|
|
4000
|
+
// has not run yet at this point in initIntegrations(). config.preroll is
|
|
4001
|
+
// immutable and readable regardless of integration init order.
|
|
4002
|
+
if (config.preroll) {
|
|
3916
4003
|
await new Promise((resolve) => {
|
|
3917
|
-
|
|
3918
|
-
|
|
4004
|
+
const settle = () => {
|
|
4005
|
+
clearIntroDeferralTimer();
|
|
4006
|
+
resolve();
|
|
4007
|
+
};
|
|
4008
|
+
bus.on('intro-stream-complete', settle, { signal });
|
|
4009
|
+
signal.addEventListener('abort', settle, { once: true });
|
|
4010
|
+
introDeferralTimer = setTimeout(() => {
|
|
4011
|
+
console.warn('AdsManager: intro-stream-complete never arrived within the bound — falling through to the ad leg (G-07-9)');
|
|
4012
|
+
settle();
|
|
4013
|
+
}, INTRO_DEFERRAL_TIMEOUT_MS);
|
|
3919
4014
|
});
|
|
3920
4015
|
if (signal.aborted)
|
|
3921
4016
|
return;
|
|
3922
4017
|
}
|
|
3923
|
-
//
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
4018
|
+
// G-07-11 / D-08-03: the ad-leg watchdog bounds every step from here —
|
|
4019
|
+
// the gesture wait, loadScript, the ad request and the ads response —
|
|
4020
|
+
// until the loader hands back an AdsManager, which disarms it below.
|
|
4021
|
+
// Armed unconditionally whenever config.ad is set (init() already
|
|
4022
|
+
// returned above at the `!config.ad` early return, so this line is only
|
|
4023
|
+
// ever reached with config.ad truthy): as of D-02, main is gated on
|
|
4024
|
+
// 'ad-complete' in BOTH the intro+ad and ad-only configurations, so an
|
|
4025
|
+
// unbounded leg in either shape is a permanent stuck loader — the exact
|
|
4026
|
+
// failure VAST-05 forbids. Invariant: whenever main waits on the ad leg,
|
|
4027
|
+
// the ad leg is bounded. Do not restore an `if (config.preroll)` guard
|
|
4028
|
+
// here — that would leave the ad-only configuration unbounded again.
|
|
4029
|
+
adLegTimer = setTimeout(() => {
|
|
4030
|
+
console.warn('AdsManager: ad leg produced no AdsManager within the bound — falling through to main (G-07-11)');
|
|
4031
|
+
completeAdLeg();
|
|
4032
|
+
}, AD_LEG_TIMEOUT_MS);
|
|
4033
|
+
// Consume the gesture observed above. A click that arrives after the ad
|
|
4034
|
+
// leg has already completed must not resurrect it.
|
|
4035
|
+
if (gesturePromise !== null) {
|
|
4036
|
+
await gesturePromise;
|
|
4037
|
+
}
|
|
4038
|
+
if (signal.aborted || adLegCompleted)
|
|
4039
|
+
return;
|
|
4040
|
+
try {
|
|
4041
|
+
const google = await loadScript(IMA_SDK_URL, 'google');
|
|
4042
|
+
// G-07-11: the release above must be terminal. A watchdog that
|
|
4043
|
+
// expired while loadScript was still in flight must not let a
|
|
4044
|
+
// resolving SDK rebuild the display container it just destroyed.
|
|
4045
|
+
if (adLegCompleted)
|
|
3927
4046
|
return;
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
// completion so the orchestration always advances.
|
|
3941
|
-
adsLoader.addEventListener(ima.AdErrorEvent.Type.AD_ERROR, (errorEvent) => {
|
|
3942
|
-
console.warn('AdsManager: AD_ERROR (loader) — falling through to main', errorEvent);
|
|
3943
|
-
state.adPlaying = false;
|
|
3944
|
-
bus.emit('ad-complete');
|
|
3945
|
-
});
|
|
3946
|
-
adsLoader.addEventListener(ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, (loadedEvent) => {
|
|
3947
|
-
const event = loadedEvent;
|
|
3948
|
-
const adsManager = event.getAdsManager(video);
|
|
3949
|
-
this.imaAdsManager = adsManager;
|
|
3950
|
-
// getBoundingClientRect is always defined on HTMLElement; the prior
|
|
3951
|
-
// truthy check on the method reference was dead code. The real edge
|
|
3952
|
-
// case is a zero-sized element (display:none, before layout) which
|
|
3953
|
-
// would make IMA's ads container collapse — fall back to a 16:9
|
|
3954
|
-
// baseline in that case.
|
|
3955
|
-
const rect = video.getBoundingClientRect();
|
|
3956
|
-
const width = rect.width > 0 ? rect.width : 640;
|
|
3957
|
-
const height = rect.height > 0 ? rect.height : 360;
|
|
3958
|
-
// Wire event handlers for ad lifecycle
|
|
3959
|
-
adsManager.addEventListener(ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, () => {
|
|
3960
|
-
state.adPlaying = true;
|
|
3961
|
-
// Pause the content stream while the ad plays. IMA's integration
|
|
3962
|
-
// contract requires the publisher to pause its own content video on
|
|
3963
|
-
// CONTENT_PAUSE_REQUESTED; the symmetric CONTENT_RESUME_REQUESTED
|
|
3964
|
-
// handler below resumes it with video.play(). Without this the main
|
|
3965
|
-
// HLS/DASH stream keeps playing (audio + video) underneath the ad.
|
|
3966
|
-
video.pause();
|
|
3967
|
-
});
|
|
3968
|
-
adsManager.addEventListener(ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, () => {
|
|
3969
|
-
state.adPlaying = false;
|
|
3970
|
-
void video.play();
|
|
4047
|
+
const { ima } = google;
|
|
4048
|
+
const displayContainer = new ima.AdDisplayContainer(adsContainer, video);
|
|
4049
|
+
this.imaDisplayContainer = displayContainer;
|
|
4050
|
+
displayContainer.initialize();
|
|
4051
|
+
const adsLoader = new ima.AdsLoader(displayContainer);
|
|
4052
|
+
// AD_ERROR fall-through (CR-02): if the VAST tag fails to load (network
|
|
4053
|
+
// error, ad-blocker, malformed XML, autoplay rejection), IMA fires
|
|
4054
|
+
// AD_ERROR and never ALL_ADS_COMPLETED. Route it through the shared
|
|
4055
|
+
// completion funnel so the orchestration always advances.
|
|
4056
|
+
adsLoader.addEventListener(ima.AdErrorEvent.Type.AD_ERROR, (errorEvent) => {
|
|
4057
|
+
console.warn('AdsManager: AD_ERROR (loader) — falling through to main', errorEvent);
|
|
4058
|
+
completeAdLeg();
|
|
3971
4059
|
});
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
4060
|
+
adsLoader.addEventListener(ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, (loadedEvent) => {
|
|
4061
|
+
// T-07-22: a late ads response — the request is issued before the
|
|
4062
|
+
// bound can expire, so this is reachable — must not start an ad
|
|
4063
|
+
// over an already-opened main stream, and must not emit a second
|
|
4064
|
+
// 'ad-complete'.
|
|
4065
|
+
if (adLegCompleted)
|
|
4066
|
+
return;
|
|
4067
|
+
const event = loadedEvent;
|
|
4068
|
+
const adsManager = event.getAdsManager(video);
|
|
4069
|
+
this.imaAdsManager = adsManager;
|
|
4070
|
+
// G-07-11: holding the manager is the earliest proof the ad leg is
|
|
4071
|
+
// alive and progressing. Creative fetch, buffering and render are
|
|
4072
|
+
// IMA's to own from here — disarm the watchdog now rather than at
|
|
4073
|
+
// a later, more visible signal, so none of that time is charged
|
|
4074
|
+
// to the bound.
|
|
4075
|
+
clearAdLegTimer();
|
|
4076
|
+
// getBoundingClientRect is always defined on HTMLElement; the prior
|
|
4077
|
+
// truthy check on the method reference was dead code. The real edge
|
|
4078
|
+
// case is a zero-sized element (display:none, before layout) which
|
|
4079
|
+
// would make IMA's ads container collapse — fall back to a 16:9
|
|
4080
|
+
// baseline in that case.
|
|
4081
|
+
const rect = video.getBoundingClientRect();
|
|
4082
|
+
const width = rect.width > 0 ? rect.width : 640;
|
|
4083
|
+
const height = rect.height > 0 ? rect.height : 360;
|
|
4084
|
+
// D-08-08: CONTENT_PAUSE_REQUESTED, CONTENT_RESUME_REQUESTED and
|
|
4085
|
+
// ALL_ADS_COMPLETED emitted nothing at all before this change, so
|
|
4086
|
+
// none of the three could be individually evidenced in the 08-04
|
|
4087
|
+
// live session (G-08-02) — only their side effects were visible.
|
|
4088
|
+
// These three log lines are observability-only and inherit
|
|
4089
|
+
// D-08-06's constraints below verbatim (never complete the leg,
|
|
4090
|
+
// never write state, never interpolate metadata). Each literal is
|
|
4091
|
+
// captured verbatim into a committed evidence file by 08-07.
|
|
4092
|
+
// Wire event handlers for ad lifecycle
|
|
4093
|
+
adsManager.addEventListener(ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, () => {
|
|
4094
|
+
console.log('AdsManager: CONTENT_PAUSE_REQUESTED — content paused (D-08-08)');
|
|
4095
|
+
state.adPlaying = true;
|
|
4096
|
+
// Pause the content stream while the ad plays. IMA's integration
|
|
4097
|
+
// contract requires the publisher to pause its own content video on
|
|
4098
|
+
// CONTENT_PAUSE_REQUESTED; the symmetric CONTENT_RESUME_REQUESTED
|
|
4099
|
+
// handler below resumes it with video.play(). Without this the main
|
|
4100
|
+
// HLS/DASH stream keeps playing (audio + video) underneath the ad.
|
|
4101
|
+
video.pause();
|
|
4102
|
+
});
|
|
4103
|
+
adsManager.addEventListener(ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, () => {
|
|
4104
|
+
console.log('AdsManager: CONTENT_RESUME_REQUESTED — content resumed (D-08-08)');
|
|
4105
|
+
state.adPlaying = false;
|
|
4106
|
+
void video.play();
|
|
4107
|
+
// D-08-07: CONTENT_RESUME_REQUESTED is IMA's canonical "ad break
|
|
4108
|
+
// over, resume content" signal and is the ONLY completion event
|
|
4109
|
+
// dispatched for a plain VAST response — ALL_ADS_COMPLETED is
|
|
4110
|
+
// reliable only for VMAP/ad-rules responses. Routing it through
|
|
4111
|
+
// completeAdLeg() is what makes VAST-04's "exactly once per
|
|
4112
|
+
// instance" hold on both response shapes; completeAdLeg() is
|
|
4113
|
+
// idempotent via adLegCompleted, so a later ALL_ADS_COMPLETED is
|
|
4114
|
+
// a no-op rather than a second 'ad-complete'.
|
|
4115
|
+
//
|
|
4116
|
+
// Verified live (08-04): without this, a completed Google sample
|
|
4117
|
+
// preroll never emitted 'ad-complete', and main opened only when
|
|
4118
|
+
// the 30s MAIN_OPEN_FALLBACK_MS backstop expired — a 20-29s gap
|
|
4119
|
+
// on every ad-only playback.
|
|
4120
|
+
completeAdLeg();
|
|
4121
|
+
});
|
|
4122
|
+
adsManager.addEventListener(ima.AdEvent.Type.ALL_ADS_COMPLETED, () => {
|
|
4123
|
+
console.log('AdsManager: ALL_ADS_COMPLETED — ad leg complete (D-08-08)');
|
|
4124
|
+
completeAdLeg();
|
|
4125
|
+
});
|
|
4126
|
+
// Same AD_ERROR fall-through on the manager — covers errors that
|
|
4127
|
+
// surface only after the ad starts playing (decode error, mid-roll
|
|
4128
|
+
// load failure, etc.).
|
|
4129
|
+
adsManager.addEventListener(ima.AdErrorEvent.Type.AD_ERROR, (errorEvent) => {
|
|
4130
|
+
console.warn('AdsManager: AD_ERROR (manager) — falling through to main', errorEvent);
|
|
4131
|
+
completeAdLeg();
|
|
4132
|
+
});
|
|
4133
|
+
// D-08-06: IMA renders and owns its own Skip button (D-05 rules out
|
|
4134
|
+
// a custom skip UI), so this log is the only signal that proves the
|
|
4135
|
+
// control became available — observability only. It must never
|
|
4136
|
+
// call completeAdLeg() (this fires mid-ad, while a creative is
|
|
4137
|
+
// still rendering) and must never write to state (nothing consumes
|
|
4138
|
+
// an adSkippable field today, and speculative state violates
|
|
4139
|
+
// Simplicity First). No ad/creative metadata is interpolated: this
|
|
4140
|
+
// literal is captured verbatim into a committed evidence file by
|
|
4141
|
+
// 08-04 (T-08-05).
|
|
4142
|
+
adsManager.addEventListener(ima.AdEvent.Type.SKIPPABLE_STATE_CHANGED, () => {
|
|
4143
|
+
console.log('AdsManager: ad became skippable (D-08-06)');
|
|
4144
|
+
});
|
|
4145
|
+
adsManager.init(width, height);
|
|
4146
|
+
adsManager.start();
|
|
3983
4147
|
});
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
}
|
|
3997
|
-
if (this.imaDisplayContainer !== null) {
|
|
3998
|
-
this.imaDisplayContainer.destroy();
|
|
3999
|
-
this.imaDisplayContainer = null;
|
|
4000
|
-
}
|
|
4001
|
-
}, { once: true });
|
|
4148
|
+
// Load the ad tag
|
|
4149
|
+
const adsRequest = new ima.AdsRequest();
|
|
4150
|
+
adsRequest.adTagUrl = config.ad;
|
|
4151
|
+
adsLoader.requestAds(adsRequest);
|
|
4152
|
+
}
|
|
4153
|
+
catch (error) {
|
|
4154
|
+
// G-07-11: an ad-blocker blocking the CDN, a network error, or
|
|
4155
|
+
// loadScript's own 10s timeout must fall through to main instead of
|
|
4156
|
+
// escaping to the logging-only .catch() in lifecycle.ts.
|
|
4157
|
+
console.warn('AdsManager: failed to load or initialize the IMA SDK — falling through to main (G-07-11)', error);
|
|
4158
|
+
completeAdLeg();
|
|
4159
|
+
}
|
|
4002
4160
|
}
|
|
4003
4161
|
/**
|
|
4004
|
-
*
|
|
4162
|
+
* Shared teardown for imaAdsManager and imaDisplayContainer, each behind
|
|
4163
|
+
* its own null check and nulled immediately after — idempotent by
|
|
4164
|
+
* construction. Called unconditionally on abort, and from the completion
|
|
4165
|
+
* funnel only when this.imaAdsManager is still null (T-07-23).
|
|
4166
|
+
*/
|
|
4167
|
+
releaseImaResources() {
|
|
4168
|
+
if (this.imaAdsManager !== null) {
|
|
4169
|
+
this.imaAdsManager.destroy();
|
|
4170
|
+
this.imaAdsManager = null;
|
|
4171
|
+
}
|
|
4172
|
+
if (this.imaDisplayContainer !== null) {
|
|
4173
|
+
this.imaDisplayContainer.destroy();
|
|
4174
|
+
this.imaDisplayContainer = null;
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
/**
|
|
4178
|
+
* Wait for a click event anywhere inside the player root.
|
|
4005
4179
|
* Used to satisfy browser autoplay policies when autoplay is disabled.
|
|
4006
4180
|
*
|
|
4181
|
+
* G-07-11: the target widens from the video element to
|
|
4182
|
+
* video.closest('.eb-player') (falling back to video itself for bare
|
|
4183
|
+
* integrations), registered in the capture phase so a descendant that
|
|
4184
|
+
* halts bubbling cannot hide the click from it. Observes only; never
|
|
4185
|
+
* mutates the event, so the skip button and click-through link keep
|
|
4186
|
+
* working exactly as they do today.
|
|
4187
|
+
*
|
|
4007
4188
|
* WR-05: single-shot settle flag guards against the click-vs-abort race —
|
|
4008
4189
|
* Promise resolution is already idempotent but settled tightens the
|
|
4009
4190
|
* contract by ensuring listener cleanup happens exactly once for either
|
|
@@ -4011,16 +4192,17 @@
|
|
|
4011
4192
|
*/
|
|
4012
4193
|
waitForUserGesture(video, signal) {
|
|
4013
4194
|
return new Promise((resolve) => {
|
|
4195
|
+
const target = video.closest('.eb-player') ?? video;
|
|
4014
4196
|
let settled = false;
|
|
4015
4197
|
const settle = () => {
|
|
4016
4198
|
if (settled)
|
|
4017
4199
|
return;
|
|
4018
4200
|
settled = true;
|
|
4019
|
-
|
|
4201
|
+
target.removeEventListener('click', onClick, { capture: true });
|
|
4020
4202
|
resolve();
|
|
4021
4203
|
};
|
|
4022
4204
|
const onClick = () => settle();
|
|
4023
|
-
|
|
4205
|
+
target.addEventListener('click', onClick, { capture: true });
|
|
4024
4206
|
// Clean up if aborted before click. The abort listener auto-removes
|
|
4025
4207
|
// via `{ once: true }`; the click listener is removed inside settle().
|
|
4026
4208
|
signal.addEventListener('abort', settle, { once: true });
|
|
@@ -4028,6 +4210,21 @@
|
|
|
4028
4210
|
}
|
|
4029
4211
|
}
|
|
4030
4212
|
|
|
4213
|
+
/**
|
|
4214
|
+
* G-07-12: absolute backstop for an intro pass where every other completion
|
|
4215
|
+
* trigger is lost — no video `ended`, no `preroll-skip`, no `error-fatal`,
|
|
4216
|
+
* and no `state.error` write. 90s cannot truncate a real brand intro
|
|
4217
|
+
* (single-digit to ~30s assets) but converts an infinite hang into a bounded
|
|
4218
|
+
* one.
|
|
4219
|
+
*
|
|
4220
|
+
* D-10: this value MUST stay strictly below AdsManager's
|
|
4221
|
+
* INTRO_DEFERRAL_TIMEOUT_MS (120_000, src/integrations/ads-manager.ts) so
|
|
4222
|
+
* the intro pass always completes before the ad leg falls through — intro →
|
|
4223
|
+
* ad → main precedence must hold in the failure case too, not only the
|
|
4224
|
+
* happy path. Test 14 in test/integrations/intro-stream-manager.test.ts
|
|
4225
|
+
* asserts this ordering so a future edit to either constant fails loudly.
|
|
4226
|
+
*/
|
|
4227
|
+
const INTRO_MAX_DURATION_MS = 90000;
|
|
4031
4228
|
/**
|
|
4032
4229
|
* IntroStreamManager owns the intro-stream lifecycle.
|
|
4033
4230
|
*
|
|
@@ -4036,12 +4233,24 @@
|
|
|
4036
4233
|
* - the video element's native `ended` event
|
|
4037
4234
|
* - bus `preroll-skip` (from the PrerollOverlay skip button)
|
|
4038
4235
|
* - bus `error-fatal` (D-04 silent fall-through — intro must never block main)
|
|
4236
|
+
* - PlayerState `error` becoming non-null (G-07-12: the one signal shared
|
|
4237
|
+
* by two silent failure modes that never reach the bus —
|
|
4238
|
+
* StallWatchdog reload-budget exhaustion,
|
|
4239
|
+
* src/core/command-handler.ts:159-164, and a synchronous engine
|
|
4240
|
+
* init() throw caught in onAttach, src/engines/dash.ts:108-120 and
|
|
4241
|
+
* src/engines/hls.ts:194-207)
|
|
4242
|
+
* - the on-demand bus event that lets eb-player.ts's openMainStream()
|
|
4243
|
+
* guarantee the intro pass is finished before main opens (G-07-13
|
|
4244
|
+
* receiving half — see the bus.on() registration below for its name)
|
|
4245
|
+
* - an absolute INTRO_MAX_DURATION_MS backstop timer, armed in case no
|
|
4246
|
+
* other trigger ever fires
|
|
4039
4247
|
* - Any one of those triggers calls a closure `complete()` exactly once
|
|
4040
4248
|
* (idempotent via a `completed` boolean guard), which flips
|
|
4041
4249
|
* state.introStreamPlaying = false and emits `intro-stream-complete`.
|
|
4042
4250
|
* - On abort, the video `ended` listener is removed explicitly via
|
|
4043
|
-
* removeEventListener
|
|
4044
|
-
*
|
|
4251
|
+
* removeEventListener and the backstop timer is cleared. Bus and state
|
|
4252
|
+
* listeners auto-clean via the `{ signal }` option (see
|
|
4253
|
+
* src/core/event-bus.ts on() and src/core/player-state.ts on()).
|
|
4045
4254
|
*
|
|
4046
4255
|
* Scope: this manager listens and emits — it does NOT itself open the intro
|
|
4047
4256
|
* stream. The engine open() call is owned by eb-player.ts (single source of
|
|
@@ -4052,8 +4261,10 @@
|
|
|
4052
4261
|
/**
|
|
4053
4262
|
* Initialize intro-stream lifecycle tracking.
|
|
4054
4263
|
* - Skips entirely if config.preroll is falsy (no state flip, no listeners).
|
|
4055
|
-
* - Otherwise flips state.introStreamPlaying = true and registers the
|
|
4056
|
-
* completion triggers (video `ended`, bus `preroll-skip`, bus
|
|
4264
|
+
* - Otherwise flips state.introStreamPlaying = true and registers the
|
|
4265
|
+
* completion triggers (video `ended`, bus `preroll-skip`, bus
|
|
4266
|
+
* `error-fatal`, PlayerState `error`, the on-demand force-completion bus
|
|
4267
|
+
* event, and the INTRO_MAX_DURATION_MS backstop timer).
|
|
4057
4268
|
*/
|
|
4058
4269
|
async init(config, state, bus, video, signal) {
|
|
4059
4270
|
if (!config.preroll)
|
|
@@ -4068,14 +4279,18 @@
|
|
|
4068
4279
|
// Mirror outer aborts onto the local controller so dispose() still
|
|
4069
4280
|
// cleans everything up.
|
|
4070
4281
|
signal.addEventListener('abort', () => local.abort(), { once: true });
|
|
4282
|
+
// Initialized to 0; clearing a zero timer handle is a no-op, so no null
|
|
4283
|
+
// guard is needed around either clear call below.
|
|
4284
|
+
let backstopTimer = 0;
|
|
4071
4285
|
let completed = false;
|
|
4072
4286
|
const complete = () => {
|
|
4073
4287
|
if (completed)
|
|
4074
4288
|
return;
|
|
4075
4289
|
completed = true;
|
|
4290
|
+
clearTimeout(backstopTimer);
|
|
4076
4291
|
state.introStreamPlaying = false;
|
|
4077
4292
|
bus.emit('intro-stream-complete');
|
|
4078
|
-
local.abort(); // tear down bus listeners after the intro completes
|
|
4293
|
+
local.abort(); // tear down bus/state listeners after the intro completes
|
|
4079
4294
|
};
|
|
4080
4295
|
const onEnded = () => complete();
|
|
4081
4296
|
video.addEventListener('ended', onEnded);
|
|
@@ -4086,8 +4301,40 @@
|
|
|
4086
4301
|
console.warn('IntroStreamManager: intro stream errored — falling through to main');
|
|
4087
4302
|
complete();
|
|
4088
4303
|
}, { signal: local.signal });
|
|
4304
|
+
// G-07-12: state.error is the one signal shared by two silent failure
|
|
4305
|
+
// modes that never reach the bus — StallWatchdog reload-budget
|
|
4306
|
+
// exhaustion (src/core/command-handler.ts:159-164) and a synchronous
|
|
4307
|
+
// engine init() throw caught in onAttach (src/engines/dash.ts:108-120,
|
|
4308
|
+
// src/engines/hls.ts:194-207). Both writers are shared with the MAIN
|
|
4309
|
+
// pass, so the fix lives here (the intro-owning component) rather than
|
|
4310
|
+
// in either shared file. The Proxy `set` trap already no-ops on an
|
|
4311
|
+
// unchanged value, but a `null` write (e.g. openMainStream() clearing a
|
|
4312
|
+
// stale error) must still never be read as "the intro finished".
|
|
4313
|
+
state.on('error', (newValue) => {
|
|
4314
|
+
if (newValue === null)
|
|
4315
|
+
return;
|
|
4316
|
+
console.warn('IntroStreamManager: intro stream errored via state.error — falling through to main');
|
|
4317
|
+
complete();
|
|
4318
|
+
}, { signal: local.signal });
|
|
4319
|
+
// G-07-13 receiving half: openMainStream() (src/eb-player.ts, plan
|
|
4320
|
+
// 07-10) emits this to guarantee the intro pass is finished before main
|
|
4321
|
+
// opens. No `completed` re-check is needed or wanted here: complete()
|
|
4322
|
+
// aborts `local`, which removes this listener, so the handler below is
|
|
4323
|
+
// only ever reachable while the intro is still live — adding a guard
|
|
4324
|
+
// would create an unreachable branch.
|
|
4325
|
+
bus.on('force-intro-complete', () => {
|
|
4326
|
+
console.warn('IntroStreamManager: main is opening — force-completing the intro pass');
|
|
4327
|
+
complete();
|
|
4328
|
+
}, { signal: local.signal });
|
|
4329
|
+
// G-07-12: absolute bound — even if no other trigger ever fires, the
|
|
4330
|
+
// intro pass still completes within INTRO_MAX_DURATION_MS.
|
|
4331
|
+
backstopTimer = setTimeout(() => {
|
|
4332
|
+
console.warn('IntroStreamManager: intro stream exceeded INTRO_MAX_DURATION_MS — falling through to main');
|
|
4333
|
+
complete();
|
|
4334
|
+
}, INTRO_MAX_DURATION_MS);
|
|
4089
4335
|
signal.addEventListener('abort', () => {
|
|
4090
4336
|
video.removeEventListener('ended', onEnded);
|
|
4337
|
+
clearTimeout(backstopTimer);
|
|
4091
4338
|
}, { once: true });
|
|
4092
4339
|
}
|
|
4093
4340
|
}
|
|
@@ -5978,7 +6225,14 @@
|
|
|
5978
6225
|
* transient network failures (segment fetch, manifest re-fetch, etc.)
|
|
5979
6226
|
* never permanently stop playback.
|
|
5980
6227
|
*/
|
|
5981
|
-
|
|
6228
|
+
/**
|
|
6229
|
+
* Bounded retry-attempt count used for the intro pass so an unreachable
|
|
6230
|
+
* intro manifest surrenders quickly instead of retrying forever. With
|
|
6231
|
+
* retryIntervals at 1000ms this surfaces the dash.js ERROR event roughly
|
|
6232
|
+
* two seconds after an unreachable manifest.
|
|
6233
|
+
*/
|
|
6234
|
+
const INTRO_PASS_RETRY_ATTEMPTS = 2;
|
|
6235
|
+
function applyDashRetrySettings(driver, options = {}) {
|
|
5982
6236
|
driver.updateSettings({
|
|
5983
6237
|
streaming: {
|
|
5984
6238
|
retryIntervals: {
|
|
@@ -5993,16 +6247,17 @@
|
|
|
5993
6247
|
}
|
|
5994
6248
|
}
|
|
5995
6249
|
});
|
|
6250
|
+
const retryAttempts = options.introPass === true ? INTRO_PASS_RETRY_ATTEMPTS : Infinity;
|
|
5996
6251
|
driver.updateSettings({
|
|
5997
6252
|
streaming: {
|
|
5998
6253
|
retryAttempts: {
|
|
5999
|
-
MPD:
|
|
6000
|
-
XLinkExpansion:
|
|
6001
|
-
InitializationSegment:
|
|
6002
|
-
IndexSegment:
|
|
6003
|
-
MediaSegment:
|
|
6004
|
-
BitstreamSwitchingSegment:
|
|
6005
|
-
other:
|
|
6254
|
+
MPD: retryAttempts,
|
|
6255
|
+
XLinkExpansion: retryAttempts,
|
|
6256
|
+
InitializationSegment: retryAttempts,
|
|
6257
|
+
IndexSegment: retryAttempts,
|
|
6258
|
+
MediaSegment: retryAttempts,
|
|
6259
|
+
BitstreamSwitchingSegment: retryAttempts,
|
|
6260
|
+
other: retryAttempts,
|
|
6006
6261
|
lowLatencyMultiplyFactor: 1
|
|
6007
6262
|
}
|
|
6008
6263
|
}
|
|
@@ -6184,6 +6439,11 @@
|
|
|
6184
6439
|
// Private — NEVER in PlayerState
|
|
6185
6440
|
this.driver = null;
|
|
6186
6441
|
this.dvrErrorHandler = null;
|
|
6442
|
+
// True when this engine instance is loading the intro (preroll) stream
|
|
6443
|
+
// rather than the main stream — see D-04 / G-07-10 in init().
|
|
6444
|
+
this.isIntroPass = false;
|
|
6445
|
+
// Guards the one-shot error-fatal emit on the intro pass (T-07-21).
|
|
6446
|
+
this.introFallThroughReported = false;
|
|
6187
6447
|
}
|
|
6188
6448
|
getDriver() {
|
|
6189
6449
|
return this.driver;
|
|
@@ -6290,6 +6550,10 @@
|
|
|
6290
6550
|
const { config, video, state, signal } = this;
|
|
6291
6551
|
if (!config || !video || !state || !signal)
|
|
6292
6552
|
return;
|
|
6553
|
+
// D-04 / G-07-10: identify the intro pass so a permanently-unreachable
|
|
6554
|
+
// intro manifest can fall through to main instead of looping forever.
|
|
6555
|
+
// Same gate shape as src/engines/hls.ts:445.
|
|
6556
|
+
this.isIntroPass = Boolean(config.preroll) && this.loadSourceUrl === config.preroll;
|
|
6293
6557
|
const dashjsUrl = config.dashjs;
|
|
6294
6558
|
if (!dashjsUrl) {
|
|
6295
6559
|
throw new Error('DashEngine: config.dashjs URL is required');
|
|
@@ -6319,7 +6583,7 @@
|
|
|
6319
6583
|
player.initialize(video, this.loadSourceUrl || config.src || '', config.autoplay ?? false);
|
|
6320
6584
|
// Apply retry settings if requested
|
|
6321
6585
|
if (config.retry) {
|
|
6322
|
-
applyDashRetrySettings(player);
|
|
6586
|
+
applyDashRetrySettings(player, { introPass: this.isIntroPass });
|
|
6323
6587
|
}
|
|
6324
6588
|
// Apply custom ABR rule unless disabled
|
|
6325
6589
|
if (!config.disableCustomAbr) {
|
|
@@ -6420,12 +6684,34 @@
|
|
|
6420
6684
|
player.on(events['TRACK_CHANGE_RENDERED'], () => {
|
|
6421
6685
|
state.currentAudioTrack = player.getQualityFor('audio');
|
|
6422
6686
|
});
|
|
6423
|
-
// ERROR: log dash.js errors
|
|
6687
|
+
// ERROR: log dash.js errors, and on the intro pass fall through to main
|
|
6424
6688
|
player.on(events['ERROR'], (eventData) => {
|
|
6425
|
-
|
|
6426
|
-
console.warn('DashEngine error:', typedData?.error?.message ?? typedData?.error ?? typedData);
|
|
6689
|
+
this.handleDriverError(eventData);
|
|
6427
6690
|
});
|
|
6428
6691
|
}
|
|
6692
|
+
/**
|
|
6693
|
+
* Handles a dash.js ERROR event.
|
|
6694
|
+
*
|
|
6695
|
+
* D-04 / G-07-10: always logs, exactly as before (test/engines/dash.test.ts:619
|
|
6696
|
+
* and :1156 assert this unchanged). On the intro pass only — gated by
|
|
6697
|
+
* this.isIntroPass, computed once in init() from
|
|
6698
|
+
* `Boolean(config.preroll) && this.loadSourceUrl === config.preroll` — a fatal
|
|
6699
|
+
* manifest/segment error stops the stall watchdog and emits `error-fatal`
|
|
6700
|
+
* exactly once (guarded by introFallThroughReported) so
|
|
6701
|
+
* IntroStreamManager.complete() can fire and openMainStream() run instead of
|
|
6702
|
+
* the watchdog reopening the (still intro) lastSrc in a loop. On the main
|
|
6703
|
+
* pass this method only warns — main-stream resilience must stay untouched.
|
|
6704
|
+
*/
|
|
6705
|
+
handleDriverError(eventData) {
|
|
6706
|
+
const typedData = eventData;
|
|
6707
|
+
console.warn('DashEngine error:', typedData?.error?.message ?? typedData?.error ?? typedData);
|
|
6708
|
+
if (!this.isIntroPass || this.introFallThroughReported)
|
|
6709
|
+
return;
|
|
6710
|
+
this.introFallThroughReported = true;
|
|
6711
|
+
this.stopWatchdog();
|
|
6712
|
+
const errorKey = typedData?.error?.code ?? typedData?.error?.message ?? 'unknown';
|
|
6713
|
+
this.bus?.emit('error-fatal', { code: 'NETWORK', message: `Intro stream error: ${errorKey}` });
|
|
6714
|
+
}
|
|
6429
6715
|
}
|
|
6430
6716
|
|
|
6431
6717
|
/**
|
|
@@ -6893,6 +7179,18 @@
|
|
|
6893
7179
|
// observably awaitable.
|
|
6894
7180
|
return new Promise((resolve) => setTimeout(resolve, EME_TEARDOWN_DELAY_MS));
|
|
6895
7181
|
}
|
|
7182
|
+
/**
|
|
7183
|
+
* G-07-11: bounded backstop so main always opens even when the ad leg never
|
|
7184
|
+
* produces 'ad-complete'. Longer than AdsManager's own AD_LEG_TIMEOUT_MS
|
|
7185
|
+
* (20s) so that more precise, better-logged inner mechanism wins first in a
|
|
7186
|
+
* live player — this is the outer safety net, not the primary signal. Covers
|
|
7187
|
+
* two cases the inner mechanism cannot: an AdsManager that was never
|
|
7188
|
+
* constructed at all (noUi:true, or a null ads container — lifecycle.ts
|
|
7189
|
+
* skips the branch that would create one), and an AdsManager that obtained
|
|
7190
|
+
* its manager from the loader (disarming its own bound) whose creative then
|
|
7191
|
+
* never reached the screen.
|
|
7192
|
+
*/
|
|
7193
|
+
const MAIN_OPEN_FALLBACK_MS = 30000;
|
|
6896
7194
|
// ---------------------------------------------------------------------------
|
|
6897
7195
|
// Public API
|
|
6898
7196
|
// ---------------------------------------------------------------------------
|
|
@@ -7103,13 +7401,17 @@
|
|
|
7103
7401
|
controller.bus.on('error-fatal', () => {
|
|
7104
7402
|
inst.p2p?.stop();
|
|
7105
7403
|
}, { signal: controller.signal });
|
|
7106
|
-
// Phase 7 —
|
|
7404
|
+
// Phase 7/8 — pre-main leg orchestration.
|
|
7107
7405
|
// When mergedConfig.preroll is set, open the intro stream first, then on
|
|
7108
7406
|
// bus 'intro-stream-complete' (emitted by IntroStreamManager on video.ended,
|
|
7109
7407
|
// preroll-skip, or error-fatal) close → 500ms EME teardown delay → open main.
|
|
7110
7408
|
// If config.ad is also set, wait additionally for bus 'ad-complete' before
|
|
7111
7409
|
// opening main (D-10: intro → ad → main precedence).
|
|
7112
7410
|
//
|
|
7411
|
+
// D-02 (Phase 8): when mergedConfig.preroll is NOT set but mergedConfig.ad
|
|
7412
|
+
// IS, main is gated on 'ad-complete' alone (no intro leg to precede it) —
|
|
7413
|
+
// see the `hasAd` branch below and tryOpenMainAdOnly().
|
|
7414
|
+
//
|
|
7113
7415
|
// Runtime mute override: the live <video>.muted and PlayerState.muted are
|
|
7114
7416
|
// forced to false for the intro pass (D-13 — intros always audible) and
|
|
7115
7417
|
// restored to `originalMuted` before opening main. We do NOT mutate
|
|
@@ -7119,7 +7421,22 @@
|
|
|
7119
7421
|
// P2P opt-in: `mergedConfig.manager` IS re-read on each reference.open() in
|
|
7120
7422
|
// the P2P branch (eb-player.ts:192), so we config-level swap it to false for
|
|
7121
7423
|
// the intro pass and restore it for main (D-08 — P2P attaches only to main).
|
|
7424
|
+
// G-07-11: two independent paths can now reach openMainStream — the
|
|
7425
|
+
// ad-complete handler and the MAIN_OPEN_FALLBACK_MS backstop — so it must
|
|
7426
|
+
// be idempotent.
|
|
7427
|
+
let mainOpened = false;
|
|
7122
7428
|
const openMainStream = () => {
|
|
7429
|
+
if (mainOpened)
|
|
7430
|
+
return;
|
|
7431
|
+
mainOpened = true;
|
|
7432
|
+
// G-07-13 / D-10: force-complete the intro pass on EVERY path into main —
|
|
7433
|
+
// including ad-complete firing with no prior intro-stream-complete (a hung
|
|
7434
|
+
// intro deferred through AdsManager's INTRO_DEFERRAL_TIMEOUT_MS). This is a
|
|
7435
|
+
// FORCE-complete, never a wait: IntroStreamManager.complete() runs
|
|
7436
|
+
// synchronously, so D-04 liveness is untouched — main still opens in the
|
|
7437
|
+
// same tick. complete() is idempotent, so on the normal ordering (intro
|
|
7438
|
+
// already finished) this is a no-op; the listener is already torn down.
|
|
7439
|
+
controller.bus.emit('force-intro-complete');
|
|
7123
7440
|
if (video !== null) {
|
|
7124
7441
|
video.muted = originalMuted;
|
|
7125
7442
|
}
|
|
@@ -7145,8 +7462,100 @@
|
|
|
7145
7462
|
return;
|
|
7146
7463
|
reference.open(mergedConfig.src);
|
|
7147
7464
|
};
|
|
7148
|
-
|
|
7149
|
-
|
|
7465
|
+
// D-02 (Phase 8): promoted out of the `preroll` branch so both the
|
|
7466
|
+
// intro+ad and ad-only configurations share one gated main-open path — see
|
|
7467
|
+
// the assumption_delta_decision in 08-01-PLAN.md ("pluralization": preroll
|
|
7468
|
+
// and ad are two variants of a single "pre-main leg" concept).
|
|
7469
|
+
const hasIntro = Boolean(mergedConfig.preroll);
|
|
7470
|
+
const hasAd = Boolean(mergedConfig.ad);
|
|
7471
|
+
// G-07-11: bounds the ad leg from this player's side, independent of
|
|
7472
|
+
// AdsManager's own internal watchdog — see MAIN_OPEN_FALLBACK_MS.
|
|
7473
|
+
let mainOpenFallbackTimer = null;
|
|
7474
|
+
const clearMainOpenFallback = () => {
|
|
7475
|
+
if (mainOpenFallbackTimer !== null) {
|
|
7476
|
+
clearTimeout(mainOpenFallbackTimer);
|
|
7477
|
+
mainOpenFallbackTimer = null;
|
|
7478
|
+
}
|
|
7479
|
+
};
|
|
7480
|
+
const armMainOpenFallback = (onExpire) => {
|
|
7481
|
+
mainOpenFallbackTimer = setTimeout(() => {
|
|
7482
|
+
if (controller.signal.aborted)
|
|
7483
|
+
return;
|
|
7484
|
+
// CR-01: an ad still playing when the bound elapses is legitimate — a
|
|
7485
|
+
// 60s creative outlives the 30s window. But simply returning here would
|
|
7486
|
+
// retire the ONLY remaining bound on the ad leg: AdsManager disarms its
|
|
7487
|
+
// own AD_LEG_TIMEOUT_MS watchdog as soon as IMA hands back a manager.
|
|
7488
|
+
// An ad that then stalls (no ad-complete, no AD_ERROR) would strand main
|
|
7489
|
+
// playback for the life of the player. Re-arm instead, so the leg stays
|
|
7490
|
+
// bounded on every path for as long as the ad genuinely progresses.
|
|
7491
|
+
if (controller.state.adPlaying) {
|
|
7492
|
+
armMainOpenFallback(onExpire);
|
|
7493
|
+
return;
|
|
7494
|
+
}
|
|
7495
|
+
console.warn('eb-player: ad leg produced no ad-complete within the bound — falling through to main (G-07-11)');
|
|
7496
|
+
onExpire();
|
|
7497
|
+
}, MAIN_OPEN_FALLBACK_MS);
|
|
7498
|
+
};
|
|
7499
|
+
// WR-03: clear the fallback timer on dispose — otherwise a torn-down
|
|
7500
|
+
// player leaves a pending setTimeout holding a closure reference alive.
|
|
7501
|
+
controller.signal.addEventListener('abort', () => {
|
|
7502
|
+
clearMainOpenFallback();
|
|
7503
|
+
}, { once: true });
|
|
7504
|
+
// D-08-02 (Phase 8): the two-gate compound funnel for the ad-only
|
|
7505
|
+
// configuration. With no intro leg, main can open only once BOTH the ad
|
|
7506
|
+
// leg has settled (adGateOpen) AND the user has requested playback
|
|
7507
|
+
// (playGateOpen) — see the `else if (hasAd)` branch below. With no ad
|
|
7508
|
+
// configured the ad gate starts open. With `autoplay: false`, the ad-leg
|
|
7509
|
+
// bound is now armed unconditionally (D-08-03) and sits BEFORE
|
|
7510
|
+
// AdsManager's `await gesturePromise`, so `ad-complete` can fire ~20s
|
|
7511
|
+
// after page load with zero user interaction. Without this play gate that
|
|
7512
|
+
// alone would fetch the CDN token and load the main manifest, breaking the
|
|
7513
|
+
// documented no-prefetch contract (src/eb-player.ts:598-601 below). The
|
|
7514
|
+
// gate is deliberately kept, not dropped (A-OQ1, 08-01-PLAN.md).
|
|
7515
|
+
let adGateOpen = !hasAd;
|
|
7516
|
+
let playGateOpen = Boolean(mergedConfig.autoplay);
|
|
7517
|
+
let deferredPlaybackStarted = false;
|
|
7518
|
+
const startDeferredPlayback = () => {
|
|
7519
|
+
if (deferredPlaybackStarted)
|
|
7520
|
+
return;
|
|
7521
|
+
deferredPlaybackStarted = true;
|
|
7522
|
+
const engine = inst.engine;
|
|
7523
|
+
if (engine === null || video === null)
|
|
7524
|
+
return;
|
|
7525
|
+
// Honor the user's click intent. CommandHandler's earlier bus.on('play')
|
|
7526
|
+
// listener fired before us with no source attached, so its video.play()
|
|
7527
|
+
// was a silent no-op. Wait for driverReady (attachMedia + loadSource
|
|
7528
|
+
// done) then start playback ourselves.
|
|
7529
|
+
engine.driverReady.then(() => {
|
|
7530
|
+
const playResult = video.play();
|
|
7531
|
+
if (playResult && typeof playResult.catch === 'function') {
|
|
7532
|
+
playResult.catch((error) => {
|
|
7533
|
+
console.warn('EBPlayer: deferred play() rejected', error);
|
|
7534
|
+
});
|
|
7535
|
+
}
|
|
7536
|
+
});
|
|
7537
|
+
};
|
|
7538
|
+
const tryOpenMainAdOnly = () => {
|
|
7539
|
+
if (!adGateOpen || !playGateOpen)
|
|
7540
|
+
return;
|
|
7541
|
+
openMainStream();
|
|
7542
|
+
if (!mergedConfig.autoplay) {
|
|
7543
|
+
startDeferredPlayback();
|
|
7544
|
+
}
|
|
7545
|
+
};
|
|
7546
|
+
if (hasAd) {
|
|
7547
|
+
controller.bus.on('ad-complete', () => {
|
|
7548
|
+
clearMainOpenFallback();
|
|
7549
|
+
adGateOpen = true;
|
|
7550
|
+
if (hasIntro) {
|
|
7551
|
+
openMainStream();
|
|
7552
|
+
}
|
|
7553
|
+
else {
|
|
7554
|
+
tryOpenMainAdOnly();
|
|
7555
|
+
}
|
|
7556
|
+
}, { signal: controller.signal });
|
|
7557
|
+
}
|
|
7558
|
+
if (hasIntro) {
|
|
7150
7559
|
controller.bus.on('intro-stream-complete', () => {
|
|
7151
7560
|
reference.close();
|
|
7152
7561
|
// WR-08: restore PlayerState.muted synchronously — DO NOT wait for the
|
|
@@ -7160,18 +7569,25 @@
|
|
|
7160
7569
|
controller.state.muted = originalMuted;
|
|
7161
7570
|
// EME teardown delay — see waitForEmeTeardown / EME_TEARDOWN_DELAY_MS.
|
|
7162
7571
|
waitForEmeTeardown().then(() => {
|
|
7572
|
+
// G-07-13: main may already be open — the ad-complete path force-
|
|
7573
|
+
// completes the intro (openMainStream() emits force-intro-complete),
|
|
7574
|
+
// which re-enters this handler synchronously before this async
|
|
7575
|
+
// continuation ever runs. Arming the fallback timer here would be an
|
|
7576
|
+
// orphan: main is already open and nothing will ever clear it.
|
|
7577
|
+
if (mainOpened)
|
|
7578
|
+
return;
|
|
7163
7579
|
if (!hasAd) {
|
|
7164
7580
|
openMainStream();
|
|
7581
|
+
return;
|
|
7165
7582
|
}
|
|
7166
|
-
//
|
|
7167
|
-
// after the AdsManager finishes (D-10 precedence).
|
|
7583
|
+
// hasAd: the ad-complete handler above will normally call
|
|
7584
|
+
// openMainStream after the AdsManager finishes (D-10 precedence).
|
|
7585
|
+
// G-07-11: arm the bounded fallback in case it never does — an
|
|
7586
|
+
// AdsManager that was never constructed, or one whose creative
|
|
7587
|
+
// never rendered after disarming its own bound.
|
|
7588
|
+
armMainOpenFallback(openMainStream);
|
|
7168
7589
|
});
|
|
7169
7590
|
}, { signal: controller.signal });
|
|
7170
|
-
if (hasAd) {
|
|
7171
|
-
controller.bus.on('ad-complete', () => {
|
|
7172
|
-
openMainStream();
|
|
7173
|
-
}, { signal: controller.signal });
|
|
7174
|
-
}
|
|
7175
7591
|
// Force runtime mute off for the intro pass — applied to the LIVE <video>
|
|
7176
7592
|
// element and PlayerState, NOT to mergedConfig.muted (no-op for runtime).
|
|
7177
7593
|
if (video !== null) {
|
|
@@ -7184,6 +7600,52 @@
|
|
|
7184
7600
|
controller.config.manager = false;
|
|
7185
7601
|
reference.open(mergedConfig.preroll);
|
|
7186
7602
|
}
|
|
7603
|
+
// D-02/D-08-02 (Phase 8): ad-only configuration — no intro stream, so this
|
|
7604
|
+
// must NOT call reference.open() directly and must NOT copy any
|
|
7605
|
+
// intro-specific step (no intro source open, no force-unmute, no P2P
|
|
7606
|
+
// manager swap, no intro-stream-complete listener). Main opens only via
|
|
7607
|
+
// tryOpenMainAdOnly() once both gates clear.
|
|
7608
|
+
else if (hasAd) {
|
|
7609
|
+
if (mergedConfig.autoplay) {
|
|
7610
|
+
// playGateOpen is already true (set above) — main opens on whichever
|
|
7611
|
+
// of 'ad-complete' or the fallback arrives first. tryOpenMainAdOnly()
|
|
7612
|
+
// alone is NOT a real backstop here: it returns early unless
|
|
7613
|
+
// adGateOpen is already true, and the only place that flips
|
|
7614
|
+
// adGateOpen is the ad-complete handler above, which clears this very
|
|
7615
|
+
// timer before calling it — so an unmodified tryOpenMainAdOnly passed
|
|
7616
|
+
// directly can never observe adGateOpen === false the one time it
|
|
7617
|
+
// would need to. Force the ad gate open in the callback itself so the
|
|
7618
|
+
// bound can still open main when ad-complete never arrives at all
|
|
7619
|
+
// (e.g. AdsManager is never constructed) — see G-07-11.
|
|
7620
|
+
armMainOpenFallback(() => {
|
|
7621
|
+
adGateOpen = true;
|
|
7622
|
+
tryOpenMainAdOnly();
|
|
7623
|
+
});
|
|
7624
|
+
}
|
|
7625
|
+
else {
|
|
7626
|
+
// D-08-02: the 'play' gate — see the rationale comment above
|
|
7627
|
+
// tryOpenMainAdOnly(). A repeat click must be inert.
|
|
7628
|
+
controller.bus.on('play', () => {
|
|
7629
|
+
if (playGateOpen)
|
|
7630
|
+
return;
|
|
7631
|
+
playGateOpen = true;
|
|
7632
|
+
if (adGateOpen) {
|
|
7633
|
+
// The ad leg already settled — main can open now.
|
|
7634
|
+
tryOpenMainAdOnly();
|
|
7635
|
+
startDeferredPlayback();
|
|
7636
|
+
}
|
|
7637
|
+
else {
|
|
7638
|
+
// The ad leg is still in flight — start the outer 30s backstop
|
|
7639
|
+
// from the play request, not from page load, using the same
|
|
7640
|
+
// force-the-gate shape as the autoplay:true arm above.
|
|
7641
|
+
armMainOpenFallback(() => {
|
|
7642
|
+
adGateOpen = true;
|
|
7643
|
+
tryOpenMainAdOnly();
|
|
7644
|
+
});
|
|
7645
|
+
}
|
|
7646
|
+
}, { signal: controller.signal });
|
|
7647
|
+
}
|
|
7648
|
+
}
|
|
7187
7649
|
// Auto-open the stream if src is provided in config (matches legacy player behaviour
|
|
7188
7650
|
// where consumers call start({ src: '...' }) and expect playback to begin immediately).
|
|
7189
7651
|
// When autoplay is false, defer open() until the user requests play — this avoids
|
|
@@ -7199,21 +7661,7 @@
|
|
|
7199
7661
|
return;
|
|
7200
7662
|
deferredOpen = false;
|
|
7201
7663
|
reference.open(mergedConfig.src);
|
|
7202
|
-
|
|
7203
|
-
// listener fired before us with no source attached, so its video.play()
|
|
7204
|
-
// was a silent no-op. Wait for driverReady (attachMedia + loadSource
|
|
7205
|
-
// done) then start playback ourselves.
|
|
7206
|
-
const engine = inst.engine;
|
|
7207
|
-
if (engine !== null && video !== null) {
|
|
7208
|
-
engine.driverReady.then(() => {
|
|
7209
|
-
const playResult = video.play();
|
|
7210
|
-
if (playResult && typeof playResult.catch === 'function') {
|
|
7211
|
-
playResult.catch((error) => {
|
|
7212
|
-
console.warn('EBPlayer: deferred play() rejected', error);
|
|
7213
|
-
});
|
|
7214
|
-
}
|
|
7215
|
-
});
|
|
7216
|
-
}
|
|
7664
|
+
startDeferredPlayback();
|
|
7217
7665
|
}, { signal: controller.signal });
|
|
7218
7666
|
}
|
|
7219
7667
|
}
|