HBA instream reporting with Google IMA SDK

Learn how to enable HBA to pick up revenue and impressions when using Google's IMA SDK for instream video - either directly or via a 3rd party video player.

When using Google's IMA SDK the instreamTracking module for Prebid.js is not working. Instead it is necessary to "manually" register instream impressions using the relevantDigital.Auction.registerImpressionByAdId() function.

NOTICE: This method is comparing <Ad id="??"> ids in VAST XML for so called Wrapper ads - with the ids in Prebid bid responses. Potentially it might happen that the same instream ad (with the same id) is delivered e.g. via Open Bidding in Google Ad Manager instead. In this case the impression + revenue will incorrectly be attributed to Prebid in the headerbid reports.

When using the IMA SDK directly

We need to listen for the google.ima.AdEvent.Type.IMPRESSION event and then extract the ad id(s) via the Ad.getWrapperAdIds() and Ad.getAdId() functions that we'll then supply to relevantDigital.Auction.registerImpressionByAdId() .

First we create the event handler:

// Our impression event handler
const onImp = (ev) => {
window.relevantDigital = window.relevantDigital || {};
relevantDigital.cmd = relevantDigital.cmd || [];
relevantDigital.cmd.push(() => {
// Loop through all ad ids starting from the innermost ad.
// registerImpressionByAdId() will return true if it registered an impression
      const adIds = [
ev.getAd().getAdId(),
           ...ev.getAd().getWrapperAdIds(),
];
for (const id of adIds) {
if (relevantDigital.Auction.registerImpressionByAdId(id)) {
return;
}
}
});
};

We can then use this onImp handler from the existing site code similar to this:

const onAdsManagerLoaded = (ev) => {
...
 adsManager = ev.getAdsManager(...);

// THE NEW LINE!
adsManager.addEventListener(google.ima.AdEvent.Type.IMPRESSION, onImp);
};

...

// Create ads loader.
const adsLoader = new google.ima.AdsLoader(...);
// Listen and respond to ads loaded and error events.
adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
onAdsManagerLoaded, false);

When using the IMA SDK indirectly via 3'rd party video players

Unfortunately it's currently not possible to listen for the impression-events in a supported way from "outside" using the IMA SDK as there is no reliable way to get access to the google.ima.AdsManager instances.

It is highly recommended that you check the documentation for your existing video player to see if there is a way to to listen to impression events and get ad ids in the same manner as via Ad.getWrapperAdIds() and Ad.getAdId(). If so it should be possible create a listener very similar to the onImp listener above.

As a last resort it's possible to "inject" some own JavaScript-functions into the IMA SDK instead - in order to capture the impression events.

WARNING: The code below overwrites google.ima.AdsLoader etc. In worst case a subsequent update of the IMA SDK might interfere with this solution in such a way so that instream ads breaks completely!

The code below must be executed before the video player code creates the google.ima.AdsLoader object in order to work.

// Helper-function that triggers a callback when a variable is set
// in an object. Our use-case will be to react when
// google.ima.AdsLoader is set, and replace it with an own version.
const inject = (obj, [name, ...rest], cb) => {
let val = obj[name];
const update = (newVal) => {
if (rest.length) {
inject(newVal, rest, cb);
} else {
newVal = cb(newVal)
}
val = newVal;
}
if (val) {
update(val)
} else {
Object.defineProperty(obj, name, {
get() {
return val;
},
set: update,
});
}
}

inject(window, ['google', 'ima', 'AdsLoader'], (org) => {
// Our impression event handler
const onImp = (ev) => {
window.relevantDigital = window.relevantDigital || {};
relevantDigital.cmd = relevantDigital.cmd || [];
relevantDigital.cmd.push(() => {
// Loop through all ad ids starting from the innermost ad.
// registerImpressionByAdId() will return
// true if it registered an impression
    const adIds = [
                ev.getAd().getAdId(),
               ...ev.getAd().getWrapperAdIds(),
];
for (const id of adIds) {
if (relevantDigital.Auction
.registerImpressionByAdId(id))
{
return;
}
}
});
};

const onAdsManagerLoaded = (ev) => {
const orgGet = ev.getAdsManager;
ev.getAdsManager = function () {
const mgr = orgGet.apply(this, arguments);
if (mgr) {
mgr.addEventListener(
google.ima.AdEvent.Type.IMPRESSION,
onImp,
false
);
}
return mgr;
}
};

// Create our own version of google.ima.AdsLoader
const loader = function () {
const res = org.apply(this, arguments);
this.addEventListener(
google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
onAdsManagerLoaded,
false
);
return res;
};
// Make our AdLoader "inherit" from the orignal AdLoader
Object.assign(loader, org);
loader.prototype = org.prototype;
return loader;
});