video-ad-sdk
Version:
VAST/VPAID SDK that allows video ads to be played on top of any player
228 lines (227 loc) • 8.03 kB
JavaScript
var _a;
import { linearEvents, ErrorCode, isVastErrorCode } from '../tracker';
import { getSkipOffset } from '../vastSelectors';
import { findBestMedia } from './helpers/media/findBestMedia';
import { once } from './helpers/dom/once';
import { setupMetricHandlers } from './helpers/metrics/setupMetricHandlers';
import { updateMedia } from './helpers/media/updateMedia';
import { AdUnitError } from './helpers/adUnitError';
import { VideoAdUnit, _protected } from './VideoAdUnit';
const { complete, error: errorEvent, skip } = linearEvents;
const _private = Symbol('_private');
/**
* This class provides everything necessary to run a Vast ad.
*/
export class VastAdUnit extends VideoAdUnit {
/**
* Creates a {@link VastAdUnit}.
*
* @param vastChain The {@link VastChain} with all the {@link VastResponse}
* @param videoAdContainer - container instance to place the ad
* @param options Options Map
*/
constructor(vastChain, videoAdContainer, options = {}) {
super(vastChain, videoAdContainer, options);
this[_a] = {
handleMetric: (event, data) => {
var _b;
switch (event) {
case complete: {
this[_protected].finish();
break;
}
case errorEvent: {
if (data instanceof Error) {
this.error = data;
this.errorCode =
((_b = this.error) === null || _b === void 0 ? void 0 : _b.code) && isVastErrorCode(this.error.code)
? this.error.code
: ErrorCode.VAST_PROBLEM_DISPLAYING_MEDIA_FILE;
}
this[_protected].onErrorCallbacks.forEach((callback) => callback(this.error, {
adUnit: this,
vastChain: this.vastChain
}));
this[_protected].finish();
break;
}
case skip: {
this.cancel();
break;
}
}
this.emit(event, {
adUnit: this,
data,
type: event
});
},
drawIcons: async () => {
var _b, _c, _d, _e;
if (this.isFinished()) {
return;
}
await ((_c = (_b = this[_protected]).drawIcons) === null || _c === void 0 ? void 0 : _c.call(_b));
if (((_e = (_d = this[_protected]).hasPendingIconRedraws) === null || _e === void 0 ? void 0 : _e.call(_d)) && !this.isFinished()) {
const { videoElement } = this.videoAdContainer;
once(videoElement, 'timeupdate', this[_private].drawIcons);
}
}
};
/** Ad unit type. Will be `VAST` for VastAdUnit */
this.type = 'VAST';
const { onFinishCallbacks } = this[_protected];
const { handleMetric } = this[_private];
this.hooks = options.hooks || {};
const removeMetricHandlers = setupMetricHandlers({
hooks: this.hooks,
pauseOnAdClick: this.pauseOnAdClick,
vastChain: this.vastChain,
videoAdContainer: this.videoAdContainer
}, handleMetric);
onFinishCallbacks.push(removeMetricHandlers);
}
/**
* Starts the ad unit.
*
* @throws if called twice.
* @throws if ad unit is finished.
*/
async start() {
this[_protected].throwIfFinished();
if (this.isStarted()) {
throw new AdUnitError('VastAdUnit already started');
}
const inlineAd = this.vastChain[0].ad;
const { videoElement } = this.videoAdContainer;
const media = inlineAd && findBestMedia(inlineAd, this.videoAdContainer, this.hooks);
if (media) {
if (this.icons) {
await this[_private].drawIcons();
}
if (media === null || media === void 0 ? void 0 : media.src) {
videoElement.src = media.src;
this.assetUri = media.src;
}
videoElement.play();
}
else {
const adUnitError = new AdUnitError("Can't find a suitable media to play");
adUnitError.code = ErrorCode.VAST_LINEAR_ASSET_MISMATCH;
this[_private].handleMetric(errorEvent, adUnitError);
}
this[_protected].started = true;
}
/**
* Resumes a previously paused ad unit.
*
* @throws if ad unit is not started.
* @throws if ad unit is finished.
*/
resume() {
this.videoAdContainer.videoElement.play();
}
/**
* Pauses the ad unit.
*
* @throws if ad unit is not started.
* @throws if ad unit is finished.
*/
pause() {
this.videoAdContainer.videoElement.pause();
}
/**
* Skips the ad unit.
*
* @throws if ad unit is not started.
* @throws if ad unit is finished.
*/
skip() {
const inlineAd = this.vastChain[0].ad;
const skipoffset = inlineAd && getSkipOffset(inlineAd);
const currentTimeMs = this.currentTime() * 1000;
if (typeof skipoffset === 'number' && currentTimeMs >= skipoffset) {
this[_private].handleMetric(skip);
}
}
/**
* Returns true if the ad is paused and false otherwise
*/
paused() {
return this.videoAdContainer.videoElement.paused;
}
/**
* Sets the volume of the ad unit.
*
* @throws if ad unit is not started.
* @throws if ad unit is finished.
*
* @param volume must be a value between 0 and 1;
*/
setVolume(volume) {
this.videoAdContainer.videoElement.volume = volume;
}
/**
* Gets the volume of the ad unit.
*
* @throws if ad unit is not started.
* @throws if ad unit is finished.
*
* @returns the volume of the ad unit.
*/
getVolume() {
return this.videoAdContainer.videoElement.volume;
}
/**
* Cancels the ad unit.
*
* @throws if ad unit is finished.
*/
cancel() {
this[_protected].throwIfFinished();
this.videoAdContainer.videoElement.pause();
this[_protected].finish();
}
/**
* Returns the duration of the ad Creative or 0 if there is no creative.
*
* @returns the duration of the ad unit.
*/
duration() {
if (!this.isStarted()) {
return 0;
}
return this.videoAdContainer.videoElement.duration;
}
/**
* Returns the current time of the ad Creative or 0 if there is no creative.
*
* @returns the current time of the ad unit.
*/
currentTime() {
if (!this.isStarted()) {
return 0;
}
return this.videoAdContainer.videoElement.currentTime;
}
/**
* This method resizes the ad unit to fit the available space in the passed {@link VideoAdContainer}
*
* @param width the new width of the ad container.
* @param height the new height of the ad container.
* @param viewmode fullscreen | normal | thumbnail
* @returns Promise that resolves once the unit was resized
*/
async resize(width, height, viewmode) {
await super.resize(width, height, viewmode);
if (this.isStarted() && !this.isFinished()) {
const inlineAd = this.vastChain[0].ad;
const { videoElement } = this.videoAdContainer;
const media = inlineAd && findBestMedia(inlineAd, this.videoAdContainer, this.hooks);
if (media && videoElement.src !== media.src) {
updateMedia(videoElement, media);
}
}
}
}
_a = _private;