eb-player 2.0.19 → 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/eb-player.css +9 -0
- package/dist/build/ebplayer.bundle.js +604 -132
- package/dist/build/ebplayer.bundle.js.map +1 -1
- package/dist/build/types/integrations/ads-manager.d.ts.map +1 -1
- package/dist/build/types/skin/skin-root.d.ts +8 -0
- package/dist/build/types/skin/skin-root.d.ts.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/dist/eb-player.css +9 -0
- package/package.json +15 -15
|
@@ -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.
|
|
@@ -3324,6 +3324,11 @@
|
|
|
3324
3324
|
state.on('controlsVisible', () => {
|
|
3325
3325
|
this.updateControlsVisibility();
|
|
3326
3326
|
}, { signal });
|
|
3327
|
+
// Toggle ad-playing class so the IMA ads container can receive clicks
|
|
3328
|
+
// (skip button, click-through) and sit above the player chrome during ads.
|
|
3329
|
+
state.on('adPlaying', () => {
|
|
3330
|
+
this.updateAdPlayingState();
|
|
3331
|
+
}, { signal });
|
|
3327
3332
|
}
|
|
3328
3333
|
/**
|
|
3329
3334
|
* Toggles poster image visibility based on playbackState.
|
|
@@ -3398,6 +3403,19 @@
|
|
|
3398
3403
|
inner.classList.toggle('eb-controls-visible', visible);
|
|
3399
3404
|
inner.classList.toggle('eb-controls-hidden', !visible);
|
|
3400
3405
|
}
|
|
3406
|
+
/**
|
|
3407
|
+
* Toggles the eb-ad-playing class on the player container. While an IMA ad
|
|
3408
|
+
* plays, CSS raises the ads container above the player chrome and enables
|
|
3409
|
+
* pointer-events on it so IMA's own ad UI (Skip button, click-through) is
|
|
3410
|
+
* clickable — the container is pointer-events:none at rest to avoid blocking
|
|
3411
|
+
* clicks on the content video.
|
|
3412
|
+
*/
|
|
3413
|
+
updateAdPlayingState() {
|
|
3414
|
+
const inner = this.el?.firstElementChild;
|
|
3415
|
+
if (inner == null)
|
|
3416
|
+
return;
|
|
3417
|
+
inner.classList.toggle('eb-ad-playing', this.state.adPlaying);
|
|
3418
|
+
}
|
|
3401
3419
|
/**
|
|
3402
3420
|
* Applies or removes the dir=rtl attribute without full re-render.
|
|
3403
3421
|
* More efficient than re-rendering the entire template for a simple attribute change.
|
|
@@ -3863,15 +3881,37 @@
|
|
|
3863
3881
|
}
|
|
3864
3882
|
|
|
3865
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;
|
|
3866
3901
|
/**
|
|
3867
3902
|
* AdsManager manages the IMA SDK preroll ad lifecycle.
|
|
3868
3903
|
*
|
|
3869
|
-
* -
|
|
3870
|
-
*
|
|
3871
|
-
*
|
|
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)
|
|
3872
3911
|
* - Sets state.adPlaying based on IMA lifecycle events
|
|
3873
|
-
* - Emits 'ad-complete' on TypedEventBus
|
|
3874
|
-
* - 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
|
|
3875
3915
|
*/
|
|
3876
3916
|
class AdsManager {
|
|
3877
3917
|
constructor() {
|
|
@@ -3881,105 +3921,270 @@
|
|
|
3881
3921
|
/**
|
|
3882
3922
|
* Initialize the IMA SDK preroll ad system.
|
|
3883
3923
|
* - Skips if config.ad is undefined
|
|
3884
|
-
* -
|
|
3885
|
-
*
|
|
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)
|
|
3886
3932
|
* - Loads IMA SDK, creates AdDisplayContainer, AdsLoader, and AdsRequest
|
|
3887
3933
|
* - Wires ad lifecycle events to PlayerState and TypedEventBus
|
|
3888
3934
|
*/
|
|
3889
3935
|
async init(config, state, bus, video, adsContainer, signal) {
|
|
3890
3936
|
if (!config.ad)
|
|
3891
3937
|
return;
|
|
3892
|
-
//
|
|
3893
|
-
//
|
|
3894
|
-
//
|
|
3895
|
-
//
|
|
3896
|
-
//
|
|
3897
|
-
|
|
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) {
|
|
3898
4003
|
await new Promise((resolve) => {
|
|
3899
|
-
|
|
3900
|
-
|
|
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);
|
|
3901
4014
|
});
|
|
3902
4015
|
if (signal.aborted)
|
|
3903
4016
|
return;
|
|
3904
4017
|
}
|
|
3905
|
-
//
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
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)
|
|
3909
4046
|
return;
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
// completion so the orchestration always advances.
|
|
3923
|
-
adsLoader.addEventListener(ima.AdErrorEvent.Type.AD_ERROR, (errorEvent) => {
|
|
3924
|
-
console.warn('AdsManager: AD_ERROR (loader) — falling through to main', errorEvent);
|
|
3925
|
-
state.adPlaying = false;
|
|
3926
|
-
bus.emit('ad-complete');
|
|
3927
|
-
});
|
|
3928
|
-
adsLoader.addEventListener(ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, (loadedEvent) => {
|
|
3929
|
-
const event = loadedEvent;
|
|
3930
|
-
const adsManager = event.getAdsManager(video);
|
|
3931
|
-
this.imaAdsManager = adsManager;
|
|
3932
|
-
// getBoundingClientRect is always defined on HTMLElement; the prior
|
|
3933
|
-
// truthy check on the method reference was dead code. The real edge
|
|
3934
|
-
// case is a zero-sized element (display:none, before layout) which
|
|
3935
|
-
// would make IMA's ads container collapse — fall back to a 16:9
|
|
3936
|
-
// baseline in that case.
|
|
3937
|
-
const rect = video.getBoundingClientRect();
|
|
3938
|
-
const width = rect.width > 0 ? rect.width : 640;
|
|
3939
|
-
const height = rect.height > 0 ? rect.height : 360;
|
|
3940
|
-
// Wire event handlers for ad lifecycle
|
|
3941
|
-
adsManager.addEventListener(ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, () => {
|
|
3942
|
-
state.adPlaying = true;
|
|
3943
|
-
});
|
|
3944
|
-
adsManager.addEventListener(ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, () => {
|
|
3945
|
-
state.adPlaying = false;
|
|
3946
|
-
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();
|
|
3947
4059
|
});
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
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();
|
|
3959
4147
|
});
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
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
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
/**
|
|
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
|
+
}
|
|
3978
4176
|
}
|
|
3979
4177
|
/**
|
|
3980
|
-
* Wait for a click event
|
|
4178
|
+
* Wait for a click event anywhere inside the player root.
|
|
3981
4179
|
* Used to satisfy browser autoplay policies when autoplay is disabled.
|
|
3982
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
|
+
*
|
|
3983
4188
|
* WR-05: single-shot settle flag guards against the click-vs-abort race —
|
|
3984
4189
|
* Promise resolution is already idempotent but settled tightens the
|
|
3985
4190
|
* contract by ensuring listener cleanup happens exactly once for either
|
|
@@ -3987,16 +4192,17 @@
|
|
|
3987
4192
|
*/
|
|
3988
4193
|
waitForUserGesture(video, signal) {
|
|
3989
4194
|
return new Promise((resolve) => {
|
|
4195
|
+
const target = video.closest('.eb-player') ?? video;
|
|
3990
4196
|
let settled = false;
|
|
3991
4197
|
const settle = () => {
|
|
3992
4198
|
if (settled)
|
|
3993
4199
|
return;
|
|
3994
4200
|
settled = true;
|
|
3995
|
-
|
|
4201
|
+
target.removeEventListener('click', onClick, { capture: true });
|
|
3996
4202
|
resolve();
|
|
3997
4203
|
};
|
|
3998
4204
|
const onClick = () => settle();
|
|
3999
|
-
|
|
4205
|
+
target.addEventListener('click', onClick, { capture: true });
|
|
4000
4206
|
// Clean up if aborted before click. The abort listener auto-removes
|
|
4001
4207
|
// via `{ once: true }`; the click listener is removed inside settle().
|
|
4002
4208
|
signal.addEventListener('abort', settle, { once: true });
|
|
@@ -4004,6 +4210,21 @@
|
|
|
4004
4210
|
}
|
|
4005
4211
|
}
|
|
4006
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;
|
|
4007
4228
|
/**
|
|
4008
4229
|
* IntroStreamManager owns the intro-stream lifecycle.
|
|
4009
4230
|
*
|
|
@@ -4012,12 +4233,24 @@
|
|
|
4012
4233
|
* - the video element's native `ended` event
|
|
4013
4234
|
* - bus `preroll-skip` (from the PrerollOverlay skip button)
|
|
4014
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
|
|
4015
4247
|
* - Any one of those triggers calls a closure `complete()` exactly once
|
|
4016
4248
|
* (idempotent via a `completed` boolean guard), which flips
|
|
4017
4249
|
* state.introStreamPlaying = false and emits `intro-stream-complete`.
|
|
4018
4250
|
* - On abort, the video `ended` listener is removed explicitly via
|
|
4019
|
-
* removeEventListener
|
|
4020
|
-
*
|
|
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()).
|
|
4021
4254
|
*
|
|
4022
4255
|
* Scope: this manager listens and emits — it does NOT itself open the intro
|
|
4023
4256
|
* stream. The engine open() call is owned by eb-player.ts (single source of
|
|
@@ -4028,8 +4261,10 @@
|
|
|
4028
4261
|
/**
|
|
4029
4262
|
* Initialize intro-stream lifecycle tracking.
|
|
4030
4263
|
* - Skips entirely if config.preroll is falsy (no state flip, no listeners).
|
|
4031
|
-
* - Otherwise flips state.introStreamPlaying = true and registers the
|
|
4032
|
-
* 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).
|
|
4033
4268
|
*/
|
|
4034
4269
|
async init(config, state, bus, video, signal) {
|
|
4035
4270
|
if (!config.preroll)
|
|
@@ -4044,14 +4279,18 @@
|
|
|
4044
4279
|
// Mirror outer aborts onto the local controller so dispose() still
|
|
4045
4280
|
// cleans everything up.
|
|
4046
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;
|
|
4047
4285
|
let completed = false;
|
|
4048
4286
|
const complete = () => {
|
|
4049
4287
|
if (completed)
|
|
4050
4288
|
return;
|
|
4051
4289
|
completed = true;
|
|
4290
|
+
clearTimeout(backstopTimer);
|
|
4052
4291
|
state.introStreamPlaying = false;
|
|
4053
4292
|
bus.emit('intro-stream-complete');
|
|
4054
|
-
local.abort(); // tear down bus listeners after the intro completes
|
|
4293
|
+
local.abort(); // tear down bus/state listeners after the intro completes
|
|
4055
4294
|
};
|
|
4056
4295
|
const onEnded = () => complete();
|
|
4057
4296
|
video.addEventListener('ended', onEnded);
|
|
@@ -4062,8 +4301,40 @@
|
|
|
4062
4301
|
console.warn('IntroStreamManager: intro stream errored — falling through to main');
|
|
4063
4302
|
complete();
|
|
4064
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);
|
|
4065
4335
|
signal.addEventListener('abort', () => {
|
|
4066
4336
|
video.removeEventListener('ended', onEnded);
|
|
4337
|
+
clearTimeout(backstopTimer);
|
|
4067
4338
|
}, { once: true });
|
|
4068
4339
|
}
|
|
4069
4340
|
}
|
|
@@ -5954,7 +6225,14 @@
|
|
|
5954
6225
|
* transient network failures (segment fetch, manifest re-fetch, etc.)
|
|
5955
6226
|
* never permanently stop playback.
|
|
5956
6227
|
*/
|
|
5957
|
-
|
|
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 = {}) {
|
|
5958
6236
|
driver.updateSettings({
|
|
5959
6237
|
streaming: {
|
|
5960
6238
|
retryIntervals: {
|
|
@@ -5969,16 +6247,17 @@
|
|
|
5969
6247
|
}
|
|
5970
6248
|
}
|
|
5971
6249
|
});
|
|
6250
|
+
const retryAttempts = options.introPass === true ? INTRO_PASS_RETRY_ATTEMPTS : Infinity;
|
|
5972
6251
|
driver.updateSettings({
|
|
5973
6252
|
streaming: {
|
|
5974
6253
|
retryAttempts: {
|
|
5975
|
-
MPD:
|
|
5976
|
-
XLinkExpansion:
|
|
5977
|
-
InitializationSegment:
|
|
5978
|
-
IndexSegment:
|
|
5979
|
-
MediaSegment:
|
|
5980
|
-
BitstreamSwitchingSegment:
|
|
5981
|
-
other:
|
|
6254
|
+
MPD: retryAttempts,
|
|
6255
|
+
XLinkExpansion: retryAttempts,
|
|
6256
|
+
InitializationSegment: retryAttempts,
|
|
6257
|
+
IndexSegment: retryAttempts,
|
|
6258
|
+
MediaSegment: retryAttempts,
|
|
6259
|
+
BitstreamSwitchingSegment: retryAttempts,
|
|
6260
|
+
other: retryAttempts,
|
|
5982
6261
|
lowLatencyMultiplyFactor: 1
|
|
5983
6262
|
}
|
|
5984
6263
|
}
|
|
@@ -6160,6 +6439,11 @@
|
|
|
6160
6439
|
// Private — NEVER in PlayerState
|
|
6161
6440
|
this.driver = null;
|
|
6162
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;
|
|
6163
6447
|
}
|
|
6164
6448
|
getDriver() {
|
|
6165
6449
|
return this.driver;
|
|
@@ -6266,6 +6550,10 @@
|
|
|
6266
6550
|
const { config, video, state, signal } = this;
|
|
6267
6551
|
if (!config || !video || !state || !signal)
|
|
6268
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;
|
|
6269
6557
|
const dashjsUrl = config.dashjs;
|
|
6270
6558
|
if (!dashjsUrl) {
|
|
6271
6559
|
throw new Error('DashEngine: config.dashjs URL is required');
|
|
@@ -6295,7 +6583,7 @@
|
|
|
6295
6583
|
player.initialize(video, this.loadSourceUrl || config.src || '', config.autoplay ?? false);
|
|
6296
6584
|
// Apply retry settings if requested
|
|
6297
6585
|
if (config.retry) {
|
|
6298
|
-
applyDashRetrySettings(player);
|
|
6586
|
+
applyDashRetrySettings(player, { introPass: this.isIntroPass });
|
|
6299
6587
|
}
|
|
6300
6588
|
// Apply custom ABR rule unless disabled
|
|
6301
6589
|
if (!config.disableCustomAbr) {
|
|
@@ -6396,12 +6684,34 @@
|
|
|
6396
6684
|
player.on(events['TRACK_CHANGE_RENDERED'], () => {
|
|
6397
6685
|
state.currentAudioTrack = player.getQualityFor('audio');
|
|
6398
6686
|
});
|
|
6399
|
-
// ERROR: log dash.js errors
|
|
6687
|
+
// ERROR: log dash.js errors, and on the intro pass fall through to main
|
|
6400
6688
|
player.on(events['ERROR'], (eventData) => {
|
|
6401
|
-
|
|
6402
|
-
console.warn('DashEngine error:', typedData?.error?.message ?? typedData?.error ?? typedData);
|
|
6689
|
+
this.handleDriverError(eventData);
|
|
6403
6690
|
});
|
|
6404
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
|
+
}
|
|
6405
6715
|
}
|
|
6406
6716
|
|
|
6407
6717
|
/**
|
|
@@ -6869,6 +7179,18 @@
|
|
|
6869
7179
|
// observably awaitable.
|
|
6870
7180
|
return new Promise((resolve) => setTimeout(resolve, EME_TEARDOWN_DELAY_MS));
|
|
6871
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;
|
|
6872
7194
|
// ---------------------------------------------------------------------------
|
|
6873
7195
|
// Public API
|
|
6874
7196
|
// ---------------------------------------------------------------------------
|
|
@@ -7079,13 +7401,17 @@
|
|
|
7079
7401
|
controller.bus.on('error-fatal', () => {
|
|
7080
7402
|
inst.p2p?.stop();
|
|
7081
7403
|
}, { signal: controller.signal });
|
|
7082
|
-
// Phase 7 —
|
|
7404
|
+
// Phase 7/8 — pre-main leg orchestration.
|
|
7083
7405
|
// When mergedConfig.preroll is set, open the intro stream first, then on
|
|
7084
7406
|
// bus 'intro-stream-complete' (emitted by IntroStreamManager on video.ended,
|
|
7085
7407
|
// preroll-skip, or error-fatal) close → 500ms EME teardown delay → open main.
|
|
7086
7408
|
// If config.ad is also set, wait additionally for bus 'ad-complete' before
|
|
7087
7409
|
// opening main (D-10: intro → ad → main precedence).
|
|
7088
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
|
+
//
|
|
7089
7415
|
// Runtime mute override: the live <video>.muted and PlayerState.muted are
|
|
7090
7416
|
// forced to false for the intro pass (D-13 — intros always audible) and
|
|
7091
7417
|
// restored to `originalMuted` before opening main. We do NOT mutate
|
|
@@ -7095,7 +7421,22 @@
|
|
|
7095
7421
|
// P2P opt-in: `mergedConfig.manager` IS re-read on each reference.open() in
|
|
7096
7422
|
// the P2P branch (eb-player.ts:192), so we config-level swap it to false for
|
|
7097
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;
|
|
7098
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');
|
|
7099
7440
|
if (video !== null) {
|
|
7100
7441
|
video.muted = originalMuted;
|
|
7101
7442
|
}
|
|
@@ -7121,8 +7462,100 @@
|
|
|
7121
7462
|
return;
|
|
7122
7463
|
reference.open(mergedConfig.src);
|
|
7123
7464
|
};
|
|
7124
|
-
|
|
7125
|
-
|
|
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) {
|
|
7126
7559
|
controller.bus.on('intro-stream-complete', () => {
|
|
7127
7560
|
reference.close();
|
|
7128
7561
|
// WR-08: restore PlayerState.muted synchronously — DO NOT wait for the
|
|
@@ -7136,18 +7569,25 @@
|
|
|
7136
7569
|
controller.state.muted = originalMuted;
|
|
7137
7570
|
// EME teardown delay — see waitForEmeTeardown / EME_TEARDOWN_DELAY_MS.
|
|
7138
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;
|
|
7139
7579
|
if (!hasAd) {
|
|
7140
7580
|
openMainStream();
|
|
7581
|
+
return;
|
|
7141
7582
|
}
|
|
7142
|
-
//
|
|
7143
|
-
// 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);
|
|
7144
7589
|
});
|
|
7145
7590
|
}, { signal: controller.signal });
|
|
7146
|
-
if (hasAd) {
|
|
7147
|
-
controller.bus.on('ad-complete', () => {
|
|
7148
|
-
openMainStream();
|
|
7149
|
-
}, { signal: controller.signal });
|
|
7150
|
-
}
|
|
7151
7591
|
// Force runtime mute off for the intro pass — applied to the LIVE <video>
|
|
7152
7592
|
// element and PlayerState, NOT to mergedConfig.muted (no-op for runtime).
|
|
7153
7593
|
if (video !== null) {
|
|
@@ -7160,6 +7600,52 @@
|
|
|
7160
7600
|
controller.config.manager = false;
|
|
7161
7601
|
reference.open(mergedConfig.preroll);
|
|
7162
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
|
+
}
|
|
7163
7649
|
// Auto-open the stream if src is provided in config (matches legacy player behaviour
|
|
7164
7650
|
// where consumers call start({ src: '...' }) and expect playback to begin immediately).
|
|
7165
7651
|
// When autoplay is false, defer open() until the user requests play — this avoids
|
|
@@ -7175,21 +7661,7 @@
|
|
|
7175
7661
|
return;
|
|
7176
7662
|
deferredOpen = false;
|
|
7177
7663
|
reference.open(mergedConfig.src);
|
|
7178
|
-
|
|
7179
|
-
// listener fired before us with no source attached, so its video.play()
|
|
7180
|
-
// was a silent no-op. Wait for driverReady (attachMedia + loadSource
|
|
7181
|
-
// done) then start playback ourselves.
|
|
7182
|
-
const engine = inst.engine;
|
|
7183
|
-
if (engine !== null && video !== null) {
|
|
7184
|
-
engine.driverReady.then(() => {
|
|
7185
|
-
const playResult = video.play();
|
|
7186
|
-
if (playResult && typeof playResult.catch === 'function') {
|
|
7187
|
-
playResult.catch((error) => {
|
|
7188
|
-
console.warn('EBPlayer: deferred play() rejected', error);
|
|
7189
|
-
});
|
|
7190
|
-
}
|
|
7191
|
-
});
|
|
7192
|
-
}
|
|
7664
|
+
startDeferredPlayback();
|
|
7193
7665
|
}, { signal: controller.signal });
|
|
7194
7666
|
}
|
|
7195
7667
|
}
|