UNPKG

unified-video-framework

Version:

Cross-platform video player framework supporting iOS, Android, Web, Smart TVs (Samsung/LG), Roku, and more

757 lines (743 loc) โ€ข 32.2 kB
export class GoogleAdsManager { constructor(video, adContainer, config) { this.adsManager = null; this.adsLoader = null; this.adDisplayContainer = null; this.isAdPlaying = false; this.isMuted = true; this.unmuteButton = null; this.adProgressBar = null; this.adProgressInterval = null; this.lastAdTime = 0; this.periodicAdCheckInterval = null; this.pendingAdRequest = false; this.timeupdateHandler = null; this.volumechangeHandler = null; this.triggeredMidrollTimes = new Set(); this.video = video; this.adContainer = adContainer; this.config = config; this.focusHandler = () => { if (this.isAdPlaying) { console.log('Window focused - resuming ad playback'); this.resumeAdPlayback(); setTimeout(() => this.resumeAdPlayback(), 200); } }; this.visibilityHandler = () => { if (!document.hidden && this.isAdPlaying) { console.log('Tab became visible - resuming ad playback'); this.resumeAdPlayback(); } }; this.setupFocusHandler(); } setupFocusHandler() { window.addEventListener('focus', this.focusHandler); document.addEventListener('visibilitychange', this.visibilityHandler); } async initialize() { try { await this.loadIMASDK(); this.setupAdsLoader(); } catch (error) { console.error('Failed to initialize Google Ads:', error); this.config.onAdError?.(error); } } loadIMASDK() { return new Promise((resolve, reject) => { if (window.google?.ima) { resolve(); return; } const script = document.createElement('script'); script.src = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js'; script.async = true; script.onload = () => resolve(); script.onerror = () => reject(new Error('Failed to load Google IMA SDK')); document.head.appendChild(script); }); } setupAdsLoader() { const google = window.google; const rect = this.adContainer.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) { console.warn('โš ๏ธ Ad container has zero dimensions:', rect.width, 'x', rect.height); console.warn('IMA UI may not render correctly. Ensure container is visible before initialization.'); } this.adDisplayContainer = new google.ima.AdDisplayContainer(this.adContainer, this.video); this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer); this.adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, (event) => this.onAdsManagerLoaded(event), false); this.adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, (event) => this.onAdError(event), false); this.video.addEventListener('ended', () => { if (!this.isAdPlaying) { this.adsLoader?.contentComplete(); } }); this.setupMidrollTriggers(); } setupMidrollTriggers() { if (!this.config.midrollTimes || this.config.midrollTimes.length === 0) { return; } console.log(`๐Ÿ“บ Setting up mid-roll triggers for times: ${this.config.midrollTimes.join(', ')}s`); this.timeupdateHandler = () => { if (this.isAdPlaying || this.pendingAdRequest) return; const currentTime = this.video.currentTime; for (const triggerTime of this.config.midrollTimes) { if (!this.triggeredMidrollTimes.has(triggerTime) && currentTime >= triggerTime && currentTime < triggerTime + 2) { console.log(`โฐ Mid-roll triggered at ${currentTime.toFixed(1)}s (scheduled: ${triggerTime}s)`); this.triggeredMidrollTimes.add(triggerTime); this.pendingAdRequest = true; this.lastAdTime = currentTime; if (this.config.pauseStreamDuringAd && !this.video.paused) { this.video.pause(); } this.requestAds(); break; } } }; this.video.addEventListener('timeupdate', this.timeupdateHandler); } setupPeriodicAds() { if (this.config.liveAdBreakMode !== 'periodic' || !this.config.periodicAdInterval) { return; } if (this.periodicAdCheckInterval !== null) { return; } console.log(`๐Ÿ“บ Setting up periodic ads: interval=${this.config.periodicAdInterval}s`); this.periodicAdCheckInterval = setInterval(() => { if (this.isAdPlaying || this.pendingAdRequest) { return; } const currentTime = this.video.currentTime; const interval = this.config.periodicAdInterval || 30; if (currentTime - this.lastAdTime >= interval && currentTime > 0) { console.log(`โฐ Periodic ad triggered at ${currentTime.toFixed(1)}s (interval: ${interval}s)`); this.triggerPeriodicAd(); } }, 1000); console.log('โœ… Periodic ad scheduling enabled'); } triggerPeriodicAd() { if (this.pendingAdRequest || this.isAdPlaying) { return; } this.pendingAdRequest = true; this.lastAdTime = this.video.currentTime; console.log('๐ŸŽฌ Triggering periodic ad break...'); if (this.config.pauseStreamDuringAd && !this.video.paused) { this.video.pause(); } this.requestAds(); } requestAds() { const google = window.google; if (!this.adsLoader) { console.warn('requestAds() called but adsLoader is null (destroyed?)'); this.pendingAdRequest = false; return; } try { const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = this.config.adTagUrl; adsRequest.linearAdSlotWidth = this.video.clientWidth; adsRequest.linearAdSlotHeight = this.video.clientHeight; adsRequest.nonLinearAdSlotWidth = this.video.clientWidth; adsRequest.nonLinearAdSlotHeight = Math.floor(this.video.clientHeight / 3); if (this.config.companionAdSlots && this.config.companionAdSlots.length > 0) { const companionAdSlots = this.config.companionAdSlots.map(slot => { const container = document.getElementById(slot.containerId); if (!container) return null; return new google.ima.CompanionAdSelectionSettings(); }).filter(Boolean); if (companionAdSlots.length > 0) { adsRequest.companionSlots = companionAdSlots; } } adsRequest.setAdWillAutoPlay(true); adsRequest.setAdWillPlayMuted(true); this.adsLoader.requestAds(adsRequest); } catch (error) { console.error('Error requesting ads:', error); this.pendingAdRequest = false; this.config.onAdError?.(error); } } initAdDisplayContainer() { try { this.adDisplayContainer?.initialize(); } catch (error) { console.warn('Ad display container already initialized'); } } onAdsManagerLoaded(event) { const google = window.google; const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.enablePreloading = true; adsRenderingSettings.mute = this.video.muted; adsRenderingSettings.useStyledLinearAds = true; console.log('๐Ÿ“Š IMA SDK Info:', { version: google.ima.VERSION || 'unknown', hasUiElements: !!google.ima.UiElements, availableUiElements: google.ima.UiElements ? Object.keys(google.ima.UiElements) : [] }); console.log('โœ… IMA AdsRenderingSettings configured:', { uiElements: adsRenderingSettings.uiElements || 'not set (using IMA defaults)', enablePreloading: adsRenderingSettings.enablePreloading, useStyledLinearAds: adsRenderingSettings.useStyledLinearAds, mute: adsRenderingSettings.mute, restoreCustomPlaybackStateOnAdBreakComplete: adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete }); this.adsManager = event.getAdsManager(this.video, adsRenderingSettings); try { const cuePoints = this.adsManager.getCuePoints(); if (cuePoints && cuePoints.length > 0) { const allCuePoints = cuePoints.map((time) => { if (time === 0) return 0; if (time === -1) return -1; return time; }); console.log('๐Ÿ“ Ad cue points detected (pre/mid/post):', allCuePoints); if (this.config.onAdCuePoints) { this.config.onAdCuePoints(allCuePoints); } } } catch (error) { console.warn('Could not extract ad cue points:', error); } this.setupAdsManagerListeners(); try { this.adsManager.init(this.video.clientWidth, this.video.clientHeight, google.ima.ViewMode.NORMAL); this.adsManager.start(); this.setupPeriodicAds(); } catch (error) { console.error('Error starting ads:', error); this.pendingAdRequest = false; this.isAdPlaying = false; this.video.play().catch(() => { }); } } setupAdsManagerListeners() { const google = window.google; this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, () => { console.log('Ad: Content paused'); this.isAdPlaying = true; this.pendingAdRequest = false; this.video.pause(); if (this.adContainer) { this.adContainer.style.visibility = 'visible'; this.adContainer.style.opacity = '1'; this.adContainer.style.pointerEvents = 'auto'; this.adContainer.style.zIndex = '2147483647'; console.log('โœ… Ad container visibility enforced'); setTimeout(() => { const imaElements = this.adContainer.querySelectorAll('[class*="ima-"], [id*="ima-"]'); const allDivs = this.adContainer.querySelectorAll('div'); let advertisementLabelFound = false; allDivs.forEach((div) => { const text = div.textContent?.trim() || ''; if (text === 'Advertisement' || text === 'Ad' || text.startsWith('Advertisement')) { console.log('๐Ÿ“ข Found Advertisement label:', { text, visible: div.offsetWidth > 0 && div.offsetHeight > 0, display: getComputedStyle(div).display, position: getComputedStyle(div).position }); div.style.display = 'block'; div.style.visibility = 'visible'; div.style.opacity = '1'; div.style.position = 'absolute'; div.style.top = '10px'; div.style.left = '10px'; div.style.zIndex = '999999'; div.style.color = 'white'; div.style.background = 'rgba(0, 0, 0, 0.6)'; div.style.padding = '4px 8px'; div.style.fontSize = '12px'; div.style.fontFamily = 'Arial, sans-serif'; advertisementLabelFound = true; } }); console.log('๐Ÿ” IMA UI Diagnostic:', { totalDivs: allDivs.length, imaElements: imaElements.length, containerChildren: this.adContainer.children.length, adContainerSize: `${this.adContainer.offsetWidth}x${this.adContainer.offsetHeight}`, advertisementLabelFound }); if (!advertisementLabelFound) { console.warn('โš ๏ธ Advertisement label not found in DOM - creating custom overlay'); const adLabel = document.createElement('div'); adLabel.id = 'uvf-custom-ad-label'; adLabel.textContent = 'Advertisement'; adLabel.style.cssText = ` position: absolute !important; top: 10px !important; left: 10px !important; z-index: 2147483647 !important; color: white !important; background: rgba(0, 0, 0, 0.7) !important; padding: 4px 10px !important; font-size: 12px !important; font-family: Arial, Roboto, sans-serif !important; font-weight: 500 !important; letter-spacing: 0.5px !important; border-radius: 2px !important; pointer-events: none !important; user-select: none !important; `; this.adContainer.appendChild(adLabel); console.log('โœ… Custom Advertisement label created and displayed'); } if (imaElements.length === 0) { console.warn('โš ๏ธ No IMA UI elements found! UI may not be rendering.'); console.warn('This could mean: 1) Ad creative has no UI, 2) uiElements config issue, 3) IMA SDK version issue'); } else { console.log('โœ… IMA UI elements detected:', Array.from(imaElements).map(el => el.className || el.id)); } }, 500); } const preventPlayDuringAd = (e) => { if (this.isAdPlaying) { e.preventDefault(); this.video.pause(); console.warn('Blocked video play attempt during ad'); } }; this.video.addEventListener('play', preventPlayDuringAd); this.video.__adPlayBlocker = preventPlayDuringAd; this.volumechangeHandler = () => { if (!this.adsManager || !this.isAdPlaying) return; const vol = this.video.muted ? 0 : this.video.volume; this.adsManager.setVolume(vol); const wasMuted = this.isMuted; this.isMuted = this.video.muted || this.video.volume === 0; if (wasMuted !== this.isMuted) { this.updateMuteButtonUI(); } }; this.video.addEventListener('volumechange', this.volumechangeHandler); this.config.onAdStart?.(); }); this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, () => { console.log('Ad: Content resume'); this.isAdPlaying = false; const customLabel = this.adContainer.querySelector('#uvf-custom-ad-label'); if (customLabel) { customLabel.remove(); } this.hideAdProgressBar(); const preventPlayDuringAd = this.video.__adPlayBlocker; if (preventPlayDuringAd) { this.video.removeEventListener('play', preventPlayDuringAd); delete this.video.__adPlayBlocker; } if (this.volumechangeHandler) { this.video.removeEventListener('volumechange', this.volumechangeHandler); this.volumechangeHandler = null; } this.config.onAdEnd?.(); this.video.play().catch(() => { }); }); this.adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, (event) => { const ad = event.getAd(); console.log('Ad started:', { type: ad.isLinear() ? 'Linear (video)' : 'Non-linear (overlay)', duration: ad.getDuration(), skippable: ad.getSkipTimeOffset() !== -1, title: ad.getTitle(), }); this.isMuted = this.video.muted; console.log(`Ad started - video.muted=${this.video.muted}, isMuted=${this.isMuted}`); if (this.isMuted) { this.showUnmuteButton(); } this.showAdProgressBar(ad.getDuration()); }); this.adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, () => { console.log('Ad completed'); }); this.adsManager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, () => { console.log('All ads completed'); this.config.onAllAdsComplete?.(); }); this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, (event) => this.onAdError(event)); this.adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPED, () => { console.log('Ad skipped by user'); }); this.adsManager.addEventListener(google.ima.AdEvent.Type.PAUSED, () => { console.log('Ad paused (likely from click-through)'); }); this.adsManager.addEventListener(google.ima.AdEvent.Type.PLAYING, () => { console.log('Ad resumed playing'); }); } onAdError(event) { const error = event.getError?.(); console.error('Ad error:', error?.getMessage?.() || error); this.config.onAdError?.(error); if (this.adsManager) { this.adsManager.destroy(); this.adsManager = null; } this.isAdPlaying = false; this.pendingAdRequest = false; const blocker = this.video.__adPlayBlocker; if (blocker) { this.video.removeEventListener('play', blocker); delete this.video.__adPlayBlocker; } this.hideUnmuteButton(); this.video.play().catch(() => { }); } pause() { if (this.adsManager && this.isAdPlaying) { this.adsManager.pause(); } } resume() { if (this.adsManager && this.isAdPlaying) { this.adsManager.resume(); } } skip() { if (this.adsManager) { this.adsManager.skip(); } } resize(width, height, viewMode) { const google = window.google; if (this.adsManager && google && google.ima) { const mode = viewMode || google.ima.ViewMode.NORMAL; console.log(`๐Ÿ“ Resizing ads: ${width}x${height}, ViewMode: ${mode === google.ima.ViewMode.FULLSCREEN ? 'FULLSCREEN' : 'NORMAL'}`); if (this.adContainer && mode === google.ima.ViewMode.FULLSCREEN) { this.adContainer.style.position = 'fixed'; this.adContainer.style.top = '0'; this.adContainer.style.left = '0'; this.adContainer.style.width = `${width}px`; this.adContainer.style.height = `${height}px`; this.adContainer.style.zIndex = '2147483647'; console.log('โœ… Ad container forced to fullscreen dimensions'); } this.adsManager.resize(width, height, mode); } } setVolume(volume) { if (this.adsManager) { this.adsManager.setVolume(volume); } } isPlayingAd() { return this.isAdPlaying; } showUnmuteButton() { if (this.unmuteButton) { this.unmuteButton.remove(); } this.unmuteButton = document.createElement('button'); this.unmuteButton.id = 'ad-unmute-btn'; this.unmuteButton.className = 'uvf-unmute-btn'; this.unmuteButton.setAttribute('aria-label', 'Tap to unmute ad'); this.unmuteButton.innerHTML = ` <svg viewBox="0 0 24 24" class="uvf-unmute-icon"> <path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/> </svg> <span class="uvf-unmute-text">Tap to unmute</span> `; this.unmuteButton.addEventListener('click', (e) => { e.stopPropagation(); this.toggleAdMute(); }); if (!document.getElementById('uvf-unmute-styles')) { const style = document.createElement('style'); style.id = 'uvf-unmute-styles'; style.textContent = ` .uvf-unmute-btn { position: absolute !important; bottom: 80px !important; left: 20px !important; z-index: 1000 !important; display: flex !important; align-items: center !important; gap: 8px !important; padding: 12px 16px !important; background: rgba(0, 0, 0, 0.8) !important; border: none !important; border-radius: 4px !important; color: white !important; font-size: 14px !important; font-weight: 500 !important; cursor: pointer !important; transition: all 0.2s ease !important; backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important; animation: uvf-unmute-pulse 2s ease-in-out infinite !important; } .uvf-unmute-btn:hover { background: rgba(0, 0, 0, 0.9) !important; transform: scale(1.05) !important; } .uvf-unmute-btn:active { transform: scale(0.95) !important; } .uvf-unmute-icon { width: 20px !important; height: 20px !important; fill: white !important; } .uvf-unmute-text { white-space: nowrap !important; } @keyframes uvf-unmute-pulse { 0%, 100% { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important; } 50% { box-shadow: 0 2px 16px rgba(255, 255, 255, 0.2) !important; } } @media (max-width: 767px) { .uvf-unmute-btn { bottom: 70px !important; left: 50% !important; transform: translateX(-50%) !important; padding: 10px 14px !important; font-size: 13px !important; } .uvf-unmute-btn:hover { transform: translateX(-50%) scale(1.05) !important; } } /* Protect IMA native UI elements from being hidden by parent application CSS */ .uvf-ad-container [class*="ima-"], .uvf-ad-container [id*="ima-"] { visibility: visible !important; opacity: 1 !important; } /* Ensure IMA control containers are visible */ .uvf-ad-container .ima-ad-container, .uvf-ad-container .ima-controls-div, .uvf-ad-container .ima-countdown-div, .uvf-ad-container .ima-text-div { display: block !important; visibility: visible !important; opacity: 1 !important; pointer-events: auto !important; } /* CRITICAL: Force Advertisement label visibility (usually top-left) */ .uvf-ad-container .ima-ad-container [class*="text"], .uvf-ad-container .ima-ad-container [class*="label"], .uvf-ad-container .ima-ad-container [class*="attribution"] { display: block !important; visibility: visible !important; opacity: 1 !important; position: relative !important; z-index: 999999 !important; } /* CRITICAL: Force countdown timer visibility */ .uvf-ad-container .ima-ad-container [class*="countdown"], .uvf-ad-container .ima-ad-container [class*="time"], .uvf-ad-container .ima-ad-container [class*="remaining"] { display: block !important; visibility: visible !important; opacity: 1 !important; position: relative !important; z-index: 999999 !important; } /* Force all text elements in ad container to be visible */ .uvf-ad-container * { max-height: none !important; max-width: none !important; } /* CRITICAL: Advertisement label - typically top-left corner */ /* This is required by Google Ads policy and FTC regulations */ .uvf-ad-container div[style*="top"][style*="left"] { display: block !important; visibility: visible !important; opacity: 1 !important; } /* Force any element containing "Advertisement" or "Ad" text to show */ .uvf-ad-container div:has-text("Advertisement"), .uvf-ad-container div:has-text("Ad ยท"), .uvf-ad-container span:has-text("Advertisement") { display: block !important; visibility: visible !important; opacity: 1 !important; color: white !important; background: rgba(0, 0, 0, 0.6) !important; padding: 4px 8px !important; } `; document.head.appendChild(style); } this.adContainer.appendChild(this.unmuteButton); console.log('Unmute button displayed (matching player style)'); } toggleAdMute() { this.isMuted = !this.isMuted; if (this.adsManager) { this.adsManager.setVolume(this.isMuted ? 0 : (this.video.volume || 1)); console.log(`Ad ${this.isMuted ? 'muted' : 'unmuted'}`); } this.video.muted = this.isMuted; this.updateMuteButtonUI(); } updateMuteButtonUI() { if (!this.unmuteButton) { if (this.isMuted) this.showUnmuteButton(); return; } if (this.isMuted) { this.unmuteButton.setAttribute('aria-label', 'Tap to unmute ad'); this.unmuteButton.innerHTML = ` <svg viewBox="0 0 24 24" class="uvf-unmute-icon"> <path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/> </svg> <span class="uvf-unmute-text">Tap to unmute</span> `; } else { this.hideUnmuteButton(); } } resumeAdPlayback() { try { if (!this.adsManager || !this.isAdPlaying) { return; } console.log('Attempting to resume ad playback...'); try { this.adsManager.resume(); console.log('โœ… Ad resume() called'); } catch (e) { console.warn('resume() failed:', e); } } catch (error) { console.error('Error resuming ad playback:', error); } } hideUnmuteButton() { if (this.unmuteButton) { this.unmuteButton.remove(); this.unmuteButton = null; } } showAdProgressBar(duration) { this.hideAdProgressBar(); const container = document.createElement('div'); container.id = 'uvf-ad-progress-container'; container.style.cssText = ` position: absolute !important; bottom: 0px !important; left: 0px !important; right: 0px !important; height: 4px !important; background: rgba(0, 0, 0, 0.4) !important; overflow: hidden !important; z-index: 2147483646 !important; pointer-events: none !important; `; const fill = document.createElement('div'); fill.id = 'uvf-ad-progress-fill'; fill.style.cssText = ` position: absolute !important; left: 0 !important; top: 0 !important; height: 100% !important; width: 0% !important; background: #FFC107 !important; box-shadow: 0 0 8px rgba(255, 193, 7, 0.6) !important; transition: width 0.1s linear !important; `; container.appendChild(fill); this.adContainer.appendChild(container); this.adProgressBar = container; console.log('โœ… Ad progress bar created'); let currentTime = 0; this.adProgressInterval = setInterval(() => { if (!this.adsManager || !this.isAdPlaying) { this.hideAdProgressBar(); return; } try { const ad = this.adsManager.getCurrentAd(); if (ad) { currentTime = this.adsManager.getRemainingTime(); const elapsed = duration - currentTime; const progress = Math.min(100, Math.max(0, (elapsed / duration) * 100)); const fillEl = document.getElementById('uvf-ad-progress-fill'); if (fillEl) { fillEl.style.width = `${progress}%`; } } } catch (e) { } }, 100); } hideAdProgressBar() { if (this.adProgressInterval) { clearInterval(this.adProgressInterval); this.adProgressInterval = null; } if (this.adProgressBar) { this.adProgressBar.remove(); this.adProgressBar = null; } } destroy() { this.hideUnmuteButton(); this.hideAdProgressBar(); window.removeEventListener('focus', this.focusHandler); document.removeEventListener('visibilitychange', this.visibilityHandler); if (this.timeupdateHandler) { this.video.removeEventListener('timeupdate', this.timeupdateHandler); this.timeupdateHandler = null; } if (this.volumechangeHandler) { this.video.removeEventListener('volumechange', this.volumechangeHandler); this.volumechangeHandler = null; } const blocker = this.video.__adPlayBlocker; if (blocker) { this.video.removeEventListener('play', blocker); delete this.video.__adPlayBlocker; } if (this.periodicAdCheckInterval) { clearInterval(this.periodicAdCheckInterval); this.periodicAdCheckInterval = null; console.log('โœ… Periodic ad scheduling stopped'); } if (this.adsManager) { this.adsManager.destroy(); this.adsManager = null; } if (this.adsLoader) { this.adsLoader.destroy(); this.adsLoader = null; } this.isAdPlaying = false; this.pendingAdRequest = false; this.triggeredMidrollTimes.clear(); } } //# sourceMappingURL=GoogleAdsManager.js.map