Meta Description" name="description" />
<!-- Load IMA SDK -->
<script src="https://imasdk.googleapis.com/js/sdkloader/ima3.js"></script>
<!-- Outstream container -->
<div id="gam-outstream-wrapper" class="gam-outstream-wrapper" aria-hidden="false">
  <div class="gam-outstream-inner">
    <video id="gam-outstream-video" class="gam-outstream-video" playsinline muted aria-hidden="true"></video>
    <button id="gam-outstream-close" class="gam-outstream-close" aria-label="Close ad">✕</button>
  </div>
</div>
<style>
/* Basic styling + responsive 16:9 aspect ratio */
.gam-outstream-wrapper {
  position: relative;
  max-width: 640px;
  width: 100%;
  margin: 20px auto;
  /* keep a little fallback background while ad loads */
  background: #000;
  overflow: visible;
}
.gam-outstream-inner {
  position: relative;
  width: 100%;
  /* modern browsers: keep 16:9 aspect ratio */
  aspect-ratio: 16 / 9;
  /* fallback for older browsers */
  height: 0;
  padding-top: 56.25%; /* 16:9 fallback */
  overflow: hidden;
  background: #000;
  border-radius: 4px;
}
/* Make the video fill the inner box */
.gam-outstream-video {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #000;
}
/* Close button */
.gam-outstream-close {
  display: none; /* shown after ad starts */
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 9999;
  background: rgba(0,0,0,0.55);
  color: #fff;
  border: none;
  border-radius: 50%;
  width: 28px;
  height: 28px;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
}
</style>
<script>
(function () {
  // --- Config / constants ---
  var IMA_ADTAG_URL = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21796171171/Outstream&description_url=[placeholder]&tfcd=0&npa=0&sz=1x1&cust_params=page_url%3Dpage_url&gdfp_req=1&unviewed_position_start=1&correlator=" + Date.now() + "&vpos=preroll&output=vast&env=vp&vpmute=0&vpa=click";
  // Replace the above URL's iu=... with your own ad unit & keep "Google IMA SDK" selected when generating the VAST.
  var wrapper = document.getElementById('gam-outstream-wrapper');
  var inner = wrapper.querySelector('.gam-outstream-inner');
  var videoEl = document.getElementById('gam-outstream-video');
  var closeBtn = document.getElementById('gam-outstream-close');
  var adsLoader = null;
  var adsManager = null;
  var isSticky = false;
  var adsManagerLoaded = false;
  var noFillTimeout = null;
  // Save original inline styles so we can restore them
  var originalStyles = {
    position: wrapper.style.position || '',
    width: wrapper.style.width || '',
    height: wrapper.style.height || '',
    bottom: wrapper.style.bottom || '',
    right: wrapper.style.right || '',
    zIndex: wrapper.style.zIndex || ''
  };
  // Helper: compute 16:9 height for width w
  function heightForWidth(w) {
    return Math.round((w * 9) / 16);
  }
  // Make the wrapper sticky (bottom-right) with max width 320
  function makeSticky() {
    if (isSticky) return;
    var maxStickyW = Math.min(window.innerWidth, 320);
    wrapper.style.position = 'fixed';
    wrapper.style.width = maxStickyW + 'px';
    wrapper.style.height = heightForWidth(maxStickyW) + 'px';
    wrapper.style.bottom = '10px';
    wrapper.style.right = '10px';
    wrapper.style.zIndex = '9999';
    // adjust inner to fill
    inner.style.aspectRatio = ''; // remove aspect-ratio so absolute sizing works
    inner.style.height = '100%';
    inner.style.paddingTop = '0';
    isSticky = true;
  }
  // Reset wrapper to inline placement
  function resetInline() {
    if (!isSticky) {
      // also ensure standard inline sizing
      wrapper.style.position = originalStyles.position || 'relative';
      wrapper.style.width = originalStyles.width || '100%';
      wrapper.style.height = originalStyles.height || '';
      wrapper.style.bottom = originalStyles.bottom || '';
      wrapper.style.right = originalStyles.right || '';
      wrapper.style.zIndex = originalStyles.zIndex || '';
      inner.style.aspectRatio = '16 / 9';
      inner.style.paddingTop = '56.25%';
      inner.style.height = ''; 
      return;
    }
    // restore inline layout
    wrapper.style.position = originalStyles.position || 'relative';
    wrapper.style.width = originalStyles.width || '100%';
    wrapper.style.height = originalStyles.height || '';
    wrapper.style.bottom = originalStyles.bottom || '';
    wrapper.style.right = originalStyles.right || '';
    wrapper.style.zIndex = originalStyles.zIndex || '';
    inner.style.aspectRatio = '16 / 9';
    inner.style.paddingTop = '56.25%';
    inner.style.height = '';
    isSticky = false;
  }
  // Remove the wrapper from DOM and clean up listeners
  function collapseWrapper() {
    try {
      if (noFillTimeout) {
        clearTimeout(noFillTimeout);
        noFillTimeout = null;
      }
      if (adsManager) {
        try { adsManager.destroy(); } catch (e) {}
        adsManager = null;
      }
      if (adsLoader) {
        try { adsLoader.removeEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); } catch(e){}
        try { adsLoader.removeEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); } catch(e){}
      }
    } catch (e) { console.warn(e); }
    window.removeEventListener('scroll', onScroll);
    window.removeEventListener('resize', onResize);
    closeBtn.removeEventListener('click', onCloseClick);
    if (wrapper && wrapper.parentNode) {
      wrapper.parentNode.removeChild(wrapper);
    }
  }
  // Scroll handler: switch to sticky when wrapper scrolled out of view
  function onScroll() {
    if (!wrapper || !wrapper.getBoundingClientRect) return;
    var rect = wrapper.getBoundingClientRect();
    if (!isSticky && rect.bottom < 0) {
      makeSticky();
    } else if (isSticky && rect.top >= 0) {
      resetInline();
    }
  }
  // Resize handler: adjust sticky size if active
  function onResize() {
    if (isSticky) {
      var maxStickyW = Math.min(window.innerWidth, 320);
      wrapper.style.width = maxStickyW + 'px';
      wrapper.style.height = heightForWidth(maxStickyW) + 'px';
    }
  }
  // Close button click
  function onCloseClick() {
    collapseWrapper();
  }
  // --- IMA event handlers ---
  function onAdsManagerLoaded(adsManagerLoadedEvent) {
    adsManagerLoaded = true;
    if (noFillTimeout) { clearTimeout(noFillTimeout); noFillTimeout = null; }
    // Get the AdsManager and start playback
    adsManager = adsManagerLoadedEvent.getAdsManager(videoEl);
    // Common events: STARTED, COMPLETE, SKIPPED
    adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, function () {
      // show close button once ad is playing
      closeBtn.style.display = 'block';
    });
    adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, function () {
      // When ad completes → return inline
      resetInline();
      // hide close button (optional)
      closeBtn.style.display = 'none';
    });
    // User skipped the ad (non-linear / skippable) → collapse
    adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPED, function () {
      collapseWrapper();
    });
    // Add ad error handling at adsManager level as well
    adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, function (err) {
      console.warn('AdsManager AdError', err);
      collapseWrapper();
    });
    try {
      // initialize and start the ads manager; compute a sensible height
      var width = inner.offsetWidth || wrapper.offsetWidth || 640;
      var height = inner.offsetHeight || heightForWidth(width) || 360;
      adsManager.init(width, height, google.ima.ViewMode.NORMAL);
      adsManager.start();
    } catch (adErr) {
      console.warn('Failed to start ads manager', adErr);
      collapseWrapper();
    }
  }
  function onAdError(adErrorEvent) {
    // No fill or other ad errors → collapse the wrapper
    console.warn('IMA AdError', adErrorEvent.getError && adErrorEvent.getError().toString ? adErrorEvent.getError().toString() : adErrorEvent);
    collapseWrapper();
  }
  // --- Initialize IMA and request ads ---
  function initAds() {
    if (!window.google || !google.ima) {
      console.error('IMA SDK not available. Make sure https://imasdk.googleapis.com/js/sdkloader/ima3.js is loaded.');
      collapseWrapper();
      return;
    }
    try {
      // Create the ad display container & ads loader
      var adDisplayContainer = new google.ima.AdDisplayContainer(inner, videoEl);
      // Initialize the container. Note: IMA docs mention calling initialize() as a result of user action for some cases.
      try {
        adDisplayContainer.initialize();
      } catch (e) {
        // initialization may still work; log and continue
        console.warn('AdDisplayContainer.initialize() warning:', e);
      }
      adsLoader = new google.ima.AdsLoader(adDisplayContainer);
      adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false);
      adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false);
      // Prepare the request
      var adsRequest = new google.ima.AdsRequest();
      adsRequest.adTagUrl = IMA_ADTAG_URL;
      // sizing for request
      var w = inner.offsetWidth || wrapper.offsetWidth || 640;
      var h = inner.offsetHeight || heightForWidth(w) || 360;
      adsRequest.linearAdSlotWidth = w;
      adsRequest.linearAdSlotHeight = h;
      adsRequest.nonLinearAdSlotWidth = w;
      adsRequest.nonLinearAdSlotHeight = Math.min(150, Math.round(h * 0.4));
      // Request ads
      adsLoader.requestAds(adsRequest);
      // Safety: if no AdsManagerLoaded within X seconds, assume no fill and collapse.
      noFillTimeout = setTimeout(function () {
        if (!adsManagerLoaded) {
          console.warn('No ads manager loaded within timeout — collapsing container (assume no fill).');
          collapseWrapper();
        }
      }, 7000); // 7 seconds (adjust if you want)
    } catch (err) {
      console.error('Failed to initialize IMA ads:', err);
      collapseWrapper();
    }
  }
  // Wire up UI events
  window.addEventListener('scroll', onScroll);
  window.addEventListener('resize', onResize);
  closeBtn.addEventListener('click', onCloseClick);
  // Start the ad flow
  // It's safe to call immediately in many pages; depending on browser autoplay restrictions, IMA may require user gesture.
  initAds();
})();
</script>23
9
470KB
1467KB
300.0ms
308.0ms
615.0ms