/// <summary> /// Preloads an ad that will be played later. /// </summary> /// <param name="adDocument">The ad document to execute.</param> /// <param name="adSource">The original ad source that the ad document came from.</param> /// <param name="cancellationToken">A cancellation token to cancel the ad.</param> /// <returns></returns> async Task PreloadAdAsync(AdDocumentPayload adDocument, IAdSource adSource, CancellationToken cancellationToken) { foreach (var adPod in adDocument.AdPods) { try { // model expects ads to be in the correct order. foreach (var ad in adPod.Ads) { // group the creatives by sequence number. Always put the group without sequence number at the back of the piority list in compliance with VAST spec. foreach (var creativeSet in ad.Creatives.GroupBy(c => c.Sequence).OrderBy(cs => cs.Key.GetValueOrDefault(int.MaxValue))) { var newAdUnit = CreateAdUnit(creativeSet, ad, adSource); if (newAdUnit != null) // a violation of the VAST spec but we will just ignore { loadOperation = LoadAdUnit(newAdUnit, cancellationToken); try { await loadOperation.Task; cancellationToken.ThrowIfCancellationRequested(); } catch { loadOperation = null; throw; } return; } } } } catch (LoadException) { /* ignore, move to the next adpod */ } } }
private static IEnumerable<ICreative> GetSubsequentCreatives(IDocumentCreativeSource creativeSource, Ad creativeConcept, AdDocumentPayload payload) { if (creativeSource == null) throw new ArgumentNullException("creativeSource"); if (payload == null) throw new ArgumentNullException("payload"); if (creativeConcept == null) throw new ArgumentNullException("creativeConcept"); var adPod = payload.AdPods.FirstOrDefault(ap => ap.Ads.Contains(creativeConcept)); if (adPod != null) { return adPod.Ads .SelectMany(a => a.Creatives.OrderBy(c => c.Sequence.GetValueOrDefault(int.MaxValue))) .Where(c => !(c is CreativeCompanions)) .SkipWhile(c => c != creativeSource.Creative) .Skip(1); } return null; }
private static AdDocumentPayload CreateDocument(IClipAdPayload mediaSource) { var adDocument = new AdDocumentPayload(); var adPod = new AdPod(); var ad = new Ad(); var linearCreative = new CreativeLinear(); linearCreative.ClickThrough = mediaSource.ClickThrough; linearCreative.MediaFiles.Add(new MediaFile() { Type = mediaSource.MimeType, Value = mediaSource.MediaSource }); ad.Creatives.Add(linearCreative); adPod.Ads.Add(ad); adDocument.AdPods.Add(adPod); return(adDocument); }
private static AdDocumentPayload CreateDocument(IClipAdPayload mediaSource) { var adDocument = new AdDocumentPayload(); var adPod = new AdPod(); var ad = new Ad(); var linearCreative = new CreativeLinear(); linearCreative.ClickThrough = mediaSource.ClickThrough; linearCreative.MediaFiles.Add(new MediaFile() { Type = mediaSource.MimeType, Value = mediaSource.MediaSource }); ad.Creatives.Add(linearCreative); adPod.Ads.Add(ad); adDocument.AdPods.Add(adPod); return adDocument; }
internal static async Task<AdDocumentPayload> CreateFromVast(Stream stream, int? maxRedirectDepth, bool allowMultipleAds) #endif { XDocument xDoc = XDocument.Load(stream); XElement vastRoot = xDoc.Element("VAST"); if (vastRoot == null) { vastRoot = xDoc.Element("VideoAdServingTemplate"); if (vastRoot == null) throw new NotImplementedException(); return await CreateFromVast1(vastRoot, maxRedirectDepth, allowMultipleAds); } else { var result = new AdDocumentPayload(); result.Version = (string)vastRoot.Attribute("version"); result.Error = (string)vastRoot.Element("Error"); var eligableAds = vastRoot.Elements("Ad"); if (!allowMultipleAds) { eligableAds = eligableAds.Where(va => string.IsNullOrEmpty((string)va.Attribute("sequence"))); } foreach (var vastAdPod in eligableAds.GroupBy(va => va.Attribute("sequence") != null ? 1 : int.MaxValue).OrderBy(vap => vap.Key)) { var adPod = new AdPod(); foreach (var vastAd in vastAdPod.OrderBy(va => ToNullableInt((string)va.Attribute("sequence")).GetValueOrDefault(0))) { var ad = new Ad(); ad.Id = (string)vastAd.Attribute("id"); if (vastAd.Elements("InLine").Any()) { var vastAdInline = vastAd.Element("InLine"); ad.AdSystem = GetAdSystem(vastAdInline.Element("AdSystem")); ad.Advertiser = (string)vastAdInline.Element("Advertiser"); ad.Description = (string)vastAdInline.Element("Description"); var error = (string)vastAdInline.Element("Error"); if (error != null) ad.Errors.Add(error); ad.Title = (string)vastAdInline.Element("AdTitle"); ad.Survey = GetUriValue(vastAdInline.Element("Survey")); ad.Pricing = new Pricing(); var pricing = vastAdInline.Element("Pricing"); if (pricing != null) { ad.Pricing.Currency = (string)pricing.Attribute("currency"); ad.Pricing.Model = (PricingModel)Enum.Parse(typeof(PricingModel), (string)pricing.Attribute("model"), true); ad.Pricing.Value = Convert.ToDouble((string)pricing); } foreach (var vastImpression in vastAdInline.Elements("Impression")) { ad.Impressions.Add((string)vastImpression); } if (vastAdInline.Elements("Extensions").Any()) { foreach (var vastExtension in vastAdInline.Element("Extensions").Elements("Extension")) { ad.Extensions.Add(new Extension()); // TODO } } LoadCreatives(vastAdInline, ad); adPod.Ads.Add(ad); } else if (vastAd.Elements("Wrapper").Any()) { Ad wrapper = new Ad(); var vastAdWrapper = vastAd.Element("Wrapper"); // parse the wrapper itself wrapper.AdSystem = GetAdSystem(vastAdWrapper.Element("AdSystem")); var error = (string)vastAdWrapper.Element("Error"); if (error != null) wrapper.Errors.Add(error); foreach (var vastImpression in vastAdWrapper.Elements("Impression")) { wrapper.Impressions.Add((string)vastImpression); } LoadCreatives(vastAdWrapper, wrapper); if (vastAdWrapper.Elements("Extensions").Any()) { foreach (var vastExtension in vastAdWrapper.Element("Extensions").Elements("Extension")) { ad.Extensions.Add(new Extension()); // TODO } } AdDocumentPayload wrappedVastDoc = null; var vastAdUri = GetUriValue(vastAdWrapper.Element("VASTAdTagURI")); if (vastAdUri != null && (!maxRedirectDepth.HasValue || maxRedirectDepth.Value > 0)) { try { // load the stream from the web using (var s = await Extensions.LoadStreamAsync(vastAdUri)) { var newAllowMultipleAds = vastAdWrapper.GetBoolAttribute("allowMultipleAds", allowMultipleAds); var followAdditionalWrappers = vastAdWrapper.GetBoolAttribute("followAdditionalWrappers", true); int? nextMaxRedirectDepth = followAdditionalWrappers ? (maxRedirectDepth.HasValue ? maxRedirectDepth.Value - 1 : maxRedirectDepth) : 0; wrappedVastDoc = await CreateFromVast(s, nextMaxRedirectDepth, newAllowMultipleAds); } } catch { /* swallow */ } } AdPod wrappedAdPod = null; if (wrappedVastDoc != null) { wrappedAdPod = wrappedVastDoc.AdPods.FirstOrDefault(); } if (wrappedAdPod == null || !wrappedAdPod.Ads.Any()) { // no ads were returned var fallbackOnNoAd = vastAdWrapper.GetBoolAttribute("fallbackOnNoAd", true); if (fallbackOnNoAd) { wrappedAdPod = FallbackAdPod; } } if (wrappedAdPod != null) { // merge tracking info from this wrapper to every ad in the first adpod in the child foreach (Ad inlineAd in wrappedAdPod.Ads) MergeWrappedAd(wrapper, inlineAd); // add each ad from the first adpod in the child to the current adpod foreach (Ad inlineAd in wrappedAdPod.Ads) adPod.Ads.Add(inlineAd); } } } result.AdPods.Add(adPod); } return result; } }
internal static async Task <AdDocumentPayload> CreateFromVast(Stream stream, int?maxRedirectDepth, bool allowMultipleAds) #endif { XDocument xDoc = XDocument.Load(stream); XElement vastRoot = xDoc.Element("VAST"); if (vastRoot == null) { vastRoot = xDoc.Element("VideoAdServingTemplate"); if (vastRoot == null) { throw new NotImplementedException(); } return(await CreateFromVast1(vastRoot, maxRedirectDepth, allowMultipleAds)); } else { var result = new AdDocumentPayload(); result.Version = (string)vastRoot.Attribute("version"); result.Error = (string)vastRoot.Element("Error"); var eligableAds = vastRoot.Elements("Ad"); if (!allowMultipleAds) { eligableAds = eligableAds.Where(va => string.IsNullOrEmpty((string)va.Attribute("sequence"))); } foreach (var vastAdPod in eligableAds.GroupBy(va => va.Attribute("sequence") != null ? 1 : int.MaxValue).OrderBy(vap => vap.Key)) { var adPod = new AdPod(); foreach (var vastAd in vastAdPod.OrderBy(va => ToNullableInt((string)va.Attribute("sequence")).GetValueOrDefault(0))) { var ad = new Ad(); ad.Id = (string)vastAd.Attribute("id"); if (vastAd.Elements("InLine").Any()) { var vastAdInline = vastAd.Element("InLine"); ad.AdSystem = GetAdSystem(vastAdInline.Element("AdSystem")); ad.Advertiser = (string)vastAdInline.Element("Advertiser"); ad.Description = (string)vastAdInline.Element("Description"); var error = (string)vastAdInline.Element("Error"); if (error != null) { ad.Errors.Add(error); } ad.Title = (string)vastAdInline.Element("AdTitle"); ad.Survey = GetUriValue(vastAdInline.Element("Survey")); ad.Pricing = new Pricing(); var pricing = vastAdInline.Element("Pricing"); if (pricing != null) { ad.Pricing.Currency = (string)pricing.Attribute("currency"); ad.Pricing.Model = (PricingModel)Enum.Parse(typeof(PricingModel), (string)pricing.Attribute("model"), true); ad.Pricing.Value = Convert.ToDouble((string)pricing); } foreach (var vastImpression in vastAdInline.Elements("Impression")) { ad.Impressions.Add((string)vastImpression); } if (vastAdInline.Elements("Extensions").Any()) { foreach (var vastExtension in vastAdInline.Element("Extensions").Elements("Extension")) { ad.Extensions.Add(new Extension()); // TODO } } LoadCreatives(vastAdInline, ad); adPod.Ads.Add(ad); } else if (vastAd.Elements("Wrapper").Any()) { Ad wrapper = new Ad(); var vastAdWrapper = vastAd.Element("Wrapper"); // parse the wrapper itself wrapper.AdSystem = GetAdSystem(vastAdWrapper.Element("AdSystem")); var error = (string)vastAdWrapper.Element("Error"); if (error != null) { wrapper.Errors.Add(error); } foreach (var vastImpression in vastAdWrapper.Elements("Impression")) { wrapper.Impressions.Add((string)vastImpression); } LoadCreatives(vastAdWrapper, wrapper); if (vastAdWrapper.Elements("Extensions").Any()) { foreach (var vastExtension in vastAdWrapper.Element("Extensions").Elements("Extension")) { ad.Extensions.Add(new Extension()); // TODO } } AdDocumentPayload wrappedVastDoc = null; var vastAdUri = GetUriValue(vastAdWrapper.Element("VASTAdTagURI")); if (vastAdUri != null && (!maxRedirectDepth.HasValue || maxRedirectDepth.Value > 0)) { try { // load the stream from the web using (var s = await Extensions.LoadStreamAsync(vastAdUri)) { var newAllowMultipleAds = vastAdWrapper.GetBoolAttribute("allowMultipleAds", allowMultipleAds); var followAdditionalWrappers = vastAdWrapper.GetBoolAttribute("followAdditionalWrappers", true); int?nextMaxRedirectDepth = followAdditionalWrappers ? (maxRedirectDepth.HasValue ? maxRedirectDepth.Value - 1 : maxRedirectDepth) : 0; wrappedVastDoc = await CreateFromVast(s, nextMaxRedirectDepth, newAllowMultipleAds); } } catch { /* swallow */ } } AdPod wrappedAdPod = null; if (wrappedVastDoc != null) { wrappedAdPod = wrappedVastDoc.AdPods.FirstOrDefault(); } if (wrappedAdPod == null || !wrappedAdPod.Ads.Any()) { // no ads were returned var fallbackOnNoAd = vastAdWrapper.GetBoolAttribute("fallbackOnNoAd", true); if (fallbackOnNoAd) { wrappedAdPod = FallbackAdPod; } } if (wrappedAdPod != null) { // merge tracking info from this wrapper to every ad in the first adpod in the child foreach (Ad inlineAd in wrappedAdPod.Ads) { MergeWrappedAd(wrapper, inlineAd); } // add each ad from the first adpod in the child to the current adpod foreach (Ad inlineAd in wrappedAdPod.Ads) { adPod.Ads.Add(inlineAd); } } } } result.AdPods.Add(adPod); } return(result); } }
internal static async Task<AdDocumentPayload> CreateFromVast1(XElement vastRoot, int? maxRedirectDepth, bool allowMultipleAds) { var result = new AdDocumentPayload(); result.Version = (string)vastRoot.Attribute("version"); foreach (var vastAdPod in vastRoot.Elements("Ad").GroupBy(va => va.Attribute("sequence") != null ? 1 : int.MaxValue).OrderBy(vap => vap.Key)) { var adPod = new AdPod(); foreach (var vastAd in vastAdPod.OrderBy(va => ToNullableInt((string)va.Attribute("sequence")).GetValueOrDefault(0))) { var ad = new Ad(); ad.Id = (string)vastAd.Attribute("id"); if (vastAd.Elements("InLine").Any()) { throw new NotImplementedException(); } else if (vastAd.Elements("Wrapper").Any()) { Ad wrapper = new Ad(); var vastAdWrapper = vastAd.Element("Wrapper"); // parse the wrapper itself wrapper.AdSystem = GetAdSystem(vastAdWrapper.Element("AdSystem")); var error = (string)vastAdWrapper.Element("Error"); if (error == null) wrapper.Errors.Add(error); var linearCreative = new CreativeLinear(); foreach (var trackingEvent in GetTrackingEvents(vastAdWrapper)) linearCreative.TrackingEvents.Add(trackingEvent); LoadVideoClicks(vastAdWrapper, linearCreative); wrapper.Creatives.Add(linearCreative); var vastAdUri = GetUriValue(vastAdWrapper.Element("VASTAdTagURL")); if (vastAdUri != null) { // load the stream from the web using (var s = await Extensions.LoadStreamAsync(vastAdUri)) { int? nextMaxRedirectDepth = maxRedirectDepth.HasValue ? maxRedirectDepth.Value - 1 : maxRedirectDepth; var vastDoc = await CreateFromVast(s, nextMaxRedirectDepth, allowMultipleAds); var firstAdPodInChild = vastDoc.AdPods.FirstOrDefault(); if (firstAdPodInChild != null) { // merge tracking info from this wrapper to every ad in the first adpod in the child foreach (Ad inlineAd in firstAdPodInChild.Ads) MergeWrappedAd(wrapper, inlineAd); // add each ad from the first adpod in the child to the current adpod foreach (Ad inlineAd in firstAdPodInChild.Ads) adPod.Ads.Add(inlineAd); } } } } } result.AdPods.Add(adPod); } return result; }
public static async Task<AdDocumentPayload> GetAdDocumentPayload(FWTemporalAdSlot adSlot, FWAdResponse adResponse, CancellationToken c) #endif { var payload = new AdDocumentPayload(); var adPod = new AdPod(); payload.AdPods.Add(adPod); foreach (var adReference in adSlot.SelectedAds) { var ad = await CreateAd(adResponse, adReference); adPod.Ads.Add(ad); foreach (var fallbackAdReference in adReference.FallbackAds) { var fallbackAd = await CreateAd(adResponse, fallbackAdReference); ad.FallbackAds.Add(fallbackAd); } } return payload; }
/// <summary> /// Preloads an ad that will be played later. /// </summary> /// <param name="adDocument">The ad document to execute.</param> /// <param name="adSource">The original ad source that the ad document came from.</param> /// <param name="cancellationToken">A cancellation token to cancel the ad.</param> /// <returns></returns> async Task PreloadAdAsync(AdDocumentPayload adDocument, IAdSource adSource, CancellationToken cancellationToken) { foreach (var adPod in adDocument.AdPods) { try { // model expects ads to be in the correct order. foreach (var ad in adPod.Ads) { // group the creatives by sequence number. Always put the group without sequence number at the back of the piority list in compliance with VAST spec. foreach (var creativeSet in ad.Creatives.GroupBy(c => c.Sequence).OrderBy(cs => cs.Key.GetValueOrDefault(int.MaxValue))) { var newAdUnit = CreateAdUnit(creativeSet, ad, adSource); if (newAdUnit != null) // a violation of the VAST spec but we will just ignore { loadOperation = LoadAdUnit(newAdUnit, cancellationToken); try { await loadOperation.Task; cancellationToken.ThrowIfCancellationRequested(); } catch { loadOperation = null; throw; } return; } } } } catch (LoadException) { /* ignore, move to the next adpod */ } } }
/// <summary> /// Note: This method is re-entrant safe but not thread safe. /// </summary> /// <param name="adDocument">The ad document to execute.</param> /// <param name="adSource">The original ad source that the ad document came from.</param> /// <param name="startTimeout">The timeout for the ad</param> /// <param name="cancellationToken">A cancellation token to cancel the ad.</param> /// <param name="progress">Reports progress of the ad.</param> /// <returns>Two tasks that indicate when the ad is finished and another for when the ad is done with its linear portion</returns> PlayAdAsyncResult PlayAdAsync(AdDocumentPayload adDocument, IAdSource adSource, TimeSpan? startTimeout, CancellationToken cancellationToken, IProgress<AdStatus> progress) { // primary task wll hold a task that finishes when the ad document finishes or a non-linear ad starts. var primaryTask = new TaskCompletionSource<object>(); // secondary task will hold a task that finishes when the entire ad document finishes. var secondaryTask = TaskHelpers.Create<Task>(async () => { LoadException loadException = null; // keep around to re-throw in case we can't recover using (var timeoutTokenSource = new CancellationTokenSource()) { if (startTimeout.HasValue) { timeoutTokenSource.CancelAfter(startTimeout.Value); } var timeoutToken = timeoutTokenSource.Token; using (var mainCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken)) { var mainCancellationToken = mainCancellationTokenSource.Token; // NOTE: mainCancellationToken is reset to just the initial token once we have successfully started an ad. progress.Report(AdStatus.Opening); try { try { bool podPlayed = false; // throw an exception if no ad ever played // a task to hold the currently playing ad Task activeAdTask = null; foreach (var adPod in adDocument.AdPods) { try { bool adPlayed = false; // throw an exception if no ad ever played // model expects ads to be in the correct order. foreach (var defaultAd in adPod.Ads) { try { var adCandidates = new List<Ad>(); adCandidates.Add(defaultAd); adCandidates.AddRange(defaultAd.FallbackAds); foreach (var ad in adCandidates) { try // **** try ad **** { // group the creatives by sequence number. Always put the group without sequence number at the back of the piority list in compliance with VAST spec. foreach (var creativeSet in ad.Creatives.GroupBy(c => c.Sequence).OrderBy(cs => cs.Key.GetValueOrDefault(int.MaxValue)).Select(cs => cs.ToList())) { var newAdUnit = await LoadAdUnitAsync(creativeSet, ad, adSource, mainCancellationToken); // if there is an ad playing, wait for it to finish before proceeding. if (activeAdTask.IsRunning()) { await activeAdTask; mainCancellationToken.ThrowIfCancellationRequested(); // we can safely assume this is not a timeout and only check the token passed as a param } if (!newAdUnit.Player.AdLinear) { // if the next ad is nonlinear, we've successfully finished the primary task. primaryTask.TrySetResult(null); } LoadAdUnit(newAdUnit, creativeSet); adPlayed = true; loadException = null; // if there was a load error, we recovered, reset. progress.Report(AdStatus.Loaded); activeAd = newAdUnit; VpaidController.AddAd(activeAd); try { await StartAdUnitAsync(newAdUnit, ad, mainCancellationToken); // we successfully started an ad, create a new cancellation token that is not linked to timeout mainCancellationToken = cancellationToken; progress.Report(AdStatus.Playing); // returns when task is over or approaching end activeAdTask = await PlayAdUnitAsync(newAdUnit, mainCancellationToken); if (activeAdTask != null) { activeAdTask = activeAdTask.ContinueWith((Task t) => { progress.Report(AdStatus.Complete); activeAd = null; }, TaskScheduler.FromCurrentSynchronizationContext()); } else { progress.Report(AdStatus.Complete); activeAd = null; } } catch { CleanupAd(activeAd); progress.Report(AdStatus.Complete); activeAd = null; throw; } } break; // we successfully played an ad, break out of loop. } // **** end try ad **** catch (LoadException) { if (ad == adCandidates.Last()) throw; // ignore if it's not the last ad in order to go to next fallback ad } } } catch (LoadException) { if (defaultAd == adPod.Ads.Last()) throw; // ignore if it's not the last ad in the pod } } if (!adPlayed) throw new LoadException(new Exception("No ads found.")); podPlayed = true; break; // we should only play one adPod per the VAST spec } catch (LoadException ex) { // keep around to re-throw in case we can't successfully load an ad loadException = ex; // move to the next adpod, ignore for now. We'll log this later if it's relevant } } // always wait for the playing ad to complete before returning if (activeAdTask.IsRunning()) { await activeAdTask; } if (!podPlayed) { VpaidController.TrackErrorUrl(adDocument.Error, Microsoft.Media.Advertising.VpaidController.Error_NoAd); throw new LoadException(new Exception("No ads found.")); } } catch (OperationCanceledException) { if (timeoutToken.IsCancellationRequested) throw new TimeoutException(); else throw; } catch (Exception ex) { throw new PlayException(ex); } if (loadException != null) { throw loadException; } } catch (Exception ex) { LogError(adSource, ex); primaryTask.TrySetException(ex); throw; } finally { progress.Report(AdStatus.Unloaded); } primaryTask.TrySetResult(null); } } }); return new PlayAdAsyncResult(primaryTask.Task, secondaryTask); }
internal static async Task <AdDocumentPayload> CreateFromVast1(XElement vastRoot, int?maxRedirectDepth, bool allowMultipleAds) { var result = new AdDocumentPayload(); result.Version = (string)vastRoot.Attribute("version"); foreach (var vastAdPod in vastRoot.Elements("Ad").GroupBy(va => va.Attribute("sequence") != null ? 1 : int.MaxValue).OrderBy(vap => vap.Key)) { var adPod = new AdPod(); foreach (var vastAd in vastAdPod.OrderBy(va => ToNullableInt((string)va.Attribute("sequence")).GetValueOrDefault(0))) { var ad = new Ad(); ad.Id = (string)vastAd.Attribute("id"); if (vastAd.Elements("InLine").Any()) { throw new NotImplementedException(); } else if (vastAd.Elements("Wrapper").Any()) { Ad wrapper = new Ad(); var vastAdWrapper = vastAd.Element("Wrapper"); // parse the wrapper itself wrapper.AdSystem = GetAdSystem(vastAdWrapper.Element("AdSystem")); var error = (string)vastAdWrapper.Element("Error"); if (error == null) { wrapper.Errors.Add(error); } var linearCreative = new CreativeLinear(); foreach (var trackingEvent in GetTrackingEvents(vastAdWrapper)) { linearCreative.TrackingEvents.Add(trackingEvent); } LoadVideoClicks(vastAdWrapper, linearCreative); wrapper.Creatives.Add(linearCreative); var vastAdUri = GetUriValue(vastAdWrapper.Element("VASTAdTagURL")); if (vastAdUri != null) { // load the stream from the web using (var s = await Extensions.LoadStreamAsync(vastAdUri)) { int?nextMaxRedirectDepth = maxRedirectDepth.HasValue ? maxRedirectDepth.Value - 1 : maxRedirectDepth; var vastDoc = await CreateFromVast(s, nextMaxRedirectDepth, allowMultipleAds); var firstAdPodInChild = vastDoc.AdPods.FirstOrDefault(); if (firstAdPodInChild != null) { // merge tracking info from this wrapper to every ad in the first adpod in the child foreach (Ad inlineAd in firstAdPodInChild.Ads) { MergeWrappedAd(wrapper, inlineAd); } // add each ad from the first adpod in the child to the current adpod foreach (Ad inlineAd in firstAdPodInChild.Ads) { adPod.Ads.Add(inlineAd); } } } } } } result.AdPods.Add(adPod); } return(result); }
/// <summary> /// Note: This method is re-entrant safe but not thread safe. /// </summary> /// <param name="adDocument">The ad document to execute.</param> /// <param name="adSource">The original ad source that the ad document came from.</param> /// <param name="startTimeout">The timeout for the ad</param> /// <param name="cancellationToken">A cancellation token to cancel the ad.</param> /// <param name="progress">Reports progress of the ad.</param> /// <returns>Two tasks that indicate when the ad is finished and another for when the ad is done with its linear portion</returns> PlayAdAsyncResult PlayAdAsync(AdDocumentPayload adDocument, IAdSource adSource, TimeSpan?startTimeout, CancellationToken cancellationToken, IProgress <AdStatus> progress) { // primary task wll hold a task that finishes when the ad document finishes or a non-linear ad starts. var primaryTask = new TaskCompletionSource <object>(); // secondary task will hold a task that finishes when the entire ad document finishes. var secondaryTask = TaskHelpers.Create <Task>(async() => { LoadException loadException = null; // keep around to re-throw in case we can't recover using (var timeoutTokenSource = new CancellationTokenSource()) { if (startTimeout.HasValue) { timeoutTokenSource.CancelAfter(startTimeout.Value); } var timeoutToken = timeoutTokenSource.Token; using (var mainCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken)) { var mainCancellationToken = mainCancellationTokenSource.Token; // NOTE: mainCancellationToken is reset to just the initial token once we have successfully started an ad. progress.Report(AdStatus.Opening); try { try { bool podPlayed = false; // throw an exception if no ad ever played // a task to hold the currently playing ad Task activeAdTask = null; foreach (var adPod in adDocument.AdPods) { try { bool adPlayed = false; // throw an exception if no ad ever played // model expects ads to be in the correct order. foreach (var defaultAd in adPod.Ads) { try { var adCandidates = new List <Ad>(); adCandidates.Add(defaultAd); adCandidates.AddRange(defaultAd.FallbackAds); foreach (var ad in adCandidates) { try // **** try ad **** { // group the creatives by sequence number. Always put the group without sequence number at the back of the piority list in compliance with VAST spec. foreach (var creativeSet in ad.Creatives.GroupBy(c => c.Sequence).OrderBy(cs => cs.Key.GetValueOrDefault(int.MaxValue)).Select(cs => cs.ToList())) { var newAdUnit = await LoadAdUnitAsync(creativeSet, ad, adSource, mainCancellationToken); // if there is an ad playing, wait for it to finish before proceeding. if (activeAdTask.IsRunning()) { await activeAdTask; mainCancellationToken.ThrowIfCancellationRequested(); // we can safely assume this is not a timeout and only check the token passed as a param } if (!newAdUnit.Player.AdLinear) { // if the next ad is nonlinear, we've successfully finished the primary task. primaryTask.TrySetResult(null); } LoadAdUnit(newAdUnit, creativeSet); adPlayed = true; loadException = null; // if there was a load error, we recovered, reset. progress.Report(AdStatus.Loaded); activeAd = newAdUnit; VpaidController.AddAd(activeAd); try { await StartAdUnitAsync(newAdUnit, ad, mainCancellationToken); // we successfully started an ad, create a new cancellation token that is not linked to timeout mainCancellationToken = cancellationToken; progress.Report(AdStatus.Playing); // returns when task is over or approaching end activeAdTask = await PlayAdUnitAsync(newAdUnit, mainCancellationToken); if (activeAdTask != null) { activeAdTask = activeAdTask.ContinueWith((Task t) => { progress.Report(AdStatus.Complete); activeAd = null; }, TaskScheduler.FromCurrentSynchronizationContext()); } else { progress.Report(AdStatus.Complete); activeAd = null; } } catch { CleanupAd(activeAd); progress.Report(AdStatus.Complete); activeAd = null; throw; } } break; // we successfully played an ad, break out of loop. } // **** end try ad **** catch (LoadException) { if (ad == adCandidates.Last()) { throw; // ignore if it's not the last ad in order to go to next fallback ad } } } } catch (LoadException) { if (defaultAd == adPod.Ads.Last()) { throw; // ignore if it's not the last ad in the pod } } } if (!adPlayed) { throw new LoadException(new Exception("No ads found.")); } podPlayed = true; break; // we should only play one adPod per the VAST spec } catch (LoadException ex) { // keep around to re-throw in case we can't successfully load an ad loadException = ex; // move to the next adpod, ignore for now. We'll log this later if it's relevant } } // always wait for the playing ad to complete before returning if (activeAdTask.IsRunning()) { await activeAdTask; } if (!podPlayed) { VpaidController.TrackErrorUrl(adDocument.Error, Microsoft.Media.Advertising.VpaidController.Error_NoAd); throw new LoadException(new Exception("No ads found.")); } } catch (OperationCanceledException) { if (timeoutToken.IsCancellationRequested) { throw new TimeoutException(); } else { throw; } } catch (Exception ex) { throw new PlayException(ex); } if (loadException != null) { throw loadException; } } catch (Exception ex) { LogError(adSource, ex); primaryTask.TrySetException(ex); throw; } finally { progress.Report(AdStatus.Unloaded); } primaryTask.TrySetResult(null); } } }); return(new PlayAdAsyncResult(primaryTask.Task, secondaryTask)); }