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