UNPKG

@speechkit/speechkit-audio-player

Version:

A web player component that can play audio from https://speechkit.io

337 lines (304 loc) 12.2 kB
import { fetchWithErrorHandling } from '../utils/BetterFetch' import { stringToXML, getCreative, getVASTEventURLs, getNodesWithAttrValue, getContentOfNodesWithAttrValue, } from '../utils/ParseXML' class AdsService { constructor(options) { this.apiKey = options.apiKey; this.skBackend = options.skBackend; this.projectCampaignId = options.projectCampaignId; this.customAdMedia = options.customAdMedia; this.ads = null; this.adsIsPlayed = false; this.adsIsPlaying = false; this.googleAds = false; this.adsHistory = null; this.vastAdTag = options.vastAdTag; this.GDPR = {gdprApplies: false}; this.getAds(options.vastAdTag); } // decide which type of ad to get, (this choice will be modified with full VAST integration) getAds(vastAdTag) { // call respective function: vast / spkt ads if (!vastAdTag) { return this.getSpktAds(); } else { return this.getVastAd(); } } // Fetch Speechkit ads for specified campaign ID and return promise to ads object (JSON) getSpktAds() { if (!this.promise) { this.promise = new Promise((resolve, reject) => { // if JSON is passed in for ad previews, process in player if(this.customAdMedia) { this.ads = this.customAdMedia return resolve(this.ads) } // exit if no projectCampaignId if (!this.projectCampaignId) return resolve(this.ads); // if we do have campaignId, fetch ads from API const sUrl = `${this.skBackend}/api/v2/project_campaigns/${this.projectCampaignId}` return fetchWithErrorHandling(sUrl, { headers: { 'Authorization': `Token token=${this.apiKey}`, } }) // if response ok, return ads from body as json .then(response => response.json()) // update the ads object and resolve the promise .then(oJson => { this.ads = oJson; return resolve(this.ads); }) .catch(error => { reject(); console.log('Get Spkt Ad Error: '+ error); }); }); } return this.promise; } // to get CMP from URL parameter getURLParameter(name) { return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)') .exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null; } loadScript(src) { return new Promise((resolve, reject) => { if(this.googleAds) { resolve(); return; } const script = document.createElement('script'); let ready = false; script.type = 'text/javascript'; script.src = src; script.async = true; script.onerror = e => { console.log('adswizz inject error: ', JSON.stringify(e)); reject(e, script); }; script.onload = script.onreadystatechange = () => { if (!ready && (!this.readyState || this.readyState == 'complete')) { ready = true; resolve('src'); } }; document.body.appendChild(script); }); } getVastAd() { if (!this.promise) { // get the GDPR information from the iframe (added in iframe-helper) const sEncodedGDPR = this.getURLParameter('cmp'); let sConsent = '?aw_0_req.gdpr=false'; // if attribute exists, decode and parse to object, then update consent if(sEncodedGDPR) { const sGDPR = atob(sEncodedGDPR); this.GDPR = JSON.parse(sGDPR); if(this.GDPR.gdprApplies) { sConsent = '?aw_0_req.gdpr=' + this.GDPR.gdprApplies; } } this.googleAds = this.vastAdTag.includes('pubads.g.doubleclick.net'); // load first AdsWizz script const src = location.protocol + '//synchrobox.adswizz.com/register2.php'; this.promise = this.loadScript(src + sConsent) .then(() => { // load second AdsWizz script let src2 = location.protocol + '//cdn.adswizz.com/adswizz/js/SynchroClient2.js'; return this.loadScript(src2 + sConsent); }) .then(() => { //update vast URL with extra info once Adswizz script has loaded, request Ad return new Promise((resolve, reject) => { if(this.googleAds) { //CODE FOR GOOLE AD MANAGER: overwrite url + description_url params let sURL = new URL('http:'+this.vastAdTag); let sQueryString = sURL.search; let URLParams = new URLSearchParams(sQueryString); // new value of "id" is set to "101" URLParams.set('url', document.referrer); URLParams.set('description_url', location.href); // change the search property of the main url sURL.search = URLParams.toString(); // the new url string sURL = sURL.toString(); this.vastAdTag = sURL.slice(5); console.log('loading Google Ad Tag: ', this.vastAdTag); } else { // normally wrap below in try/catch (promise will catch errors here) this.vastAdTag=com_adswizz_synchro_decorateUrl(this.vastAdTag); // add consent data if present let sCMP = '&aw_0_req.gdpr=false'; if(this.GDPR.consentData) { sCMP = '&aw_0_req.gdpr=' + this.GDPR.gdprApplies + '&aw_0_req.userConsent=' + this.GDPR.consentData; } this.vastAdTag += sCMP; // finally add cache buster } const cb = '&cb=' + (Math.round(new Date().getTime() / 1000)); this.vastAdTag += cb; const sAdTagWithProtocol = location.protocol + this.vastAdTag; return fetchWithErrorHandling(sAdTagWithProtocol, { }) .then(response => response.text()) .then(stringToXML) .then(xVastDoc => { // get array of ads from Vast XML const aAds = xVastDoc.getElementsByTagName('Ad'); if(!aAds.length) { this.fireError(xVastDoc) throw new Error("No Demand"); } // getFirstAd will get object with details of first Ad in response this.ads = this.getFirstAd(aAds); return resolve(this.ads); }) .catch(error => { reject(); console.log('Get VAST Ad Error: '+ error); }); }) }); } return this.promise; } //append new trackingUrls of event type to oAds object //takes tracking Node and gets & stores all events addTrackingUrls(oAd, xEvents, aEventTypes) { const aTrackingEvents = xEvents.getElementsByTagName('Tracking') for (let i = 0; i < aEventTypes.length; i += 1) { let sEventType = aEventTypes[i] let aThisEventUrls = getContentOfNodesWithAttrValue(aTrackingEvents, 'event', sEventType) for (let j = 0; j < aThisEventUrls.length; j += 1) { if(!oAd.media[0].trackingUrls[sEventType]) { oAd.media[0].trackingUrls[sEventType] = new Array(); } oAd.media[0].trackingUrls[sEventType].push(aThisEventUrls[j]); } } } // in event of no Ad, fire error fireError(xVastDoc) { const xError = xVastDoc.getElementsByTagName('Error')[0] if(xError.textContent) { const img = new Image() img.src = xError.textContent } } // parse ads from the VAST repsonse, returning Ad object, populated with relevant data getFirstAd(aAds) { // get first Ad from VAST - WHEN FETCHING MORE THAN ONE AD, LOOK FOR SEQ=2 here let xAd = getNodesWithAttrValue(aAds, 'sequence', '1')[0]; if(!xAd) { xAd = aAds[0]; } // next find both the linear creative and the companion const xMediaCreative = getCreative(xAd) // collect all useful nodes from the Ad: const oNodes = { xDuration: xMediaCreative.getElementsByTagName('Duration')[0], xMediaFile: xMediaCreative.getElementsByTagName('MediaFile')[0], xPricing: xAd.getElementsByTagName('Pricing')[0], xAdTitle: xAd.getElementsByTagName('AdTitle')[0], xAdvertiser: xAd.getElementsByTagName('Advertiser')[0], xGoogleClickThrough: xAd.getElementsByTagName('ClickThrough')[0], } // Look for companion (optional) for more info const xCompanion = xAd.getElementsByTagName('Companion')[0] if(xCompanion) { oNodes.xStaticResource = xCompanion.getElementsByTagName('StaticResource')[0] oNodes.xAltText = xCompanion.getElementsByTagName('AltText')[0] oNodes.xClickThrough = xCompanion.getElementsByTagName('CompanionClickThrough')[0] } const sAdvertiser = oNodes.xAdvertiser ? oNodes.xAdvertiser.textContent : 'Link'; const sClickThrough = oNodes.xClickThrough ? oNodes.xClickThrough.textContent : oNodes.xGoogleClickThrough ? oNodes.xGoogleClickThrough.textContent : ('https://' + sAdvertiser); //loop over object to make sure nodes are populated: // for (let key in oNodes) { // if (!oNodes[key]) { // //TODO: add error handling here // console.log('VAST node ', key); // } // } const oAd = { id: xMediaCreative.getAttribute('id'), campaign_id: xMediaCreative.getAttribute('id'), campaign_name : sAdvertiser, media: [{ bitrate: parseInt(oNodes.xMediaFile.getAttribute('bitrate')) || 0, content_type: oNodes.xMediaFile.getAttribute('type'), delivery: oNodes.xMediaFile.getAttribute('delivery'), duration: this.getAdDuration(oNodes.xDuration) || 0, logo: oNodes.xStaticResource ? oNodes.xStaticResource.textContent : '', height: xCompanion ? parseInt(xCompanion.getAttribute('height')) : 0, width: xCompanion ? parseInt(xCompanion.getAttribute('width')) : 0, title: oNodes.xAltText ? oNodes.xAltText.textContent : 'no title', pricing: { currency: oNodes.xPricing ? oNodes.xPricing.getAttribute('currency') : 0, model: oNodes.xPricing ? oNodes.xPricing.getAttribute('model') : 'CPM', cost: oNodes.xPricing ? parseInt(oNodes.xPricing.textContent) : 0, }, trackingUrls: {}, promo_link: sClickThrough, url: oNodes.xMediaFile.textContent, }], } // get tracking nodes from vast const xTrackingEvents = xMediaCreative.getElementsByTagName('TrackingEvents')[0] // const xCompanionTrackingEvents = xCompanion.getElementsByTagName('TrackingEvents')[0] // set the events we want to track const aCreativeEvents = ['start', 'firstQuartile', 'midpoint', 'thirdQuartile', 'complete'] // const aCompanionEvents = ['creativeView'] // get and store all the tracking URLs for the above events on our oAd object this.addTrackingUrls(oAd, xTrackingEvents, aCreativeEvents) // this.addTrackingUrls(oAd, xCompanionTrackingEvents, aCompanionEvents) // const aErrorUrls = getVASTEventURLs(xVastDoc, 'Error') oAd.media[0].trackingUrls.impression = getVASTEventURLs(xAd, 'Impression') // oAd.media[0].trackingUrls.click = getVASTEventURLs(xCompanion, 'CompanionClickTracking') //TODO: remove log, console.log('VAST Ad Object Created: ', oAd) return oAd; } getAdDuration(xDuration) { // add error case if (!xDuration.textContent) { return null } let aTimeComponents = xDuration.textContent.split(':'); // Split it at the colons. // Map the array of strings to an array of numbers aTimeComponents = aTimeComponents.map(sTime => +sTime); // return total, minutes are worth 60 seconds, hours are worth 60 minutes const nTimeSeconds = aTimeComponents[0] * 60 * 60 + aTimeComponents[1] * 60 + aTimeComponents[2]; return nTimeSeconds * 1000; } removeAds() { if (this.ads) { this.adsHistory = { ...this.ads } } this.ads = null } isPlaying() { return this.adsIsPlaying; } isPlayed() { return this.adsIsPlayed; } setPlaying() { this.adsIsPlaying = true; } setPlayed() { this.adsIsPlayed = true; this.adsIsPlaying = false; } } export default AdsService;