UNPKG

unified-video-framework

Version:

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

405 lines (398 loc) 16.1 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.video = video; this.adContainer = adContainer; this.config = config; this.setupFocusHandler(); } setupFocusHandler() { window.addEventListener('focus', () => { if (this.isAdPlaying) { console.log('Window focused - resuming ad playback'); this.resumeAdPlayback(); setTimeout(() => this.resumeAdPlayback(), 200); } }); document.addEventListener('visibilitychange', () => { if (!document.hidden && this.isAdPlaying) { console.log('Tab became visible - resuming ad playback'); this.resumeAdPlayback(); } }); } 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; 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(); } }); } requestAds() { const google = window.google; 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 => { return new google.ima.CompanionAdSelectionSettings(); }); } adsRequest.setAdWillAutoPlay(true); adsRequest.setAdWillPlayMuted(true); this.adsLoader.requestAds(adsRequest); } catch (error) { console.error('Error requesting ads:', error); 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; 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(); } catch (error) { console.error('Error starting ads:', error); 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.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'); } 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.config.onAdStart?.(); }); this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, () => { console.log('Ad: Content resume'); this.isAdPlaying = false; const preventPlayDuringAd = this.video.__adPlayBlocker; if (preventPlayDuringAd) { this.video.removeEventListener('play', preventPlayDuringAd); delete this.video.__adPlayBlocker; } 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.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.isAdPlaying = false; 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; } } `; 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 : 1); console.log(`Ad ${this.isMuted ? 'muted' : 'unmuted'}`); } this.video.muted = this.isMuted; if (!this.isMuted && this.unmuteButton) { this.unmuteButton.remove(); this.unmuteButton = null; } } 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); } if (this.video) { try { if (this.video.paused) { const playPromise = this.video.play(); if (playPromise) { playPromise.catch((err) => { console.warn('Video play() failed:', err); }); } console.log('✅ Video play() called'); } } catch (e) { console.warn('Video play failed:', e); } } } catch (error) { console.error('Error resuming ad playback:', error); } } hideUnmuteButton() { if (this.unmuteButton) { this.unmuteButton.remove(); this.unmuteButton = null; } } destroy() { this.hideUnmuteButton(); if (this.adsManager) { this.adsManager.destroy(); this.adsManager = null; } if (this.adsLoader) { this.adsLoader.destroy(); this.adsLoader = null; } this.isAdPlaying = false; } } //# sourceMappingURL=GoogleAdsManager.js.map