Skip to content

Instantly share code, notes, and snippets.

@AramZS
Created March 16, 2016 22:21
Show Gist options
  • Save AramZS/85b593fb3a070eb3d4a4 to your computer and use it in GitHub Desktop.
Save AramZS/85b593fb3a070eb3d4a4 to your computer and use it in GitHub Desktop.
OpenVideoViews tracking script
/**
* Copyright (c) 2013 Open VideoView
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* A container for all OpenVV instances running on the page
* @class
* @constructor
*/
function OVV() {
///////////////////////////////////////////////////////////////////////////
// PUBLIC ATTRIBUTES
///////////////////////////////////////////////////////////////////////////
/**
* Determines whether OpenVV should run in debug mode. Debug mode always
* adds beacon SWFs to the page, which are color-coded based on their
* status. OVVCheck.beaconViewabilityState and
* OVVCheck.geometryViewabilityState are also populated in debug mode.
* @type {Boolean}
* @see {@link OVVCheck#geometryViewabilityState}
* @see {@link OVVCheck#beaconViewabilityState}
* @see {@link OVVAsset#BEACON_SIZE}
*/
this.DEBUG = false;
/**
* Whether OpenVV is running within an iframe or not.
* @type {Boolean}
*/
this.IN_IFRAME = (window.top !== window.self);
/**
* The last asset added to OVV. Useful for easy access from the
* JavaScript console.
* @type {OVVAsset}
*/
this.asset = null;
/**
* The id of the interval responsible for positioning beacons.
*/
this.positionInterval;
var userAgent = window.testOvvConfig && window.testOvvConfig.userAgent ? window.testOvvConfig.userAgent : navigator.userAgent;
/**
* Returns an object that contains the browser name, version and id {@link OVV#browserIDEnum}
* @param {ua} userAgent
*/
function getBrowserDetailsByUserAgent(ua) {
var getData = function () {
var data = { ID: 0, name: '', version: '' };
var dataString = ua;
for (var i = 0; i < dataBrowsers.length; i++) {
// Fill Browser ID
if (dataString.match(new RegExp(dataBrowsers[i].brRegex)) != null) {
data.ID = dataBrowsers[i].id;
data.name = dataBrowsers[i].name;
if (dataBrowsers[i].verRegex == null) {
break;
}
//Fill Browser Version
var brverRes = dataString.match(new RegExp(dataBrowsers[i].verRegex + '[0-9]*'));
if (brverRes != null) {
var replaceStr = brverRes[0].match(new RegExp(dataBrowsers[i].verRegex));
data.version = brverRes[0].replace(replaceStr[0], '');
}
break;
}
}
return data;
};
var dataBrowsers = [{
id: 4,
name: 'Opera',
brRegex: 'OPR|Opera',
verRegex: '(OPR\/|Version\/)'
}, {
id: 1,
name: 'MSIE',
brRegex: 'MSIE|Trident/7.*rv:11|rv:11.*Trident/7',
verRegex: '(MSIE |rv:)'
}, {
id: 2,
name: 'Firefox',
brRegex: 'Firefox',
verRegex: 'Firefox\/'
}, {
id: 3,
name: 'Chrome',
brRegex: 'Chrome',
verRegex: 'Chrome\/'
}, {
id: 5,
name: 'Safari',
brRegex: 'Safari|(OS |OS X )[0-9].*AppleWebKit',
verRegex: 'Version\/'
}
];
return getData();
};
this.browserIDEnum = {
MSIE: 1,
Firefox: 2,
Chrome: 3,
Opera: 4,
safari: 5
};
/**
* browser:
* {
* ID: ,
* name: '',
* version: ''
* };
*/
this.browser = getBrowserDetailsByUserAgent(userAgent);
this.servingScenarioEnum = { OnPage: 1, SameDomainIframe: 2, CrossDomainIframe: 3 };
function getServingScenarioType(servingScenarioEnum) {
try {
if (window.top == window) {
return servingScenarioEnum.OnPage;
} else if (window.top.document.domain == window.document.domain) {
return servingScenarioEnum.SameDomainIframe;
}
} catch (e) { }
return servingScenarioEnum.CrossDomainIframe;
};
this.servingScenario = getServingScenarioType(this.servingScenarioEnum);
/**
* The interval in which ActionScript will poll OVV for viewability
* information
* @type {Number}
*/
this.interval = 200;
/**
* OVV version
* @type {Number}
*/
this.releaseVersion = '1.1.0';
/**
* OVV build version
* @type {String}
*/
this.buildVersion = 'UNKNOWN';
///////////////////////////////////////////////////////////////////////////
// PRIVATE ATTRIBUTES
///////////////////////////////////////////////////////////////////////////
/**
* An object for storing OVVAssets. {@link OVVAsset}s are stored with their
* id as the key and the OVVAsset as the value.
* @type {Object}
*/
var assets = {};
/**
* An array for storing the first PREVIOUS_EVENTS_CAPACITY events for each event type. {@see PREVIOUS_EVENTS_CAPACITY}
* @type {Array}
*/
var previousEvents = [];
/**
* Number of event to store
* @type {int}
*/
var PREVIOUS_EVENTS_CAPACITY = 1000;
/**
* An array that holds all the subscribes for a eventName+uid combination
* @type {Array}
*/
var subscribers = [];
///////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS
///////////////////////////////////////////////////////////////////////////
/**
* Stores an asset which can be retrieved later using
* {@link OVV#getAssetById}. The latest asset added to OVV can also be
* retrieved via the {@link OVV#asset} property.
* @param {OVVAsset} ovvAsset An asset to observe
*/
this.addAsset = function (ovvAsset) {
if (!assets.hasOwnProperty(ovvAsset.getId())) {
assets[ovvAsset.getId()] = ovvAsset;
// save a reference for convenience
this.asset = ovvAsset;
}
};
/**
* Removes an {@link OVVAsset} from OVV.
* @param {OVVAsset} ovvAsset An {@link OVVAsset} to remove
*/
this.removeAsset = function (ovvAsset) {
delete assets[ovvAsset.getId()];
};
/**
* Retrieves an {@link OVVAsset} based on its ID
* @param {String} The id of the element to retrieve
* @returns {OVVAsset|null} The element matching the given ID, or null if
* one could not be found
*/
this.getAssetById = function (id) {
return assets[id];
};
/**
* @returns {Object} Object an object containing all of the OVVAssets being tracked
*/
this.getAds = function () {
var copy = {};
for (var id in assets) {
if (assets.hasOwnProperty(id)) {
copy[id] = assets[id];
}
}
return copy;
};
/**
* Subscribe the {func} to the list of {events}. When getPreviousEvents is true all the stored events that were passed will be fired
* in a chronological order
* @param {events} array with all the event names to subscribe to
* @param {uid} asset identifier
* @param {func} a function to execute once the assert raise the event
* @param {getPreviousEvents} if true all buffered event will be triggered
*/
this.subscribe = function (events, uid, func, getPreviousEvents) {
if (getPreviousEvents) {
for (key in previousEvents[uid]) {
if (contains(previousEvents[uid][key].eventName, events)) {
runSafely(function () {
func(uid, previousEvents[uid][key]); // changed in vtag.js
});
}
}
}
for (key in events) {
if (!subscribers[events[key] + uid])
subscribers[events[key] + uid] = [];
subscribers[events[key] + uid].push({
Func: func
});
}
};
/**
* Publish {eventName} to all the subscribers. Also, storing the publish event in a buffered array is the capacity wasn't reached
* @param {eventName} name of the event to publish
* @param {uid} asset identifier
* @param {args} argument to send to the published function
*/
this.publish = function (eventName, uid, args) {
var eventArgs = {
eventName: eventName,
eventTime: getCurrentTime(),
ovvArgs: args
};
if (!previousEvents[uid]) {
previousEvents[uid] = [];
}
if (previousEvents[uid].length < PREVIOUS_EVENTS_CAPACITY) {
previousEvents[uid].push(eventArgs);
}
if (eventName && uid && subscribers[eventName + uid] instanceof Array) {
for (var i = 0; i < subscribers[eventName + uid].length; i++) {
var funcObject = subscribers[eventName + uid][i];
if (funcObject && funcObject.Func && typeof funcObject.Func === "function") {
runSafely(function () {
funcObject.Func(uid, eventArgs);
});
}
}
}
};
var getCurrentTime = function () {
"use strict";
if (Date.now) {
return Date.now();
}
return (new Date()).getTime();
};
var contains = function (item, list) {
for (var i = 0; i < list.length; i++) {
if (list[i] === item) {
return true;
}
}
return false;
};
var runSafely = function (action) {
try {
var ret = action();
return ret !== undefined ? ret : true;
} catch (e) {
return false;
}
};
}
/**
* A container for all the values that OpenVV collects.
* @class
* @constructor
*/
function OVVCheck() {
///////////////////////////////////////////////////////////////////////////
// PUBLIC ATTRIBUTES
///////////////////////////////////////////////////////////////////////////
/**
* The height of the viewport
* @type {Number}
*/
this.clientHeight = -1;
/**
* The width of the viewport
* @type {Number}
*/
this.clientWidth = -1;
/**
* A description of any error that occurred
* @type {String}
*/
this.error = '';
/**
* Whether the tab is focused or not (populated by ActionScript)
* @type {Boolean}
*/
this.focus = null;
/**
* The frame rate of the asset (populated by ActionScript)
* @type {Number}
*/
this.fps = -1;
/**
* A unique identifier of the asset
* @type {String}
*/
this.id = '';
/**
* Whether beacon checking is supported. Beacon support is defined by
* placing a "control beacon" SWF off screen, and verifying that it is
* throttled as expected.
* @type {Boolean}
*/
this.beaconsSupported = null;
/**
* Whether geometry checking is supported. Geometry support requires
* that the asset is not within an iframe.
* @type {Boolean}
*/
this.geometrySupported = null;
/**
* The viewability state measured by the geometry technique. Only populated
* when OVV.DEBUG is true.
* @type {String}
* @see {@link checkGeometry}
* @see {@link OVV#DEBUG}
*/
this.geometryViewabilityState = '';
/**
* The viewability state measured by the beacon technique. Only populated
* when OVV.DEBUG is true.
* @type {String}
* @see {@link OVVAsset#checkBeacons}
* @see {@link OVV#DEBUG}
*/
this.beaconViewabilityState = '';
/**
* The technique used to populate OVVCheck.viewabilityState. Will be either
* OVV.GEOMETRY when OVV is run in the root page, or OVV.BEACON when OVV is
* run in an iframe. When in debug mode, will always remain blank.
* @type {String}
* @see {@link OVV#GEOMETRY}
* @see {@link OVV#BEACON}
*/
this.technique = '';
/**
* When OVV is run in an iframe and the beacon technique is used, this array
* is populated with the states of each beacon, identified by their index.
* True means the beacon was viewable and false means the beacon was
* unviewable. Beacon 0 is the "control beacon" and should always be false.
* @type {Array.<Boolean>|null}
* @see {@link OVVAsset.CONTROL}
* @see {@link OVVAsset.CENTER}
* @see {@link OVVAsset.OUTER_TOP_LEFT}
* @see {@link OVVAsset.OUTER_TOP_RIGHT}
* @see {@link OVVAsset.OUTER_BOTTOM_LEFT}
* @see {@link OVVAsset.OUTER_BOTTOM_RIGHT}
* @see {@link OVVAsset.MIDDLE_TOP_LEFT}
* @see {@link OVVAsset.MIDDLE_TOP_RIGHT}
* @see {@link OVVAsset.MIDDLE_BOTTOM_LEFT}
* @see {@link OVVAsset.MIDDLE_BOTTOM_RIGHT}
* @see {@link OVVAsset.INNER_TOP_LEFT}
* @see {@link OVVAsset.INNER_TOP_RIGHT}
* @see {@link OVVAsset.INNER_BOTTOM_LEFT}
* @see {@link OVVAsset.INNER_BOTTOM_RIGHT}
*/
this.beacons = new Array();
/**
* Whether this asset is in an iframe.
* @type {Boolean}
* @see {@link OVV#IN_IFRAME}
* @see {@link OVV#DEBUG}
*/
this.inIframe = null;
/**
* The distance, in pixels, from the bottom of the asset to the bottom of
* the viewport
* @type {Number}
*/
this.objBottom = -1;
/**
* The distance, in pixels, from the left of the asset to the left of
* the viewport
* @type {Number}
*/
this.objLeft = -1;
/**
* The distance, in pixels, from the right of the asset to the right of
* the viewport
* @type {Number}
*/
this.objRight = -1;
/**
* The distance, in pixels, from the top of the asset to the top of
* the viewport
* @type {Number}
*/
this.objTop = -1;
/**
* The percentage of the player that is viewable within the viewport
* @type {Number}
*/
this.percentViewable = -1;
/**
* Set to {@link OVVCheck#VIEWABLE} when the player was at least 50%
* viewable. Set to OVVCheck when the player was less than 50% viewable.
* Set to {@link OVVCheck#UNMEASURABLE} when a determination could not be made.
* @type {String}
* @see {@link OVVCheck.UNMEASURABLE}
* @see {@link OVVCheck.VIEWABLE}
* @see {@link OVVCheck.UNVIEWABLE}
* @see {@link OVVCheck.NOT_READY}
*/
this.viewabilityState = '';
}
/**
* The value that {@link OVVCheck#viewabilityState} will be set to if OVV cannot
* determine whether the asset is at least 50% viewable.
*/
OVVCheck.UNMEASURABLE = 'unmeasurable';
/**
* The value that {@link OVVCheck#viewabilityState} will be set to if OVV
* determines that the asset is at least 50% viewable.
*/
OVVCheck.VIEWABLE = 'viewable';
/**
* The value that {@link OVVCheck#viewabilityState} will be set to if OVV
* determines that the asset is less than 50% viewable.
*/
OVVCheck.UNVIEWABLE = 'unviewable';
/**
* The value that {@link OVVCheck#viewabilityState} will be set to if the beacons
* are not ready to determine the viewability state
*/
OVVCheck.NOT_READY = 'not_ready';
/**
* The value that {@link OVVCheck#technique} will be set to if OVV
* uses the beacon technique to determine {@link OVVCheck#viewabilityState}
*/
OVVCheck.BEACON = 'beacon';
/**
* The value that {@link OVVCheck#technique} will be set to if OVV
* uses the geometry technique to determine {@link OVVCheck#viewabilityState}
*/
OVVCheck.GEOMETRY = 'geometry';
/**
* Represents an Asset which OVV is going to determine the viewability of
* @constructor
* @param {String} uid - The unique identifier of this asset
*/
function OVVAsset(uid, dependencies) {
///////////////////////////////////////////////////////////////////////////
// CONSTANTS
///////////////////////////////////////////////////////////////////////////
/**
* The total number of beacons being used
* @type {Number}
*/
var TOTAL_BEACONS = 13;
/**
* The value of the square root of 2. Computed here and saved for reuse
* later. Approximately 1.41.
* @type {Number}
*/
var SQRT_2 = Math.sqrt(2);
/**
* The index/identifier of the control beacon, which is placed off screen to
* test that throttling occurs.
* @type {Number}
*/
var CONTROL = 0;
/**
* The index/identifier of the center beacon, which is placed in the center
* of the player.
* @type {Number}
*/
var CENTER = 1;
/**
* The index/identifier of the beacon placed at the top left corner of the
* player.
* @type {Number}
*/
var OUTER_TOP_LEFT = 2;
/**
* The index/identifier of the beacon placed at the top right corner of the
* player.
* @type {Number}
*/
var OUTER_TOP_RIGHT = 3;
/**
* The index/identifier of the beacon placed at the bottom left corner of
* the player.
* @type {Number}
*/
var OUTER_BOTTOM_LEFT = 4;
/**
* The index/identifier of the beacon placed at the bottom right corner of
* the player.
* @type {Number}
*/
var OUTER_BOTTOM_RIGHT = 5;
/**
* The index/identifier of the beacon placed at the top left corner of the
* middle area. The middle area defines a region which is 50% of the total
* area of the player.
* @type {Number}
*/
var MIDDLE_TOP_LEFT = 6;
/**
* The index/identifier of the beacon placed at the top right corner of the
* middle area. The middle area defines a region which is 50% of the total
* area of the player.
* @type {Number}
*/
var MIDDLE_TOP_RIGHT = 7;
/**
* The index/identifier of the beacon placed at the bottom left corner of
* the middle area. The middle area defines a region which is 50% of the total
* area of the player.
* @type {Number}
*/
var MIDDLE_BOTTOM_LEFT = 8;
/**
* The index/identifier of the beacon placed at the bottom right corner of
* the middle area. The middle area defines a region which is 50% of the total
* area of the player.
* @type {Number}
*/
var MIDDLE_BOTTOM_RIGHT = 9;
/**
* The index/identifier of the beacon placed at the top left corner of
* the inner area. The inner area defines a region such that the area
* outside 2 sides of it are 50% of the player's total area.
* @type {Number}
*/
var INNER_TOP_LEFT = 10;
/**
* The index/identifier of the beacon placed at the top right corner of
* the inner area. The inner area defines a region such that the area
* outside 2 sides of it are 50% of the player's total area.
* @type {Number}
*/
var INNER_TOP_RIGHT = 11;
/**
* The index/identifier of the beacon placed at the bottom left corner of
* the inner area. The inner area defines a region such that the area
* outside 2 sides of it are 50% of the player's total area.
* @type {Number}
*/
var INNER_BOTTOM_LEFT = 12;
/**
* The index/identifier of the beacon placed at the bottom right corner of
* the inner area. The inner area defines a region such that the area
* outside 2 sides of it are 50% of the player's total area.
* @type {Number}
*/
var INNER_BOTTOM_RIGHT = 13;
///////////////////////////////////////////////////////////////////////////
// PRIVATE ATTRIBUTES
///////////////////////////////////////////////////////////////////////////
/**
* the id of the ad that this asset is associated with
* @type {!String}
*/
var id = uid;
/**
* The number of beacons that have checked in as being ready
* @type {Number}
*/
var beaconsStarted = 0;
/**
* The height and width of the beacons on the page. 1 for production, 20
* for {@link OVV#DEBUG} mode.
* @type {Number}
*/
var BEACON_SIZE = $ovv.DEBUG ? 20 : 1;
/**
* The last known location of the player on the page
* @type {ClientRect}
*/
var lastPlayerLocation;
/**
* The video player being measured
* @type {Element}
*/
var player;
var geometryViewabilityCalculator = dependencies.geometryViewabilityCalculator;
///////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS
///////////////////////////////////////////////////////////////////////////
/**
* <p>
* Returns an {@link OVVCheck} object populated with information gathered
* from the browser. The viewabilityState attribute is populated with
* either {@link OVVCheck.VIEWABLE}, {@link OVVCheck.UNVIEWABLE}, or {@link OVVCheck.UNMEASURABLE}
* as determined by either beacon technique when in a cross domain iframe, or the
* geometry technique otherwise.
* </p><p>
* The geometry technique compares the bounds of the viewport, taking
* scrolling into account, and the bounds of the player.
* </p><p>
* The beacon technique places a single beacon off-screen and several
* on top of the player. It then queries the state of the beacons on top
* of the player to determine how much of the player is viewable.
* </p>
* @returns {OVVCheck}
* @see {@link OVVCheck}
* @see {@link checkGeometry}
* @see {@link checkBeacons}
*/
this.checkViewability = function () {
var check = new OVVCheck();
check.id = id;
check.inIframe = $ovv.IN_IFRAME;
check.geometrySupported = $ovv.servingScenario !== $ovv.servingScenarioEnum.CrossDomainIframe;
check.focus = isInFocus();
if (!player) {
check.error = 'Player not found!';
return check;
}
// if we're in IE or FF and we're in an cross domain iframe, return unmeasurable
// We are able to measure for same domain iframe ('friendly iframe')
if (($ovv.browser.ID === $ovv.browserIDEnum.MSIE || $ovv.browser.ID === $ovv.browserIDEnum.Firefox) &&
check.geometrySupported === false) {
check.viewabilityState = OVVCheck.UNMEASURABLE;
if (!$ovv.DEBUG) {
return check;
}
}
// if we can use the geometry method, use it over the beacon method
if (check.geometrySupported) {
check.technique = OVVCheck.GEOMETRY;
checkGeometry(check, player);
check.viewabilityState = (check.percentViewable >= 50) ? OVVCheck.VIEWABLE : OVVCheck.UNVIEWABLE;
if ($ovv.DEBUG) {
// add an additional field when debugging
check.geometryViewabilityState = check.viewabilityState;
} else {
return check;
}
}
var controlBeacon = getBeacon(0);
// check to make sure the control beacon is found and its
// callback has been setup
if (controlBeacon && controlBeacon.isViewable) {
// the control beacon should always be off screen and not viewable,
// if that's not true, it can't be used
var controlBeaconVisible = isOnScreen(controlBeacon) && controlBeacon.isViewable();
check.beaconsSupported = !controlBeaconVisible;
} else {
// if the control beacon wasn't found or it isn't ready yet,
// then beacons can't be used for this check
check.beaconsSupported = false;
}
if (!beaconsReady()) {
check.technique = OVVCheck.BEACON;
check.viewabilityState = OVVCheck.NOT_READY;
} else if (check.beaconsSupported) { // if the control beacon checked out, and all the beacons are ready proceed
check.technique = OVVCheck.BEACON;
var viewable = checkBeacons(check);
// certain scenarios return null when the beacons can't guarantee
// that the player is > 50% viewable, so it's deemed unmeasurable
if (viewable === null) {
check.viewabilityState = OVVCheck.UNMEASURABLE;
// add this informational field when debugging
if ($ovv.DEBUG) {
check.beaconViewabilityState = OVVCheck.UNMEASURABLE;
}
} else {
check.viewabilityState = viewable ? OVVCheck.VIEWABLE : OVVCheck.UNVIEWABLE;
// add this informational field when debugging
if ($ovv.DEBUG) {
check.beaconViewabilityState = viewable ? OVVCheck.VIEWABLE : OVVCheck.UNVIEWABLE;
}
}
} else {
check.viewabilityState = OVVCheck.UNMEASURABLE;
}
// in debug mode, reconcile the viewability states from both techniques
if ($ovv.DEBUG) {
// revert the technique to blank during debug, since both were used
check.technique = '';
if (check.geometryViewabilityState === null && check.beaconViewabilityState === null) {
check.viewabilityState = OVVCheck.UNMEASURABLE;
} else {
var beaconViewable = (check.beaconViewabilityState === OVVCheck.VIEWABLE);
var geometryViewable = (check.geometryViewabilityState === OVVCheck.VIEWABLE);
check.viewabilityState = (beaconViewable || geometryViewable) ? OVVCheck.VIEWABLE : OVVCheck.UNVIEWABLE;
}
}
return check;
};
/**
* Called by each beacon to signify that it's ready to measure
* @param {Number} index The index identifier of the beacon
*/
this.beaconStarted = function (index) {
if ($ovv.DEBUG) {
getBeacon(index).debug();
}
if (index === 0) {
return;
}
beaconsStarted++;
if (beaconsReady()) {
player.onJsReady();
}
};
/**
* Frees up resources created and used by the asset.
*/
this.dispose = function () {
for (var index = 1; index <= TOTAL_BEACONS; index++) {
var container = getBeaconContainer(index);
if (container) {
delete beaconsStarted[index];
container.parentElement.removeChild(container);
}
}
clearInterval( window.$ovv.positionInterval );
window.$ovv.removeAsset(this);
};
/**
* @returns {String} The randomly generated ID of this asset
*/
this.getId = function () {
return id;
};
/**
* @returns {Object} The associated asset's player
*/
this.getPlayer = function () {
return player;
};
///////////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS
///////////////////////////////////////////////////////////////////////////
/**
* Performs the geometry technique to determine viewability. First gathers
* information on the viewport and on the player. The compares the two to
* determine what percentage, if any, of the player is within the bounds
* of the viewport.
* @param {OVVCheck} check The OVVCheck object to populate
* @param {Element} player The HTML Element to measure
*/
var checkGeometry = function (check, player) {
var viewabilityResult = geometryViewabilityCalculator.getViewabilityState(player, window);
if (!viewabilityResult.error) {
check.clientWidth = viewabilityResult.clientWidth;
check.clientHeight = viewabilityResult.clientHeight;
check.percentViewable = viewabilityResult.percentViewable;
check.objTop = viewabilityResult.objTop;
check.objBottom = viewabilityResult.objBottom;
check.objLeft = viewabilityResult.objLeft;
check.objRight = viewabilityResult.objRight;
}
return viewabilityResult;
};
/**
* Performs the beacon technique. Queries the state of each beacon and
* attempts to make a determination of whether at least 50% of the player
* is within the viewport.
* @param {OVVCheck} check The OVVCheck object to populate
*/
var checkBeacons = function (check) {
if (!beaconsReady()) {
return null;
}
var beaconsVisible = 0;
var outerCornersVisible = 0;
var middleCornersVisible = 0;
var innerCornersVisible = 0;
check.beacons = new Array(TOTAL_BEACONS);
//Get player dimensions:
var objRect = player.getClientRects()[0];
check.objTop = objRect.top;
check.objBottom = objRect.bottom;
check.objLeft = objRect.left;
check.objRight = objRect.right;
for (var index = 0; index <= TOTAL_BEACONS; index++) {
// the control beacon is only involved in determining if the
// browser supports beacon measurement, so move on
if (index === 0) {
continue;
}
var beacon = getBeacon(index);
var isViewable = beacon.isViewable();
var onScreen = isOnScreen(beacon);
check.beacons[index] = isViewable && onScreen;
if (isViewable) {
beaconsVisible++;
switch (index) {
case OUTER_TOP_LEFT:
case OUTER_TOP_RIGHT:
case OUTER_BOTTOM_LEFT:
case OUTER_BOTTOM_RIGHT:
outerCornersVisible++;
break;
case MIDDLE_TOP_LEFT:
case MIDDLE_TOP_RIGHT:
case MIDDLE_BOTTOM_LEFT:
case MIDDLE_BOTTOM_RIGHT:
middleCornersVisible++;
break;
case INNER_TOP_LEFT:
case INNER_TOP_RIGHT:
case INNER_BOTTOM_LEFT:
case INNER_BOTTOM_RIGHT:
innerCornersVisible++;
break;
}
}
}
// when all points are visible
if (beaconsVisible === TOTAL_BEACONS) {
return true;
}
var beacons = check.beacons;
// when the center of the player is visible
if ((beacons[CENTER] === true) &&
// and 2 adjacent outside corners are visible
((beacons[OUTER_TOP_LEFT] === true && beacons[OUTER_TOP_RIGHT] == true) ||
(beacons[OUTER_TOP_LEFT] === true && beacons[OUTER_BOTTOM_LEFT] == true) ||
(beacons[OUTER_TOP_RIGHT] === true && beacons[OUTER_BOTTOM_RIGHT] == true) ||
(beacons[OUTER_BOTTOM_LEFT] === true && beacons[OUTER_BOTTOM_RIGHT] == true))
) {
return true;
}
// when any 3 of the inner corners are visible
if (beacons[CENTER] === false && innerCornersVisible >= 3) {
return true;
}
// when the center and all of the middle corners are visible
if (beacons[CENTER] === true && middleCornersVisible == 4) {
return true;
}
// // when top left and bottom right corners are visible
if ((beacons[OUTER_TOP_LEFT] && beacons[OUTER_BOTTOM_RIGHT]) &&
// and any of their diagonals are covered
(!beacons[MIDDLE_TOP_LEFT] || ![INNER_TOP_LEFT] || !beacons[CENTER] || beacons[INNER_BOTTOM_RIGHT] || beacons[MIDDLE_BOTTOM_RIGHT])
) {
return null;
}
// when bottom left and top right corners are visible
if ((beacons[OUTER_BOTTOM_LEFT] && beacons[OUTER_TOP_RIGHT]) &&
// and any of their diagonals are covered
(!beacons[MIDDLE_BOTTOM_LEFT] || !beacons[INNER_BOTTOM_LEFT] || !beacons[CENTER] || !beacons[INNER_TOP_RIGHT] || !beacons[MIDDLE_TOP_RIGHT])
) {
return null;
}
return false;
};
/**
* @returns {Boolean} Whether all beacons have checked in
*/
var beaconsReady = function () {
if (!player) {
return false;
}
return beaconsStarted === TOTAL_BEACONS;
};
/**
* Creates the beacon SWFs and adds them to the DOM
* @param {String} url The URL of the beacon SWFs
* @see {@link positionBeacons}
*/
var createBeacons = function (url) {
// double checking that our URL was actually set to something
// (http://cdn.altitude-arena.com/flash/OVVBeacon.swf is obfuscated here to prevent it from being
// String substituted by ActionScript)
if (url === '' || url === ('BEACON' + '_SWF_' + 'URL')) {
return;
}
for (var index = 0; index <= TOTAL_BEACONS; index++) {
var swfContainer = document.createElement('DIV');
swfContainer.id = 'OVVBeaconContainer_' + index + '_' + id;
swfContainer.style.position = 'absolute';
swfContainer.style.zIndex = $ovv.DEBUG ? 99999 : -99999;
var html =
'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="' + BEACON_SIZE + '" height="' + BEACON_SIZE + '">' +
'<param name="movie" value="' + url + '" />' +
'<param name="quality" value="low" />' +
'<param name="flashvars" value="id=' + id + '&index=' + index + '" />' +
'<param name="bgcolor" value="#ffffff" />' +
'<param name="wmode" value="transparent" />' +
'<param name="allowScriptAccess" value="always" />' +
'<param name="allowFullScreen" value="false" />' +
'<!--[if !IE]>-->' +
'<object id="OVVBeacon_' + index + '_' + id + '" type="application/x-shockwave-flash" data="' + url + '" width="' + BEACON_SIZE + '" height="' + BEACON_SIZE + '">' +
'<param name="quality" value="low" />' +
'<param name="flashvars" value="id=' + id + '&index=' + index + '" />' +
'<param name="bgcolor" value="#ff0000" />' +
'<param name="wmode" value="transparent" />' +
'<param name="allowScriptAccess" value="always" />' +
'<param name="allowFullScreen" value="false" />' +
'<!--<![endif]-->' +
'</object>';
swfContainer.innerHTML = html;
document.body.insertBefore(swfContainer, document.body.firstChild);
}
// move the beacons to their initial position
positionBeacons.bind(this)();
// it takes ~500ms for beacons to know if they've been moved off
// screen, so they're repositioned at this interval so they'll be
// ready for the next check
this.positionInterval = setInterval(positionBeacons.bind(this), 500);
};
/**
* Repositions the beacon SWFs on top of the player
*/
var positionBeacons = function () {
if (!beaconsReady()) {
return;
}
var playerLocation = player.getClientRects()[0];
// when we don't have an initial position, or the position hasn't changed
if (lastPlayerLocation && (lastPlayerLocation.left === playerLocation.left && lastPlayerLocation.right === playerLocation.right && lastPlayerLocation.top === playerLocation.top && lastPlayerLocation.bottom === playerLocation.bottom)) {
// no need to update positions
return;
}
// save for next time
lastPlayerLocation = playerLocation;
var playerWidth = playerLocation.right - playerLocation.left;
var playerHeight = playerLocation.bottom - playerLocation.top;
var innerWidth = playerWidth / (1 + SQRT_2);
var innerHeight = playerHeight / (1 + SQRT_2);
var middleWidth = playerWidth / SQRT_2;
var middleHeight = playerHeight / SQRT_2;
for (var index = 0; index <= TOTAL_BEACONS; index++) {
var left = playerLocation.left + document.body.scrollLeft;
var top = playerLocation.top + document.body.scrollTop;
switch (index) {
case CONTROL:
left = -100000;
top = -100000;
break;
case CENTER:
left += (playerWidth - BEACON_SIZE) / 2;
top += (playerHeight - BEACON_SIZE) / 2;
break;
case OUTER_TOP_LEFT:
// nothing to do, already at default position
break;
case OUTER_TOP_RIGHT:
left += playerWidth - BEACON_SIZE;
break;
case OUTER_BOTTOM_LEFT:
top += playerHeight - BEACON_SIZE;
break;
case OUTER_BOTTOM_RIGHT:
left += playerWidth - BEACON_SIZE;
top += playerHeight - BEACON_SIZE;
break;
case MIDDLE_TOP_LEFT:
left += (playerWidth - middleWidth) / 2;
top += (playerHeight - middleHeight) / 2;
break;
case MIDDLE_TOP_RIGHT:
left += ((playerWidth - middleWidth) / 2) + middleWidth;
top += (playerHeight - middleHeight) / 2;
break;
case MIDDLE_BOTTOM_LEFT:
left += (playerWidth - middleWidth) / 2;
top += ((playerHeight - middleHeight) / 2) + middleHeight;
break;
case MIDDLE_BOTTOM_RIGHT:
left += ((playerWidth - middleWidth) / 2) + middleWidth;
top += ((playerHeight - middleHeight) / 2) + middleHeight;
break;
case INNER_TOP_LEFT:
left += (playerWidth - innerWidth) / 2;
top += (playerHeight - innerHeight) / 2;
break;
case INNER_TOP_RIGHT:
left += ((playerWidth - innerWidth) / 2) + innerWidth;
top += (playerHeight - innerHeight) / 2;
break;
case INNER_BOTTOM_LEFT:
left += (playerWidth - innerWidth) / 2;
top += ((playerHeight - innerHeight) / 2) + innerHeight;
break;
case INNER_BOTTOM_RIGHT:
left += ((playerWidth - innerWidth) / 2) + innerWidth;
top += ((playerHeight - innerHeight) / 2) + innerHeight;
break;
}
// center the middle and inner beacons on their intended point
if (index >= MIDDLE_TOP_LEFT) {
left -= (BEACON_SIZE / 2);
top -= (BEACON_SIZE / 2);
}
var swfContainer = getBeaconContainer(index);
swfContainer.style.left = left + 'px';
swfContainer.style.top = top + 'px';
}
};
/**
* Determines whether a DOM element is within the bounds of the viewport
* @param {Element} element An HTML Element
* @returns {Boolean} Whether the parameter is at least partially within
* the browser's viewport
*/
var isOnScreen = function (element) {
if (!element) {
return false;
}
var screenWidth = Math.max(document.body.clientWidth, window.innerWidth);
var screenHeight = Math.max(document.body.clientHeight, window.innerHeight);
var objRect = element.getClientRects()[0];
return (objRect.top < screenHeight && objRect.bottom > 0 && objRect.left < screenWidth && objRect.right > 0);
};
/**
* @returns {Element|null} A beacon by its index
*/
var getBeacon = function (index) {
return document.getElementById('OVVBeacon_' + index + '_' + id);
};
/**
* @returns {Element|null} A beacon container by its index
*/
var getBeaconContainer = function (index) {
return document.getElementById('OVVBeaconContainer_' + index + '_' + id);
};
/**
* Finds the video player associated with this asset by searching through
* each EMBED and OBJECT element on the page, testing to see if it has the
* randomly generated callback signature.
* @returns {Element|null} The video player being measured
*/
var findPlayer = function () {
var embeds = document.getElementsByTagName('embed');
for (var i = 0; i < embeds.length; i++) {
if (embeds[i][id]) {
return embeds[i];
}
}
var objs = document.getElementsByTagName('object');
for (var i = 0; i < objs.length; i++) {
if (objs[i][id]) {
return objs[i];
}
}
return null;
};
var isInFocus = function () {
var inFocus = true;
if (typeof document.hidden !== 'undefined') {
inFocus = window.document.hidden ? false : true;
} else if (document.hasFocus) {
inFocus = document.hasFocus();
}
if ($ovv.IN_IFRAME === false && inFocus === true && document.hasFocus) {
inFocus = document.hasFocus();
}
return inFocus;
};
player = findPlayer();
// only use the beacons if we're in an iframe, but go ahead and add them
// during debug mode
if ($ovv.IN_IFRAME || $ovv.DEBUG) {
// 'http://cdn.altitude-arena.com/flash/OVVBeacon.swf' is String substituted from ActionScript
createBeacons.bind(this)('http://cdn.altitude-arena.com/flash/OVVBeacon.swf');
} else if (player && player.onJsReady) {
// since we don't have to wait for beacons to be ready, we're ready now
setTimeout( function(){ player.onJsReady() }, 5 ); //Use a tiny timeout to keep this async like the beacons
}
}
function OVVGeometryViewabilityCalculator() {
this.getViewabilityState = function (element, contextWindow) {
var viewPortSize = getViewPortSize();
if (viewPortSize.height == Infinity || viewPortSize.width == Infinity) {
return { error: "Failed to determine viewport"};
}
var assetSize = getAssetVisibleDimension(element, contextWindow);
var viewablePercentage = getAssetViewablePercentage(assetSize, viewPortSize);
//Get player dimensions:
var assetRect = element.getBoundingClientRect();
return {
clientWidth: viewPortSize.width,
clientHeight: viewPortSize.height,
objTop: assetRect.top,
objBottom: assetRect.bottom,
objLeft: assetRect.left,
objRight: assetRect.right,
percentViewable: viewablePercentage
};
};
///////////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS
///////////////////////////////////////////////////////////////////////////
/**
* Get the viewport size by taking the smallest dimensions
*/
var getViewPortSize = function () {
var viewPortSize = {
width: Infinity,
height: Infinity
};
var contextWindow = window.top;
//document.body - Handling case where viewport is represented by documentBody
//.width
if (!isNaN(contextWindow.document.body.clientWidth) && contextWindow.document.body.clientWidth > 0) {
viewPortSize.width = contextWindow.document.body.clientWidth;
}
//.height
if (!isNaN(contextWindow.document.body.clientHeight) && contextWindow.document.body.clientHeight > 0) {
viewPortSize.height = contextWindow.document.body.clientHeight;
}
//document.documentElement - Handling case where viewport is represented by documentElement
//.width
if (!!contextWindow.document.documentElement && !!contextWindow.document.documentElement.clientWidth && !isNaN(contextWindow.document.documentElement.clientWidth)) {
viewPortSize.width = contextWindow.document.documentElement.clientWidth;
}
//.height
if (!!contextWindow.document.documentElement && !!contextWindow.document.documentElement.clientHeight && !isNaN(contextWindow.document.documentElement.clientHeight)) {
viewPortSize.height = contextWindow.document.documentElement.clientHeight;
}
//window.innerWidth/Height - Handling case where viewport is represented by window.innerH/W
//.innerWidth
if (!!contextWindow.innerWidth && !isNaN(contextWindow.innerWidth)) {
viewPortSize.width = Math.min(viewPortSize.width, contextWindow.innerWidth);
}
//.innerHeight
if (!!contextWindow.innerHeight && !isNaN(contextWindow.innerHeight)) {
viewPortSize.height = Math.min(viewPortSize.height, contextWindow.innerHeight);
}
return viewPortSize;
};
/**
* Recursive function that return the asset (element) visible dimension
* @param {element} The element to get his visible dimension
* @param {contextWindow} The relative window
*/
var getAssetVisibleDimension = function (element, contextWindow) {
var currWindow = contextWindow;
//Set parent window for recursive call
var parentWindow = contextWindow.parent;
var resultDimension = { width: 0, height: 0, left: 0, right: 0, top: 0, bottom: 0 };
if (element) {
var elementRect = getPositionRelativeToViewPort(element, contextWindow);
elementRect.width = elementRect.right - elementRect.left;
elementRect.height = elementRect.bottom - elementRect.top;
resultDimension = elementRect;
//Calculate the relative element dimension if we clime to a parent window
if (currWindow != parentWindow) {
//Recursive call to get the relative element dimension from the parent window
var parentDimension = getAssetVisibleDimension(currWindow.frameElement, parentWindow);
//The asset is partially below the parent window (asset bottom is below the visible window)
if (parentDimension.bottom < resultDimension.bottom) {
if (parentDimension.bottom < resultDimension.top) {
//The entire asset is below the parent window
resultDimension.top = parentDimension.bottom;
}
//Set the asset bottom to be the visible part
resultDimension.bottom = parentDimension.bottom;
}
//The asset is partially right to the parent window
if (parentDimension.right < resultDimension.right) {
if (parentDimension.right < resultDimension.left) {
//The entire asset is to the right of the parent window
resultDimension.left = parentDimension.right;
}
//Set the asset right to be the visible
resultDimension.right = parentDimension.right;
}
resultDimension.width = resultDimension.right - resultDimension.left;
resultDimension.height = resultDimension.bottom - resultDimension.top;
}
}
return resultDimension;
};
var getPositionRelativeToViewPort = function (element, contextWindow) {
var currWindow = contextWindow;
var parentWindow = contextWindow.parent;
var resultPosition = { left: 0, right: 0, top: 0, bottom: 0 };
if (element) {
var elementRect = element.getBoundingClientRect();
if (currWindow != parentWindow)
resultPosition = getPositionRelativeToViewPort(currWindow.frameElement, parentWindow);
resultPosition = {
left: elementRect.left + resultPosition.left,
right: elementRect.right + resultPosition.left,
top: elementRect.top + resultPosition.top,
bottom: elementRect.bottom + resultPosition.top
};
}
return resultPosition;
};
/**
* Calculate asset viewable percentage given the asset size and the viewport
* @param {effectiveAssetRect} the asset viewable rect; effectiveAssetRect = {left :, top :,bottom:,right:,}
* @param {viewPortSize} the browser viewport size;
*/
var getAssetViewablePercentage = function (effectiveAssetRect, viewPortSize) {
// holds the asset viewable surface
var assetVisiableHeight = 0, assetVisiableWidth = 0;
var asset = {
width: effectiveAssetRect.right - effectiveAssetRect.left,
height: effectiveAssetRect.bottom - effectiveAssetRect.top
};
// Ad is 100% out off-view
if (effectiveAssetRect.bottom < 0 // the entire asset is above the viewport
|| effectiveAssetRect.right < 0 // the entire asset is left to the viewport
|| effectiveAssetRect.top > viewPortSize.height // the entire asset bellow the viewport
|| effectiveAssetRect.left > viewPortSize.width // the entire asset is right to the viewport
|| asset.width <= 0 // the asset width is zero
|| asset.height <= 0) // the asset height is zero
{
return 0;
}
// ---- Handle asset visible height ----
// the asset is partially above the viewport
if (effectiveAssetRect.top < 0) {
// take the visible part
assetVisiableHeight = asset.height + effectiveAssetRect.top;
//if the asset height is larger then the viewport height, set the asset height to be the viewport height
if (assetVisiableHeight > viewPortSize.height) {
assetVisiableHeight = viewPortSize.height;
}
}
// the asset is partially below the viewport
else if (effectiveAssetRect.top + asset.height > viewPortSize.height) {
// take the visible part
assetVisiableHeight = viewPortSize.height - effectiveAssetRect.top;
}
// the asset is in the viewport
else {
assetVisiableHeight = asset.height;
}
// ---- Handle asset visible width ----
// the asset is partially left to the viewport
if (effectiveAssetRect.left < 0) {
// take the visible part
assetVisiableWidth = asset.width + effectiveAssetRect.left;
//if the asset width is larger then the viewport width, set the asset width to be the viewport width
if (assetVisiableWidth > viewPortSize.width) {
assetVisiableWidth = viewPortSize.width;
}
}
// the asset is partially right to the viewport
else if (effectiveAssetRect.left + asset.width > viewPortSize.width) {
// take the visible part
assetVisiableWidth = viewPortSize.width - effectiveAssetRect.left;
}
// the asset is in the viewport
else {
assetVisiableWidth = asset.width;
}
// Divied the visible asset area by the full asset area to the the visible percentage
return Math.round((((assetVisiableWidth * assetVisiableHeight)) / (asset.width * asset.height)) * 100);
};
}
// initialize the OVV object if it doesn't exist
window.$ovv = window.$ovv || new OVV();
// 'ovv218877002' is String substituted from AS
window.$ovv.addAsset(new OVVAsset('ovv218877002', { geometryViewabilityCalculator: new OVVGeometryViewabilityCalculator() }));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment