<!-- 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