Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added AdPod support for VAST and VPAID #85

Merged
merged 1 commit into from
Feb 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This SDK supports:
- VAST versions 2.0, 3.0 and 4.0+ up to 4.2 (included) - Complies with the [VAST 4.2 specification](https://iabtechlab.com/wp-content/uploads/2019/06/VAST_4.2_final_june26.pdf) provided by the [Interactive Advertising Bureau (IAB)](https://www.iab.com/).
- Inline Linear
- Wrapper
- AdPod
- Tracker for VAST tracking events
- Media Types (Assets):
- `video/mp4; codecs=“avc1.42E01E, mp4a.40.2”`
Expand Down
2 changes: 1 addition & 1 deletion dist/ads-manager.es.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/ads-manager.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ <h1>Ads Manager</h1>
<br/>
<br/>
<h3>VAST Tag URL/XML Inspector (VAST, VPAID):</h3>
<textarea id="vast-url-input">https://v.adserve.tv/pg/vast-vpaid.xml</textarea>
<textarea id="vast-url-input">https://eyevinn.adtest.eyevinn.technology/api/v1/vast?dur=30</textarea>
<!-- https://v.adserve.tv/pg/vast-vpaid.xml -->
<button id="test-ad-button" class="primary-button">Test Ad</button>
<button id="pause-ad-button" class="ad-button">Pause Ad</button>
<!--<button id="resume-ad-button" class="ad-button">Resume Ad</button>-->
Expand Down
4 changes: 2 additions & 2 deletions public/js/ads-manager.js

Large diffs are not rendered by default.

204 changes: 124 additions & 80 deletions src/ads-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ const AdsManager = function(adContainer) {

this._ad = null;
this._adPod = null;
this._isAdPod = false;

this._creative = null;
this._mediaFiles = null;
this._mediaFileIndex = 0;
Expand Down Expand Up @@ -281,6 +283,7 @@ AdsManager.prototype.onAdsManagerLoaded = function() {
this._callEvent(this.EVENTS.AdsManagerLoaded);
};
AdsManager.prototype.onAdLoaded = function() {
this._hasLoaded = true;
this.stopVASTMediaLoadTimeout();
if (this.EVENTS.AdLoaded in this._eventCallbacks) {
this._eventCallbacks[this.EVENTS.AdLoaded](new Ad(this._creative));
Expand Down Expand Up @@ -314,9 +317,39 @@ AdsManager.prototype.onAdStopped = function() {
if(!this._hasStarted) {
this.onAdError(this.ERRORS.VPAID_CREATIVE_ERROR);
} else {
this._callEvent(this.EVENTS.AdStopped);
// abort the ad, unsubscribe and reset to a default state
this._abort();
if(this._isAdPod && this._adPod.length != 0) {

this._nextQuartileIndex = 0;
this._hasImpression = false;

// Removes ad assets loaded at runtime that need to be properly removed at the time of ad completion
// and stops the ad and all tracking.
getTopWindow().removeEventListener('message', this._handleLoadCreativeMessage);

if(this._isVPAID) {
// Unsubscribe for VPAID events
this.removeCallbacksForCreative(this._creativeEventCallbacks);
this.removeCreativeAsset();
this._isVPAID = false;
}

// Remove handlers from slot and videoSlot
this._removeHandlers();
// Pause video slot, and remove src
this._videoSlot.pause();
this._videoSlot.removeAttribute('src'); // empty source
this._videoSlot.load();

setTimeout(() => {
this._nextAd();
},75);

} else {
console.log('is adpod', this._isAdPod);
this._callEvent(this.EVENTS.AdStopped);
// abort the ad, unsubscribe and reset to a default state
this._abort();
}
}
};
AdsManager.prototype.onAdSkipped = function() {
Expand All @@ -328,6 +361,7 @@ AdsManager.prototype.onAdVolumeChange = function() {
this._callEvent(this.EVENTS.AdVolumeChange);
};
AdsManager.prototype.onAdImpression = function() {
console.log('is vpaid', this._isVPAID);
if(this._isVPAID && this._vpaidCreative && this._vastTracker) {
if (!this._hasImpression) {
// Check duration
Expand Down Expand Up @@ -425,6 +459,8 @@ AdsManager.prototype.processVASTResponse = function(res) {

if(ads.length > 1) {
// Ad pod
this._isAdPod = true;
console.log('ads', ads);
// Filter by sequence
this._adPod = ads.sort(function(a, b) {
const aSequence = a.sequence;
Expand All @@ -439,62 +475,15 @@ AdsManager.prototype.processVASTResponse = function(res) {
return (aSequence < bSequence) ? -1 : (aSequence > bSequence) ? 1 : 0;
});

this._ad = ads[0];
// Shift from adPod array
this._ad = this._adPod.shift();
} else {
// Ad
this._ad = ads[0];
}

if(this._ad) {

// Filter linear creatives, get first
this._creative = ads[0].creatives.filter(creative => creative.type === 'linear')[0];
// Check if creative has media files
if(this._creative) {

if(this._creative.mediaFiles.length != 0) {
// Filter and check media files for mime type canPlay and if VPAID or not
this._mediaFiles = this._creative.mediaFiles.filter(mediaFile => {
// mime types -> mp4, webm, ogg, 3gp
if(this.canPlayVideoType(mediaFile.mimeType)) {
return mediaFile;
} else if(mediaFile.mimeType === 'application/javascript') {
// apiFramework -> mime type -> application/javascript
return mediaFile;
}
});//[0]; // take the first one

// Sort media files by size
this._mediaFiles.sort(function(a, b) {
const aHeight = a.height;
const bHeight = b.height;
return (aHeight < bHeight) ? -1 : (aHeight > bHeight) ? 1 : 0;
});

if(this._mediaFiles && this._mediaFiles.length != 0) {
// TODO: move after adLoaded
// Init VAST Tracker for tracking events
this._vastTracker = new VASTTracker(null, this._ad, this._creative);
this._vastTracker.load();

// If not VPAID dispatch AdsManagerLoaded event -> ad is ready for init
this.onAdsManagerLoaded();
} else {
// Linear assets were found in the VASt ad response, but none of them match the video player's capabilities.
this.onAdError(this.ERRORS.VAST_LINEAR_ASSET_MISMATCH);
}

} else {
// No assets were found in the VAST ad response.
this.onAdError(this.ERRORS.VAST_ASSET_NOT_FOUND);
}
} else {
// TODO:
// Non Linear
console.log('non linear');
}

}
// Process ad
this._processAd();

} else {
// The VAST response document is empty.
Expand Down Expand Up @@ -751,6 +740,18 @@ AdsManager.prototype.removeCreativeAsset = function() {
child !== this._videoSlot ? this._slot.removeChild(child) : null
});
};
AdsManager.prototype._removeHandlers = function() {
// Remove event listeners from slot
this._slot.removeEventListener('click', this._handleSlotClick);

// Remove event listeners from video slot
this._videoSlot.removeEventListener('error', this._handleVideoSlotError, false);
this._videoSlot.removeEventListener('canplay', this._handleVideoSlotCanPlay);
this._videoSlot.removeEventListener('volumechange', this._handleVideoSlotVolumeChange);
this._videoSlot.removeEventListener('timeupdate', this._handleVideoSlotTimeUpdate, true);
this._videoSlot.removeEventListener('loadedmetadata', this._handleVideoSlotLoadedMetaData);
this._videoSlot.removeEventListener('ended', this._handleVideoSlotEnded);
};
AdsManager.prototype._abort = function() {
// Abort
this.abort();
Expand Down Expand Up @@ -815,7 +816,65 @@ AdsManager.prototype.handleVideoSlotEnded = function() {
this.onAdStopped();
//}, 75);
};
AdsManager.prototype.init = function(width, height, viewMode) {
AdsManager.prototype._processAd = function(isNext = false) {

// Filter linear creatives, get first
this._creative = this._ad.creatives.filter(creative => creative.type === 'linear')[0];
// Check if creative has media files
if(this._creative) {

if(this._creative.mediaFiles.length != 0) {
// Filter and check media files for mime type canPlay and if VPAID or not
this._mediaFiles = this._creative.mediaFiles.filter(mediaFile => {
// mime types -> mp4, webm, ogg, 3gp
if(this.canPlayVideoType(mediaFile.mimeType)) {
return mediaFile;
} else if(mediaFile.mimeType === 'application/javascript') {
// apiFramework -> mime type -> application/javascript
return mediaFile;
}
});//[0]; // take the first one

// Sort media files by size
this._mediaFiles.sort(function(a, b) {
const aHeight = a.height;
const bHeight = b.height;
return (aHeight < bHeight) ? -1 : (aHeight > bHeight) ? 1 : 0;
});

if(this._mediaFiles && this._mediaFiles.length != 0) {
// Initialize VASTTracker for tracking events
this._vastTracker = new VASTTracker(null, this._ad, this._creative);
this._vastTracker.load();

if(!isNext) {
// If not VPAID dispatch AdsManagerLoaded event -> ad is ready for init
this.onAdsManagerLoaded();
} else {
this.init(this._attributes.width, this._attributes.height, this._attributes.viewMode, isNext);
}
} else {
// Linear assets were found in the VAST ad response, but none of them match the video player's capabilities.
this.onAdError(this.ERRORS.VAST_LINEAR_ASSET_MISMATCH);
}

} else {
// No assets were found in the VAST ad response.
this.onAdError(this.ERRORS.VAST_ASSET_NOT_FOUND);
}
} else {
// Non Linear
console.log('non linear');
this.onAdError('non linear');
}
};
AdsManager.prototype._nextAd = function() {
// Shift next ad
this._ad = this._adPod.shift();
// Process ad
this._processAd(true);
};
AdsManager.prototype.init = function(width, height, viewMode, isNext = false) {

console.log('init....');

Expand All @@ -825,9 +884,11 @@ AdsManager.prototype.init = function(width, height, viewMode) {

if(this.isCreativeExists()) {

if(this._options.muted) {
this._videoSlot.muted = true;
this._videoSlot.volume = 0;
if(!isNext) {
if (this._options.muted) {
this._videoSlot.muted = true;
this._videoSlot.volume = 0;
}
}

// Find the best resolution for mediaFile
Expand Down Expand Up @@ -855,7 +916,6 @@ AdsManager.prototype.init = function(width, height, viewMode) {
// Resize slot
this.resizeSlot(this._attributes.width, this._attributes.height);

console.log('remove and add events....');
this._videoSlot.addEventListener('error', this._handleVideoSlotError, false);

if(this._isVPAID) {
Expand Down Expand Up @@ -1064,21 +1124,16 @@ AdsManager.prototype.abort = function() {
this._hasStarted = false;

this._ad = null;
this._adPod = null;
this._isAdPod = false;

this._creative = null;
this._mediaFile = null;
this._vpaidCreative = null;
this._vastTracker = null;

// Remove event listeners from slot
this._slot.removeEventListener('click', this._handleSlotClick);

// Remove event listeners from video slot
this._videoSlot.removeEventListener('error', this._handleVideoSlotError, false);
this._videoSlot.removeEventListener('canplay', this._handleVideoSlotCanPlay);
this._videoSlot.removeEventListener('volumechange', this._handleVideoSlotVolumeChange);
this._videoSlot.removeEventListener('timeupdate', this._handleVideoSlotTimeUpdate, true);
this._videoSlot.removeEventListener('loadedmetadata', this._handleVideoSlotLoadedMetaData);
this._videoSlot.removeEventListener('ended', this._handleVideoSlotEnded);
// Remove handlers from slot and videoSlot
this._removeHandlers();

// Pause video slot, and remove src
this._videoSlot.pause();
Expand All @@ -1088,17 +1143,6 @@ AdsManager.prototype.abort = function() {
// Hide slot
this.hideSlot();


/*
// Remove slot
this.removeSlot();

if(reCreateSlot) {
// Re-create slot
this.createSlot();
}
*/

};
AdsManager.prototype.destroy = function() {

Expand Down