protected void TrackEvent(ActiveAdUnit adUnit, TrackingType eventToTrack) { if (AdTrackingEventOccurred != null) { AdTrackingEventOccurred(this, new AdTrackingEventEventArgs(adUnit.CreativeSource, eventToTrack)); } foreach (var trackingEvent in adUnit.CreativeSource.TrackingEvents.Where(te => te.Type == eventToTrack)) { var url = trackingEvent.Value; switch (trackingEvent.Type) { case TrackingType.FirstQuartile: case TrackingType.Midpoint: case TrackingType.ThirdQuartile: case TrackingType.Complete: var previousEvents = quartileHistory[adUnit]; var currentTime = DateTime.Now; url = url.Replace(Macro_PreviousQuartile, previousEvents.Any() ? ((int)Math.Round(currentTime.Subtract(previousEvents.Last()).TotalSeconds)).ToString() : "0"); previousEvents.Add(currentTime); break; } TrackUrl(url, adUnit.CreativeSource); } }
void OnActivateAd(ActiveAdUnit activeAdUnit, IEnumerable <ICompanionSource> companions, CompanionAdsRequired suggestedCompanionRules) { if (ActivateAdUnit != null) { ActivateAdUnit(this, new ActivateAdUnitEventArgs(activeAdUnit.CreativeSource, activeAdUnit.Player, activeAdUnit.CreativeConcept, activeAdUnit.AdSource, companions, suggestedCompanionRules)); } }
async Task <Task> PlayAdUnitAsync(ActiveAdUnit adUnit, CancellationToken cancellationToken) { #if WINDOWS_PHONE7 // WP doesn't support 3 MediaElements all with a source loaded. Instead of preloading, just wait until we finish the ad. await VpaidController.FinishAdAsync(activeAd, cancellationToken); var finished = true; #else var finished = await VpaidController.PlayAdAsync(adUnit, cancellationToken); #endif if (!finished) { // if approaching end has happened, retain a new task that will run to completion return(TaskHelpers.Create <Task>(async() => { await VpaidController.FinishAdAsync(adUnit, cancellationToken); CleanupAd(adUnit); })); } else { CleanupAd(adUnit); return(null); } }
PreloadOperation LoadAdUnit(ActiveAdUnit adUnit, CancellationToken cancellationToken) { var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); Task <ActiveAdUnit> task; if (loadOperation != null) { if (loadOperation.AdSource != adUnit.AdSource) // cancel active task and wait for it to finish { task = TaskHelpers.Create <Task <ActiveAdUnit> >(async() => { try { await loadOperation.CancelAsync(); } finally { loadOperation = null; } cancellationToken.ThrowIfCancellationRequested(); return(await GetInitializationTask(adUnit, cts.Token)); }); } else { task = loadOperation.Task; } } else { task = GetInitializationTask(adUnit, cts.Token); } return(new PreloadOperation(task, adUnit.AdSource, cts)); }
void OnDeactivateAd(ActiveAdUnit activeAdUnit, Exception error) { if (DeactivateAdUnit != null) { DeactivateAdUnit(this, new DeactivateAdUnitEventArgs(activeAdUnit.CreativeSource, activeAdUnit.Player, activeAdUnit.CreativeConcept, activeAdUnit.AdSource, error)); } }
async Task StartAdUnitAsync(ActiveAdUnit adUnit, Ad ad, CancellationToken cancellationToken) { // start the ad await VpaidController.StartAdAsync(adUnit, cancellationToken); // fire the impression beacon foreach (var url in ad.Impressions) { VpaidController.TrackUrl(url, adUnit.CreativeSource); } }
/// <summary> /// Stops the ad. /// </summary> /// <param name="ad">The ad creative that should stop playing</param> /// <param name="userState">A user state associated with this ad. This will be included with the various events associated with this ad.</param> public async Task StopAdAsync(ActiveAdUnit ad, CancellationToken cancellationToken) { try { await ad.Player.StopAdAsync(cancellationToken); } catch (Exception ex) { OnLog(new ActiveAdUnitLogEventArgs(ad, "VPAID.StopAd Exception: " + ex.Message)); throw ex; } }
public void AddAd(ActiveAdUnit ad) { if (activeAds.ContainsKey(ad.Player)) { throw new Exception("Ad is already added"); } activeAds.Add(ad.Player, ad); trackedProgressEvents.Add(ad, new List <TrackingEvent>()); quartileHistory.Add(ad, new List <DateTime>()); HookupPlayer(ad.Player); }
/// <summary> /// Tells the ad to start. /// </summary> /// <param name="ad">The ad creative that should start playing</param> /// <param name="userState">A user state associated with this ad. This will be included with the various events associated with this ad.</param> public async Task StartAdAsync(ActiveAdUnit ad, double defaultVolume, CancellationToken cancellationToken) { try { ad.Player.AdVolume = defaultVolume; await ad.Player.StartAdAsync(cancellationToken); } catch (Exception ex) { OnLog(new ActiveAdUnitLogEventArgs(ad, "VPAID.StartAd Exception: " + ex.Message)); throw ex; } }
private void TrackProgress(ActiveAdUnit activeAdUnit, TimeSpan timeEllapsed, double percentCompleted) { var trackedEvents = trackedProgressEvents[activeAdUnit]; var eligableTrackingEvents = activeAdUnit.CreativeSource.TrackingEvents.Where(te => te.Type == TrackingType.Progress && te.Offset != null) .Except(trackedEvents) .Where(t => t.Offset.IsAbsolute ? timeEllapsed >= t.Offset.AbsoluteOffset : percentCompleted >= t.Offset.RelativeOffset); foreach (var trackingEvent in eligableTrackingEvents.ToList()) { TrackUrl(trackingEvent.Value, activeAdUnit.CreativeSource); trackedEvents.Add(trackingEvent); } }
async Task <ActiveAdUnit> GetInitializationTask(ActiveAdUnit adUnit, CancellationToken cancellationToken) { try { await VpaidController.InitAdAsync(adUnit, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); } catch { OnUnloadPlayer(adUnit.Player); throw; } return(adUnit); }
void LoadAdUnit(ActiveAdUnit newAdUnit, ICollection <ICreative> creativeSet) { // load companions CompanionAdsRequired required = CompanionAdsRequired.None; IEnumerable <ICompanionSource> companionsToLoad = Enumerable.Empty <ICompanionSource>(); var companions = creativeSet.OfType <CreativeCompanions>().FirstOrDefault(); if (companions != null) { companionsToLoad = companions.Companions.Cast <ICompanionSource>(); required = companions.Required; } else { // VPAID 2.0 supports companions that come from the VPAID player. // these are only to be used with the VAST file contains none. if (newAdUnit.Player is IVpaid2) { var companionData = ((IVpaid2)newAdUnit.Player).AdCompanions; if (!string.IsNullOrEmpty(companionData)) { CreativeCompanions vpaidCompanions; using (var stream = companionData.ToStream()) { vpaidCompanions = AdModelFactory.CreateCompanionsFromVast(stream); } companionsToLoad = vpaidCompanions.Companions.Cast <ICompanionSource>(); required = vpaidCompanions.Required; } } } try { OnActivateAd(newAdUnit, companionsToLoad, required); } catch (Exception ex) { throw new LoadException(ex); } }
/// <summary> /// Loads a creative. This causes VPAID.InitAd to execute. /// </summary> /// <param name="ad">The creative to load</param> /// <param name="bitrate">The current bitrate of the player. This is passed onto the VPAID player which can use it to initialize itself.</param> /// <param name="userState">A user state associated with this ad. This will be included with the various events associated with this ad.</param> public async Task InitAdAsync(ActiveAdUnit ad, Size dimensions, bool isFullScreen, int desiredBitrate, CancellationToken cancellationToken) { try { await ad.Player.InitAdAsync( dimensions.Width, dimensions.Height, isFullScreen? "fullscreen" : "normal", desiredBitrate, ad.CreativeSource.MediaSource, ad.CreativeSource.ExtraInfo, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); } catch (Exception ex) { OnLog(new ActiveAdUnitLogEventArgs(ad, "VPAID.AdInit Exception: " + ex.Message)); throw ex; } }
/// <summary> /// Removes the ad. /// </summary> /// <param name="ad">The ad creative that should be removed.</param> public void RemoveAd(ActiveAdUnit ad) { UnhookPlayer(ad.Player); trackedProgressEvents.Remove(ad); quartileHistory.Remove(ad); activeAds.Remove(ad.Player); if (ad.Player is IDisposable) { try { ((IDisposable)ad.Player).Dispose(); } catch (Exception ex) { OnLog(new ActiveAdUnitLogEventArgs(ad, "VPAID.Dispose Exception: " + ex.Message)); } } if (AdRemoved != null) { AdRemoved(this, new ActiveAdUnitEventArgs(ad)); } }
ActiveAdUnit CreateAdUnit(IEnumerable <ICreative> creativeSet, Ad ad, IAdSource adSource) { CreativeCompanions companions = null; var activeAdUnits = new List <ActiveAdUnit>(); foreach (var creative in creativeSet) { if (creative is CreativeLinear) { var linear = (CreativeLinear)creative; var eligableMediaFiles = new Queue <MediaFile>(PrioritizeMedia(linear.MediaFiles)); ActiveAdUnit chosenAd = null; while (eligableMediaFiles.Any()) { var media = eligableMediaFiles.Dequeue(); var creativeSource = new LinearSource(linear, media); var vpaid = OnLoadPlayer(creativeSource); if (vpaid != null) { if (VpaidController.Handshake(vpaid)) { chosenAd = new ActiveAdUnit(creativeSource, vpaid, ad, adSource); break; } else { OnUnloadPlayer(vpaid); } } } if (chosenAd == null) { throw new LoadException(new Exception("Unable to find a player to play the linear ad.")); } activeAdUnits.Add(chosenAd); } else if (creative is CreativeNonLinears) { bool found = false; var nonLinears = (CreativeNonLinears)creative; foreach (var nonLinear in (nonLinears).NonLinears) { var creativeSource = new NonLinearSource(nonLinear, nonLinears); var vpaid = OnLoadPlayer(creativeSource); if (vpaid != null) { if (VpaidController.Handshake(vpaid)) { activeAdUnits.Add(new ActiveAdUnit(creativeSource, vpaid, ad, adSource)); found = true; break; } else { OnUnloadPlayer(vpaid); } } } if (!found) { throw new LoadException(new Exception("Unable to find a player to play any nonlinear ads.")); } } else if (creative is CreativeCompanions) { companions = (CreativeCompanions)creative; } else /* not supported, ignore */ } { } return(activeAdUnits.FirstOrDefault()); // there should only be one linear or nonlinear ad in the sequenced group. Others are ignored. }
internal ActiveAdUnitEventArgs(ActiveAdUnit activeAdUnit) { ActiveAdUnit = activeAdUnit; }
internal ActiveAdUnitLogEventArgs(ActiveAdUnit activeAdUnit, string message) : base(activeAdUnit) { Message = message; }
public Task InitAdAsync(ActiveAdUnit ad, CancellationToken cancellationToken) { return(base.InitAdAsync(ad, Player.Dimensions, Player.IsFullScreen, Player.CurrentBitrate, cancellationToken)); }
public Task StartAdAsync(ActiveAdUnit ad, CancellationToken cancellationToken) { return(base.StartAdAsync(ad, Player.IsMuted ? 0 : Player.Volume, cancellationToken)); }
void OnActivateAd(ActiveAdUnit activeAdUnit, IEnumerable<ICompanionSource> companions, CompanionAdsRequired suggestedCompanionRules) { if (ActivateAdUnit != null) ActivateAdUnit(this, new ActivateAdUnitEventArgs(activeAdUnit.CreativeSource, activeAdUnit.Player, activeAdUnit.CreativeConcept, activeAdUnit.AdSource, companions, suggestedCompanionRules)); }
void OnDeactivateAd(ActiveAdUnit activeAdUnit, Exception error) { if (DeactivateAdUnit != null) DeactivateAdUnit(this, new DeactivateAdUnitEventArgs(activeAdUnit.CreativeSource, activeAdUnit.Player, activeAdUnit.CreativeConcept, activeAdUnit.AdSource, error)); }
async Task<ActiveAdUnit> GetInitializationTask(ActiveAdUnit adUnit, CancellationToken cancellationToken) { try { await VpaidController.InitAdAsync(adUnit, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); } catch { OnUnloadPlayer(adUnit.Player); throw; } return adUnit; }
ActiveAdUnit CreateAdUnit(IEnumerable<ICreative> creativeSet, Ad ad, IAdSource adSource) { CreativeCompanions companions = null; var activeAdUnits = new List<ActiveAdUnit>(); foreach (var creative in creativeSet) { if (creative is CreativeLinear) { var linear = (CreativeLinear)creative; var eligableMediaFiles = new Queue<MediaFile>(PrioritizeMedia(linear.MediaFiles)); ActiveAdUnit chosenAd = null; while (eligableMediaFiles.Any()) { var media = eligableMediaFiles.Dequeue(); var creativeSource = new LinearSource(linear, media); var vpaid = OnLoadPlayer(creativeSource); if (vpaid != null) { if (VpaidController.Handshake(vpaid)) { chosenAd = new ActiveAdUnit(creativeSource, vpaid, ad, adSource); break; } else { OnUnloadPlayer(vpaid); } } } if (chosenAd == null) throw new LoadException(new Exception("Unable to find a player to play the linear ad.")); activeAdUnits.Add(chosenAd); } else if (creative is CreativeNonLinears) { bool found = false; var nonLinears = (CreativeNonLinears)creative; foreach (var nonLinear in (nonLinears).NonLinears) { var creativeSource = new NonLinearSource(nonLinear, nonLinears); var vpaid = OnLoadPlayer(creativeSource); if (vpaid != null) { if (VpaidController.Handshake(vpaid)) { activeAdUnits.Add(new ActiveAdUnit(creativeSource, vpaid, ad, adSource)); found = true; break; } else { OnUnloadPlayer(vpaid); } } } if (!found) { throw new LoadException(new Exception("Unable to find a player to play any nonlinear ads.")); } } else if (creative is CreativeCompanions) { companions = (CreativeCompanions)creative; } else { /* not supported, ignore */ } } return activeAdUnits.FirstOrDefault(); // there should only be one linear or nonlinear ad in the sequenced group. Others are ignored. }
async Task <ActiveAdUnit> LoadAdUnitAsync(ICollection <ICreative> creativeSet, Ad ad, IAdSource adSource, CancellationToken cancellationToken) { bool preloaded = false; ActiveAdUnit result = null; // check to see if there are any pre-loading ads. if (loadOperation != null) { try { if (loadOperation.AdSource == adSource) { cancellationToken.Register(() => { if (loadOperation != null) { // no need to wait var dummyTask = loadOperation.CancelAsync(); } }); result = await loadOperation.Task; preloaded = true; } else { await loadOperation.CancelAsync(); } } catch { /* ignore, we'll try again regardless of reason, it was just a preload optimization anyway */ } finally { loadOperation = null; } cancellationToken.ThrowIfCancellationRequested(); } if (result == null) { result = CreateAdUnit(creativeSet, ad, adSource); } if (result == null) { throw new LoadException(new Exception("No ad unit could be created")); } if (!preloaded) { // Start initializing the ad. It is OK if there are other ads playing still. loadOperation = LoadAdUnit(result, cancellationToken); try { await loadOperation.Task; } catch (OperationCanceledException) { throw; } catch (Exception ex) { throw new LoadException(ex); } finally { loadOperation = null; } cancellationToken.ThrowIfCancellationRequested(); } 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); }
async Task<Task> PlayAdUnitAsync(ActiveAdUnit adUnit, CancellationToken cancellationToken) { #if WINDOWS_PHONE7 // WP doesn't support 3 MediaElements all with a source loaded. Instead of preloading, just wait until we finish the ad. await VpaidController.FinishAdAsync(activeAd, cancellationToken); var finished = true; #else var finished = await VpaidController.PlayAdAsync(adUnit, cancellationToken); #endif if (!finished) { // if approaching end has happened, retain a new task that will run to completion return TaskHelpers.Create<Task>(async () => { await VpaidController.FinishAdAsync(adUnit, cancellationToken); CleanupAd(adUnit); }); } else { CleanupAd(adUnit); return null; } }
void CleanupAd(ActiveAdUnit adUnit) { OnDeactivateAd(adUnit, null); OnUnloadPlayer(adUnit.Player); VpaidController.RemoveAd(adUnit); }
PreloadOperation LoadAdUnit(ActiveAdUnit adUnit, CancellationToken cancellationToken) { var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); Task<ActiveAdUnit> task; if (loadOperation != null) { if (loadOperation.AdSource != adUnit.AdSource) // cancel active task and wait for it to finish { task = TaskHelpers.Create<Task<ActiveAdUnit>>(async () => { try { await loadOperation.CancelAsync(); } finally { loadOperation = null; } cancellationToken.ThrowIfCancellationRequested(); return await GetInitializationTask(adUnit, cts.Token); }); } else { task = loadOperation.Task; } } else { task = GetInitializationTask(adUnit, cts.Token); } return new PreloadOperation(task, adUnit.AdSource, cts); }
void LoadAdUnit(ActiveAdUnit newAdUnit, ICollection<ICreative> creativeSet) { // load companions CompanionAdsRequired required = CompanionAdsRequired.None; IEnumerable<ICompanionSource> companionsToLoad = Enumerable.Empty<ICompanionSource>(); var companions = creativeSet.OfType<CreativeCompanions>().FirstOrDefault(); if (companions != null) { companionsToLoad = companions.Companions.Cast<ICompanionSource>(); required = companions.Required; } else { // VPAID 2.0 supports companions that come from the VPAID player. // these are only to be used with the VAST file contains none. if (newAdUnit.Player is IVpaid2) { var companionData = ((IVpaid2)newAdUnit.Player).AdCompanions; if (!string.IsNullOrEmpty(companionData)) { CreativeCompanions vpaidCompanions; using (var stream = companionData.ToStream()) { vpaidCompanions = AdModelFactory.CreateCompanionsFromVast(stream); } companionsToLoad = vpaidCompanions.Companions.Cast<ICompanionSource>(); required = vpaidCompanions.Required; } } } try { OnActivateAd(newAdUnit, companionsToLoad, required); } catch (Exception ex) { throw new LoadException(ex); } }
/// <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)); }