video-ad-sdk
Version:
VAST/VPAID SDK that allows video ads to be played on top of any player
147 lines (146 loc) • 6.03 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { trackError, isVastErrorCode, ErrorCode } from '../tracker';
import { requestAd, requestNextAd } from '../vastRequest';
import { VastError } from '../vastRequest/helpers/vastError';
import { getInteractiveFiles } from '../vastSelectors';
import { isIos } from '../utils/isIos';
import { run } from './run';
const isVpaid = (vastChain) => Boolean(vastChain[0].ad && getInteractiveFiles(vastChain[0].ad));
const validateVastChain = (vastChain, options) => {
var _a;
if (!vastChain || vastChain.length === 0) {
throw new Error('Invalid VastChain');
}
const [lastVastResponse] = vastChain;
if (!options.vpaidEnabled && isVpaid(vastChain)) {
const error = new VastError('VPAID ads are not supported by the current player');
error.code = ErrorCode.VAST_UNEXPECTED_AD_TYPE;
lastVastResponse.errorCode = ErrorCode.VAST_UNEXPECTED_AD_TYPE;
lastVastResponse.error = error;
}
if (lastVastResponse.error) {
throw lastVastResponse.error;
}
if (typeof ((_a = options.hooks) === null || _a === void 0 ? void 0 : _a.validateVastResponse) === 'function') {
options.hooks.validateVastResponse(vastChain);
}
};
const callbackHandler = (callback) => (...args) => {
if (typeof callback === 'function') {
callback(...args);
}
};
const transformVastResponse = (vastChain, { hooks }) => {
if (typeof (hooks === null || hooks === void 0 ? void 0 : hooks.transformVastResponse) === 'function') {
return hooks.transformVastResponse(vastChain);
}
return vastChain;
};
const handleVastError = (error, vastChain, tracker) => {
var _a;
let errorCode = ((_a = vastChain === null || vastChain === void 0 ? void 0 : vastChain[0]) === null || _a === void 0 ? void 0 : _a.errorCode) || (error === null || error === void 0 ? void 0 : error.code);
if (vastChain && errorCode) {
if (!isVastErrorCode(errorCode)) {
errorCode = ErrorCode.UNKNOWN_ERROR;
}
trackError(vastChain, {
errorCode,
tracker
});
}
};
const waterfall = async (fetchVastChain, placeholder, _a) => {
var { isCanceled } = _a, options = __rest(_a, ["isCanceled"]);
let vastChain;
let runEpoch;
let adUnit;
const { onAdStart, onError, onRunFinish } = options;
try {
if (typeof options.timeout === 'number') {
runEpoch = Date.now();
}
vastChain = await fetchVastChain();
if (isCanceled()) {
onRunFinish();
return;
}
if (options.timeout && runEpoch) {
const newEpoch = Date.now();
options.timeout -= newEpoch - runEpoch;
runEpoch = newEpoch;
}
validateVastChain(vastChain, options);
adUnit = await run(transformVastResponse(vastChain, options), placeholder, Object.assign({}, options));
if (isCanceled()) {
adUnit.cancel();
onRunFinish();
return;
}
adUnit.onError(onError);
adUnit.onFinish(onRunFinish);
onAdStart(adUnit);
}
catch (error) {
handleVastError(error, vastChain, options.tracker);
onError(error, {
adUnit,
vastChain
});
if (!vastChain || isCanceled()) {
onRunFinish();
return;
}
if (options.timeout && runEpoch) {
options.timeout -= Date.now() - runEpoch;
}
if (runEpoch && options.timeout && options.timeout <= 0) {
onRunFinish();
return;
}
waterfall(() => requestNextAd(vastChain, options), placeholder, Object.assign(Object.assign({}, options), { isCanceled }));
}
};
/**
* Will try to start one of the ads returned by the `adTag`. It will keep trying until it times out or it runs out of ads.
*
* @param adTag The VAST ad tag request url.
* @param placeholder placeholder element that will contain the video ad.
* @param options Options Map
* @returns CancelFunction function. If called it will cancel the ad run. {@link runWaterfall~onRunFinish} will still be called;
*/
export const runWaterfall = (adTag, placeholder, options) => {
var _a, _b, _c;
let canceled = false;
let adUnit;
const isCanceled = () => canceled;
const onAdStartHandler = callbackHandler(options.onAdStart);
const onAdStart = (newAdUnit) => {
adUnit = newAdUnit;
onAdStartHandler(adUnit);
};
const resultOptions = Object.assign(Object.assign({ vpaidEnabled: true }, options), { onAdReady: callbackHandler(options.onAdReady), onAdStart, onError: callbackHandler(options.onError), onRunFinish: callbackHandler(options.onRunFinish) });
// NOTE: It seems that if the video doesn't load synchronously inside a touchend or click event handler, the user gesture breaks on iOS and it won't allow a play.
const shouldLoad = isIos() &&
((_a = options.videoElement) === null || _a === void 0 ? void 0 : _a.paused) &&
((_b = options.videoElement) === null || _b === void 0 ? void 0 : _b.canPlayType('application/vnd.apple.mpegurl'));
if (shouldLoad) {
(_c = options.videoElement) === null || _c === void 0 ? void 0 : _c.load();
}
waterfall(() => requestAd(adTag, resultOptions), placeholder, Object.assign(Object.assign({}, resultOptions), { isCanceled }));
return () => {
canceled = true;
if (adUnit && !adUnit.isFinished()) {
adUnit.cancel();
}
};
};