UNPKG

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
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(); } }; };