Skip to main content

Using Bidon As Postbid

Bidon can be used as a Postbid solution for ad mediation to increase fill rates and ad revenue. This documentation provides an example on how to integrate Bidon as a Postbid after LevelPlay.

The provided AdManager class can be used as a complete solution to manage and display ads in your app. However, the shown method is not the only possible solution and can be modified according to your needs.

info

Bidon can also be used with other ad mediation platforms in a similar way.

info

In this example, we use AppsFlyer to log events. You can exclude this logic in the AdHelper class if you do not need it.

Explanation

This section explains how Bidon can be used as a Postbid solution for LevelPlay. The integration involves several components that collaborate to manage and display ads effectively.

Key Components

  1. AdManager
    The AdManager provides a unified API to manage and display ads in your app. It handles the initialization of ad providers and controllers.

  2. PostBid Ad Controllers
    PostBid Ad Controllers manage ad caching, the ad lifecycle, and ad selection. They select the ad with the highest price (eCPM) from LevelPlay and Bidon providers.

    • PostBidBannerAdController
    • PostBidInterstitialAdController
    • PostBidRewardedAdController
  3. LevelPlay Sdk Providers
    These providers handle LevelPlay-specific functionality.

    • LevelPlaySdkProvider
    • LevelPlayBannerAdProvider
    • LevelPlayInterstitialAdProvider
    • LevelPlayRewardedAdProvider
  4. Bidon Sdk Providers
    These providers handle Bidon-specific functionality.

    • BidonSdkProvider
    • BidonBannerAdProvider
    • BidonInterstitialAdProvider
    • BidonRewardedAdProvider

Price Floor Increment Strategy

There are two implemented strategies for incrementing the price floor, provided in the PriceFloorIncrementStrategy enumeration.

  1. Percentage
    Used to try loading the ad by the second ad provider (Bidon) with the price floor
    that is incremented by a certain percentage of the eCPM of the ad loaded by the
    first provider (LevelPlay).
  2. Fixed
    Used to try loading the ad by the second ad provider (Bidon) with the price floor
    that is incremented by a fixed step to the eCPM of the ad loaded by the first
    provider (LevelPlay).

The price floor increment step must align with the selected strategy.
For Percentage strategy the price floor increment step is a percentage.
For Fixed strategy the price floor increment step is a value.

info

You can select the preferred strategy and set price floor increment step for each ad type in the AdConfig class.

Implement PostBid Solution

Step 1: Import and setup LevelPlay Unity Plugin And Bidon Unity Plugin

First of all, you need to import LevelPlay Unity Plugin and Bidon Unity Plugin to your project. Refer to the official documentation for integration instructions:

Step 2: Import PostBid Solution

Here you can download all the required files in the .unitypackage archive.

Step 3: Set IDs

Set LevelPlay IDs and Bidon Keys in AdConfig class.

info

You can find the Bidon keys on the app page in the Bidon Management Console.
For more information, refer to the Getting Started with Bidon section.

public static string LevelPlayAppId = "LEVELPLAY_APP_ID_ANDROID";
public static string LevelPlayBannerAdUnitId => "LEVELPLAY_BANNER_AD_UNIT_ID_ANDROID";
public static string LevelPlayInterstitialAdUnitId => "LEVELPLAY_INTERSTITIAL_AD_UNIT_ID_ANDROID";
public static string LevelPlayRewardedAdUnitId => "LEVELPLAY_REWARDED_AD_UNIT_ID_ANDROID";

public static string BidonAppKey => "BIDON_APP_KEY_ANDROID";
public static string BidonBannerAdAuctionKey => "BIDON_BANNER_AD_AUCTION_KEY_ANDROID";
public static string BidonInterstitialAdAuctionKey => "BIDON_INTERSTITIAL_AD_AUCTION_KEY_ANDROID";
public static string BidonRewardedAdAuctionKey => "BIDON_REWARDED_AD_AUCTION_KEY_ANDROID";
info

You can implement MREC similarly to the Banner ad by creating an MrecAdProvider and following the same logic as the BannerAdProvider.

Step 4: Set Ad Settings (optional)

info

Using Firebase Remote Config may be helpful for setting these parameters, allowing experimentation with different values without needing to update the app.

In AdConfig class you can change Banner refresh interval and postbid settings.

The interval in seconds for refreshing the banner ad. The default value is 10 seconds.

public static int BannerAdRefreshInterval => 10;

Default Price Floor

The default, the price floor is used for loading ads by the second ad provider (Bidon) when the eCPM of the ad loaded by the first provider (LevelPlay) is 0.
The default value of default price floor is 0.02 for Banner, and 0.5 for Interstitial and Rewarded ad types.

public static double DefaultBannerAdPriceFloor => 0.02;
public static double DefaultInterstitialAdPriceFloor => 0.5;
public static double DefaultRewardedAdPriceFloor => 0.5;

Price Floor Increment Strategy

By default the price floor increment strategy is set to Percentage for all ad types.

public static PriceFloorIncrementStrategy RewardedAdPriceFloorIncrementStrategy => PriceFloorIncrementStrategy.Percentage;
public static PriceFloorIncrementStrategy BannerAdPriceFloorIncrementStrategy => PriceFloorIncrementStrategy.Percentage;
public static PriceFloorIncrementStrategy InterstitialAdPriceFloorIncrementStrategy => PriceFloorIncrementStrategy.Percentage;
Make sure you set the price floor increment step that corresponds the chosen strategy.

Price Floor Increment Step

By default, the price floor increment strategy is set to Percentage for all ad types.

Example

In case the ad price floor increment step is set to 10 and the first ad provider (LevelPlay) have loaded an ad with a price of $10

  • for Percentage increment strategy the second ad provider (Bidon) will try to load the ad with price of $10 + 10% = $11.
  • for Fixed increment strategy the second ad provider (Bidon) will try to load the ad with price of $10 + $10 = $20.
public static double BannerAdPriceFloorIncrementStep => 10;
public static double InterstitialAdPriceFloorIncrementStep => 10;
public static double RewardedAdPriceFloorIncrementStep => 10;

Step 5: Utilize AdManager

You need to initialize the AdManager before using it. Once initialized, you can display ads.

Initialize AdManager

AdManager.Instance.Init();

You can use OnInitializedEvent to know when the AdManager is initialized.

AdManager.Instance.OnInitializedEvent += () => Debug.Log("AdManager initialized");

Show Banner Ad

AdManager.Instance.ShowBannerAd();

Hide Banner Ad

AdManager.Instance.HideBannerAd();

Show Interstitial Ad

AdManager.Instance.ShowInterstitialAd(placement);

Show Rewarded Video Ad

AdManager.Instance.ShowRewardedAd(placement);

Check If Interstitial Ad Available

AdManager.Instance.IsInterstitialAdAvailable(placement);

Check if Rewarded Ad Available

AdManager.Instance.IsRewardedAdAvailable();

Callbacks

There is a set of base callbacks that you can use. If you need additional callbacks, you can add them in a similar way. LevelPlay and Bidon provide additional callbacks, allowing you to expand the provided ad manager to suit your needs.

AdManager.Instance.OnInterstitialAdShowFailedEvent += () => Debug.Log($"Interstitial ad show failed");
AdManager.Instance.OnInterstitialAdClosedEvent += () => Debug.Log("Interstitial ad closed");

AdManager.Instance.OnRewardedAdShowFailedEvent += () => Debug.Log($"Rewarded ad show failed");
AdManager.Instance.OnRewardedAdFinishedEvent += () => Debug.Log($"Rewarded ad finished");
AdManager.Instance.OnRewardedAdClosedEvent += () => Debug.Log("Rewarded ad closed");

Content Of PostBid Solution

The PostBid solution is provided as a .unitypackage file, which you can download from step 2.

AdConfig

Set LevelPlay's and Bidon's IDs in the AdConfig class. Also, you can change Banner refresh interval and postbid settings.

AdConfig.cs
public static class AdConfig
{
#if UNITY_ANDROID
public static string LevelPlayAppId = "LEVELPLAY_APP_ID_ANDROID";
public static string LevelPlayBannerAdUnitId => "LEVELPLAY_BANNER_AD_UNIT_ID_ANDROID";
public static string LevelPlayInterstitialAdUnitId => "LEVELPLAY_INTERSTITIAL_AD_UNIT_ID_ANDROID";
public static string LevelPlayRewardedAdUnitId => "LEVELPLAY_REWARDED_AD_UNIT_ID_ANDROID";

public static string BidonAppKey => "BIDON_APP_KEY_ANDROID";
public static string BidonBannerAdAuctionKey => "BIDON_BANNER_AD_AUCTION_KEY_ANDROID";
public static string BidonInterstitialAdAuctionKey => "BIDON_INTERSTITIAL_AD_AUCTION_KEY_ANDROID";
public static string BidonRewardedAdAuctionKey => "BIDON_REWARDED_AD_AUCTION_KEY_ANDROID";
#else
public static string LevelPlayAppId = "LEVELPLAY_APP_ID_IOS";
public static string LevelPlayBannerAdUnitId => "LEVELPLAY_BANNER_AD_UNIT_ID_IOS";
public static string LevelPlayInterstitialAdUnitId => "LEVELPLAY_INTERSTITIAL_AD_UNIT_ID_IOS";
public static string LevelPlayRewardedAdUnitId => "LEVELPLAY_REWARDED_AD_UNIT_ID_IOS";

public static string BidonAppKey => "BIDON_APP_KEY_IOS";
public static string BidonBannerAdAuctionKey => "BIDON_BANNER_AD_AUCTION_KEY_IOS";
public static string BidonInterstitialAdAuctionKey => "BIDON_INTERSTITIAL_AD_AUCTION_KEY_IOS";
public static string BidonRewardedAdAuctionKey => "BIDON_REWARDED_AD_AUCTION_KEY_IOS";
#endif

public static int BannerAdRefreshInterval => 10;
public static double DefaultBannerAdPriceFloor => 0.02;
public static double DefaultInterstitialAdPriceFloor => 0.5;
public static double DefaultRewardedAdPriceFloor => 0.5;
public static PriceFloorIncrementStrategy RewardedAdPriceFloorIncrementStrategy => PriceFloorIncrementStrategy.Percentage;
public static PriceFloorIncrementStrategy BannerAdPriceFloorIncrementStrategy => PriceFloorIncrementStrategy.Percentage;
public static PriceFloorIncrementStrategy InterstitialAdPriceFloorIncrementStrategy => PriceFloorIncrementStrategy.Percentage;
public static double BannerAdPriceFloorIncrementStep => 10;
public static double InterstitialAdPriceFloorIncrementStep => 10;
public static double RewardedAdPriceFloorIncrementStep => 10;
}


AdManager

The AdManager class is used by the app to show ads when needed. Under the hood, it creates and manages ad providers and ad controllers.

AdManager.cs
using System;
using UnityEngine;

public class AdManager : MonoBehaviour
{
private static AdManager _instance;
private static readonly object _lock = new object();

public static AdManager Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
var gameObject = new GameObject(nameof(AdManager));
_instance = gameObject.AddComponent<AdManager>();
DontDestroyOnLoad(gameObject);
}
}
}
return _instance;
}
}
public event Action OnInitializedEvent;

public event Action OnInterstitialAdShowFailedEvent;
public event Action OnInterstitialAdClosedEvent;

public event Action OnRewardedAdShowFailedEvent;
public event Action OnRewardedAdFinishedEvent;
public event Action OnRewardedAdClosedEvent;

private bool IsInitialized { get; set; }

private bool _isFirstSdkInitialized;
private bool _isSecondSdkInitialized;

private ISdkProvider _firstSdkProvider;
private ISdkProvider _secondSdkProvider;

private IBannerAdController _bannerAdController;
private IRewardedAdController _rewardedAdController;
private IInterstitialAdController _interstitialAdController;

public void Init()
{
AdHelper.Log("[AdManager] [Method] Init()");

_firstSdkProvider = new LevelPlaySdkProvider();
_firstSdkProvider.OnInitialized += (_, _) => _isFirstSdkInitialized = true;
_firstSdkProvider.Initialize();

_secondSdkProvider = new BidonSdkProvider();
_secondSdkProvider.OnInitialized += (_, _) => _isSecondSdkInitialized = true;
_secondSdkProvider.Initialize();
}

private void Update()
{
if (!IsInitialized && _isFirstSdkInitialized && _isSecondSdkInitialized)
{
IsInitialized = true;

_bannerAdController = new PostBidBannerAdController(_firstSdkProvider?.GetBannerAdProvider(), _secondSdkProvider?.GetBannerAdProvider());
_interstitialAdController = new PostBidInterstitialAdController(_firstSdkProvider?.GetInterstitialAdProvider(), _secondSdkProvider?.GetInterstitialAdProvider());
_rewardedAdController = new PostBidRewardedAdController(_firstSdkProvider?.GetRewardedAdProvider(), _secondSdkProvider?.GetRewardedAdProvider());

_interstitialAdController.OnAdShowFailed += OnInterstitialAdShowFailed;
_interstitialAdController.OnAdClosed += OnInterstitialAdClosed;

_rewardedAdController.OnAdShowFailed += OnRewardedAdShowFailed;
_rewardedAdController.OnAdClosed += OnRewardedAdClosed;

OnInitializedEvent?.Invoke();
}
}

public void ShowBannerAd()
{
AdHelper.Log("[AdManager] [Method] ShowBanner()");

_bannerAdController?.ShowBanner();
}

public void HideBannerAd()
{
AdHelper.Log("[AdManager] [Method] HideBanner()");

_bannerAdController?.HideBanner();
}

public void ShowInterstitialAd(string placement)
{
if (IsInterstitialAdAvailable(placement))
{
AdHelper.Log($"[AdManager] [Method] ShowInterstitial(placement: {placement})");

_interstitialAdController?.ShowInterstitial(placement);
}
}

public void ShowRewardedAd(string placement)
{
if (IsRewardedAdAvailable())
{
AdHelper.Log($"[AdManager] [Method] ShowRewardedAd(placement: {placement})");

_rewardedAdController?.ShowRewarded(placement);
}
}

public bool IsRewardedAdAvailable()
{
AdHelper.Log("[AdManager] [Method] IsRewardedAdAvailable()");

return _rewardedAdController?.IsLoaded() ?? false;
}

public bool IsInterstitialAdAvailable(string placement)
{
AdHelper.Log($"[AdManager] [Method] IsInterstitialAdAvailable(placement: {placement})");

return Application.internetReachability != NetworkReachability.NotReachable
&& (_interstitialAdController?.IsLoaded() ?? false);
}

private void OnInterstitialAdShowFailed(object sender, EventArgs e)
{
AdHelper.Log("[AdManager] OnInterstitialAdShowFailedEvent");

OnInterstitialAdShowFailedEvent?.Invoke();
}

private void OnInterstitialAdClosed(object sender, EventArgs e)
{
AdHelper.Log("[AdManager] OnInterstitialAdClosedEvent");

OnInterstitialAdClosedEvent?.Invoke();
}

private void OnRewardedAdShowFailed(object sender, EventArgs e)
{
AdHelper.Log("[AdManager] OnRewardedAdShowFailedEvent()");

OnRewardedAdShowFailedEvent?.Invoke();
}

private void OnRewardedAdClosed(object sender, bool shouldReward)
{
AdHelper.Log($"[AdManager] OnRewardedAdClosedEvent(bool finished:{shouldReward})");

if (shouldReward) OnRewardedAdFinishedEvent?.Invoke();
OnRewardedAdClosedEvent?.Invoke();
}
}

AdHelper

In AdHelper class you can set refresh interval, change ecpm rounding, choose price floor increment strategy, and log ad revenue via AppsFlyer.

AdHelper.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
using AppsFlyerSDK;

public static class AdHelper
{
public static double GetRoundedEcpm(double ecpm) => Math.Round(ecpm, 10);

public static double GetIncrementedPriceFloor(double loadedEcpm, double defaultPriceFloor, PriceFloorIncrementStrategy strategy, double step)
{
if (loadedEcpm <= 0) return defaultPriceFloor;

return strategy switch
{
PriceFloorIncrementStrategy.Fixed => loadedEcpm + step,
PriceFloorIncrementStrategy.Percentage => loadedEcpm + loadedEcpm * (step / 100),
};
}

public static void Log(string message)
{
if (!Debug.isDebugBuild) return;
Debug.Log($"[Ads] {message}");
}

public static void LogError(string message) => Debug.LogError($"[Ads] {message}");

public static void LogAdRevenue(AdInfo info)
{
if (AppsFlyer.isSDKStopped()) return;

Log($"[AdHelper] [Method] LogAdRevenue(adType: {info.AdType})");

var additionalParams = new Dictionary<string, string>
{
{ "ad_type", info.AdType },
{ "ad_unit", info.AdUnitId },
{ "mediation_id", info.MediationId },
{ "network_ad_unit", info.NetworkAdUnitId },
{ "placement", info.Placement },
{ "revenue_precision", info.RevenuePrecision },
{ "session_id", SessionController.Instance.SessionUuid },
{ "session_uuid", SessionController.Instance.SessionUuid },
{ "session_uptime", SessionController.Instance.SessionUptime.ToString() }
};

var adRevenueData = new AFAdRevenueData(info.NetworkName, MediationNetwork.IronSource, info.CurrencyCode, info.Revenue);
AppsFlyer.logAdRevenue(adRevenueData, additionalParams);

additionalParams.Add("currency", info.CurrencyCode);
additionalParams.Add("network_name", info.NetworkName);
additionalParams.Add("revenue", info.Revenue.ToString(CultureInfo.InvariantCulture));

AppsFlyer.sendEvent("impression", additionalParams);
}
}


PriceFloorIncrementStrategy

PriceFloorIncrementStrategy enumeration contain two price floor increment strategies that you can choose.

PriceFloorIncrementStrategy.cs
public enum PriceFloorIncrementStrategy
{
Fixed = 1,
Percentage,
}

AdInfo

The AdInfo class is a DTO to pass various data to AdHelper.LogAdRevenue method.

AdInfo.cs
using System;

[Serializable]
public class AdInfo
{
public string AdType;

public string AdUnitId;

public string CurrencyCode;

public string MediationId;

public string NetworkAdUnitId;

public string NetworkName;

public string Placement = String.Empty;

public double Revenue;

public string RevenuePrecision;

public string ToJsonString(bool isPretty = false) => UnityEngine.JsonUtility.ToJson(this, isPretty);
}

AdRevenueReceivedEventArgs

This class is used by EventHandler to pass AdInfo object.

AdRevenueReceivedEventArgs.cs
using System;

public class AdRevenueReceivedEventArgs : EventArgs
{
public AdInfo Info { get; }

public AdRevenueReceivedEventArgs(AdInfo info) { Info = info; }
}


ISdkProvider

An interface for different SdkProviders is used for DI.

ISdkProvider.cs
using System;

public interface ISdkProvider
{
event EventHandler OnInitialized;
void Initialize();
IBannerAdProvider GetBannerAdProvider();
IRewardedAdProvider GetRewardedAdProvider();
IInterstitialAdProvider GetInterstitialAdProvider();
}


IAdProvider

Base interface for AdProviders.

IAdProvider.cs
using System;

public interface IAdProvider
{
string Name { get; }
event EventHandler<double> OnAdLoaded;
event EventHandler<string> OnAdLoadFailed;
event EventHandler OnAdShown;
event EventHandler<AdRevenueReceivedEventArgs> OnAdRevenueReceived;
void NotifyLoss(string winnerDemandId, double ecpm);
void NotifyWin();
}


IBannerAdProvider

An interface for different BannerAdProviders is used for DI.

IBannerAdProvider.cs
public interface IBannerAdProvider : IAdProvider
{
void InstantiateBannerAd();
void LoadBannerAd(double priceFloor = 0d);
void ShowBannerAd();
void HideBannerAd();
}


IRewardedAdProvider

An interface for different RewardedAdProviders is used for DI.

IRewardedAdProvider.cs
using System;

public interface IRewardedAdProvider : IAdProvider
{
event EventHandler<string> OnAdShowFailed;
event EventHandler<bool> OnAdClosed;
void LoadRewardedAd(double priceFloor = 0d);
void ShowRewardedAd(string placementName);
}


IInterstitialAdProvider

An interface for different InterstitialAdProviders is used for DI.

IInterstitialAdProvider.cs
using System;

public interface IInterstitialAdProvider : IAdProvider
{
event EventHandler<string> OnAdShowFailed;
event EventHandler OnAdClosed;
void LoadInterstitialAd(double priceFloor = 0d);
void ShowInterstitialAd(string placementName);
}


AdProvider

Enum is used by AdControllers to check whose ad is being displayed.

AdProvider.cs
public enum AdProvider
{
First,
Second,
}


BidonSdkProvider

Manages Bidon SDK initialization, and also creates BidonAdProviders.

BidonSdkProvider.cs
using System;
using UnityEngine;
using Bidon.Mediation;

public class BidonSdkProvider : ISdkProvider
{
public event EventHandler OnInitialized;

private IBannerAdProvider _bannerAdProvider;
private IRewardedAdProvider _rewardedAdProvider;
private IInterstitialAdProvider _interstitialAdProvider;

#if UNITY_IOS
private const string BidonAppKey = "BIDON_APP_KEY_IOS";
#else
private const string BidonAppKey = "BIDON_APP_KEY_ANDROID";
#endif

public void Initialize()
{
AdHelper.Log("[BidonSdkProvider] [Method] Initialize()");

BidonSdk.Instance.SetLogLevel(Debug.isDebugBuild ? BidonLogLevel.Verbose : BidonLogLevel.Off);
BidonSdk.Instance.SetBaseUrl("https://b.appbaqend.com");
BidonSdk.Instance.RegisterDefaultAdapters();

BidonSdk.Instance.OnInitializationFinished += (_, _) => OnInitialized?.Invoke(this, EventArgs.Empty);

BidonSdk.Instance.Initialize(BidonAppKey);
}

public IBannerAdProvider GetBannerAdProvider()
{
AdHelper.Log("[BidonSdkProvider] [Method] GetBannerAdProvider()");

return _bannerAdProvider ??= new BidonBannerAdProvider(RemoteConfig.BidonBannerAdAuctionKey);
}

public IRewardedAdProvider GetRewardedAdProvider()
{
AdHelper.Log("[BidonSdkProvider] [Method] GetRewardedAdProvider()");

return _rewardedAdProvider ??= new BidonRewardedAdProvider(RemoteConfig.BidonRewardedAdAuctionKey);
}

public IInterstitialAdProvider GetInterstitialAdProvider()
{
AdHelper.Log("[BidonSdkProvider] [Method] GetInterstitialAdProvider()");

return _interstitialAdProvider ??= new BidonInterstitialAdProvider(RemoteConfig.BidonInterstitialAdAuctionKey);
}
}


BidonBannerAdProvider

Implements Bidon BannerAd logic.

BidonBannerAdProvider.cs
using System;
using Bidon.Mediation;

public class BidonBannerAdProvider : IBannerAdProvider
{
public string Name { get => "bidon"; }

public event EventHandler<double> OnAdLoaded;
public event EventHandler<string> OnAdLoadFailed;
public event EventHandler OnAdShown;
public event EventHandler<AdRevenueReceivedEventArgs> OnAdRevenueReceived;

private string AuctionKey { get; }

private BidonBannerAd _bidonBannerAd;
private BidonBannerAd _bidonBannerAdOld;

public BidonBannerAdProvider(string auctionKey)
{
AdHelper.Log("[BidonBannerAdProvider] [Constructor] BidonBannerAdProvider()");

AuctionKey = auctionKey;

InstantiateBannerAd();
}

public void InstantiateBannerAd()
{
AdHelper.Log("[BidonBannerAdProvider] [Method] InstantiateBannerAd()");

if (_bidonBannerAd != null)
{
UnsubscribeFromBannerAdEvents(_bidonBannerAdOld);
_bidonBannerAdOld?.Dispose();
_bidonBannerAdOld = _bidonBannerAd;
}

_bidonBannerAd = new BidonBannerAd(AuctionKey);
_bidonBannerAd?.SetFormat(BidonBannerFormat.Banner);
_bidonBannerAd?.SetPredefinedPosition(BidonBannerPosition.HorizontalBottom);
SubscribeToBannerAdEvents(_bidonBannerAd);
}

public void LoadBannerAd(double priceFloor = 0d)
{
AdHelper.Log($"[BidonBannerAdProvider] [Method] LoadBannerAd(priceFloor: {priceFloor})");

_bidonBannerAd?.Load(priceFloor);
}

public void NotifyLoss(string winnerDemandId, double ecpm)
{
AdHelper.Log($"[BidonBannerAdProvider] [Method] NotifyLoss(winner: {winnerDemandId}, ecpm: {ecpm})");

_bidonBannerAd?.NotifyLoss(winnerDemandId, ecpm);
}

public void NotifyWin()
{
AdHelper.Log("[BidonBannerAdProvider] [Method] NotifyWin()");

_bidonBannerAd?.NotifyWin();
}

public void ShowBannerAd()
{
AdHelper.Log("[BidonBannerAdProvider] [Method] ShowBannerAd()");

_bidonBannerAd?.SetExtraData("placement", "bidon");
_bidonBannerAd?.Show();
}

public void HideBannerAd()
{
AdHelper.Log("[BidonBannerAdProvider] [Method] HideBannerAd()");

_bidonBannerAd?.Hide();
}

private void SubscribeToBannerAdEvents(BidonBannerAd ad)
{
if (ad == null) return;

AdHelper.Log("[BidonBannerAdProvider] [Method] SubscribeToBannerAdEvents()");

ad.OnAdLoaded += OnBidonBannerAdLoaded;
ad.OnAdLoadFailed += OnBidonBannerAdLoadFailed;
ad.OnAdShown += OnBidonBannerAdShown;
ad.OnAdRevenueReceived += OnBidonBannerAdRevenueReceived;
}

private void UnsubscribeFromBannerAdEvents(BidonBannerAd ad)
{
if (ad == null) return;

AdHelper.Log("[BidonBannerAdProvider] [Method] UnsubscribeFromBannerAdEvents()");

ad.OnAdLoaded -= OnBidonBannerAdLoaded;
ad.OnAdLoadFailed -= OnBidonBannerAdLoadFailed;
ad.OnAdShown -= OnBidonBannerAdShown;
ad.OnAdRevenueReceived -= OnBidonBannerAdRevenueReceived;
}

private void OnBidonBannerAdLoaded(object sender, BidonAdLoadedEventArgs args)
{
double price = AdHelper.GetRoundedEcpm(args.Ad.Price);
AdHelper.Log($"[BidonBannerAdProvider] [Callback] OnAdLoaded(price: {price})");

OnAdLoaded?.Invoke(this, price);
}

private void OnBidonBannerAdLoadFailed(object sender, BidonAdLoadFailedEventArgs args)
{
AdHelper.Log($"[BidonBannerAdProvider] [Callback] OnAdLoadFailed(cause: {args.Cause})");

OnAdLoadFailed?.Invoke(this, args.Cause.ToString());
}

private void OnBidonBannerAdShown(object sender, BidonAdShownEventArgs args)
{
AdHelper.Log("[BidonBannerAdProvider] [Callback] OnAdShown()");

OnAdShown?.Invoke(this, EventArgs.Empty);
}

private void OnBidonBannerAdRevenueReceived(object sender, BidonAdRevenueReceivedEventArgs args)
{
AdHelper.Log("[BidonBannerAdProvider] [Callback] OnAdRevenueReceived()");

var info = new AdInfo
{
AdType = args.Ad.AdType.ToString(),
AdUnitId = AuctionKey,
CurrencyCode = args.AdValue.CurrencyCode,
MediationId = Name,
NetworkAdUnitId = args.Ad.AdUnit.Uid,
NetworkName = args.Ad.NetworkName,
Revenue = args.AdValue.AdRevenue,
RevenuePrecision = args.AdValue.RevenuePrecision.ToString(),
};
OnAdRevenueReceived?.Invoke(this, new AdRevenueReceivedEventArgs(info));
}
}


BidonRewardedAdProvider

Implements Bidon RewardedAd logic.

BidonRewardedAdProvider.cs
using System;
using Bidon.Mediation;

public class BidonRewardedAdProvider : IRewardedAdProvider
{
public string Name { get => "bidon"; }

public event EventHandler<double> OnAdLoaded;
public event EventHandler<string> OnAdLoadFailed;
public event EventHandler OnAdShown;
public event EventHandler<string> OnAdShowFailed;
public event EventHandler<bool> OnAdClosed;
public event EventHandler<AdRevenueReceivedEventArgs> OnAdRevenueReceived;

private string AuctionKey { get; }

private BidonRewardedAd _bidonRewardedAd;
private BidonRewardedAd _bidonRewardedAdOld;

public BidonRewardedAdProvider(string auctionKey)
{
AdHelper.Log("[BidonRewardedAdProvider] [Constructor] BidonRewardedAdProvider()");

AuctionKey = auctionKey;

InstantiateRewardedAd();
}

public void LoadRewardedAd(double priceFloor = 0d)
{
AdHelper.Log($"[BidonRewardedAdProvider] [Method] LoadRewardedAd(priceFloor: {priceFloor})");

_bidonRewardedAd?.Load(priceFloor);
}

public void NotifyLoss(string winnerDemandId, double ecpm)
{
AdHelper.Log($"[BidonRewardedAdProvider] [Method] NotifyLoss(winner: {winnerDemandId}, ecpm: {ecpm})");

_bidonRewardedAd?.NotifyLoss(winnerDemandId, ecpm);
InstantiateRewardedAd();
}

public void NotifyWin()
{
AdHelper.Log("[BidonRewardedAdProvider] [Method] NotifyWin()");

_bidonRewardedAd?.NotifyWin();
}

public void ShowRewardedAd(string placementName)
{
AdHelper.Log("[BidonRewardedAdProvider] [Method] ShowRewardedAd()");

_bidonRewardedAd?.SetExtraData("placement", placementName);
_bidonRewardedAd?.Show();
}

private void InstantiateRewardedAd()
{
AdHelper.Log("[BidonRewardedAdProvider] [Method] InstantiateRewardedAd()");

if (_bidonRewardedAd != null)
{
UnsubscribeFromRewardedAdEvents(_bidonRewardedAdOld);
_bidonRewardedAdOld?.Dispose();
_bidonRewardedAdOld = _bidonRewardedAd;
}

_bidonRewardedAd = new BidonRewardedAd(AuctionKey);
SubscribeToRewardedAdEvents(_bidonRewardedAd);
}

private void SubscribeToRewardedAdEvents(BidonRewardedAd ad)
{
if (ad == null) return;

AdHelper.Log("[BidonRewardedAdProvider] [Method] SubscribeToRewardedAdEvents()");

ad.OnAdLoaded += OnBidonRewardedAdLoaded;
ad.OnAdLoadFailed += OnBidonRewardedAdLoadFailed;
ad.OnAdShown += OnBidonRewardedAdShown;
ad.OnAdShowFailed += OnBidonRewardedAdShowFailed;
ad.OnAdClosed += OnBidonRewardedAdClosed;
ad.OnAdRevenueReceived += OnBidonRewardedAdRevenueReceived;
}

private void UnsubscribeFromRewardedAdEvents(BidonRewardedAd ad)
{
if (ad == null) return;

AdHelper.Log("[BidonRewardedAdProvider] [Method] UnsubscribeFromRewardedAdEvents()");

ad.OnAdLoaded -= OnBidonRewardedAdLoaded;
ad.OnAdLoadFailed -= OnBidonRewardedAdLoadFailed;
ad.OnAdShown -= OnBidonRewardedAdShown;
ad.OnAdShowFailed -= OnBidonRewardedAdShowFailed;
ad.OnAdClosed -= OnBidonRewardedAdClosed;
ad.OnAdRevenueReceived -= OnBidonRewardedAdRevenueReceived;
}

private void OnBidonRewardedAdLoaded(object sender, BidonAdLoadedEventArgs args)
{
double price = AdHelper.GetRoundedEcpm(args.Ad.Price);
AdHelper.Log($"[BidonRewardedAdProvider] [Callback] OnAdLoaded(ecpm: {price})");

OnAdLoaded?.Invoke(this, price);
}

private void OnBidonRewardedAdLoadFailed(object sender, BidonAdLoadFailedEventArgs args)
{
AdHelper.Log($"[BidonRewardedAdProvider] [Callback] OnAdLoadFailed(cause: {args.Cause})");

OnAdLoadFailed?.Invoke(this, args.Cause.ToString());
}

private void OnBidonRewardedAdShown(object sender, BidonAdShownEventArgs args)
{
AdHelper.Log("[BidonRewardedAdProvider] [Callback] OnAdShown()");

OnAdShown?.Invoke(this, EventArgs.Empty);
}

private void OnBidonRewardedAdShowFailed(object sender, BidonAdShowFailedEventArgs args)
{
AdHelper.Log($"[BidonRewardedAdProvider] [Callback] OnAdShowFailed(cause: {args.Cause})");

InstantiateRewardedAd();
OnAdShowFailed?.Invoke(this, args.Cause.ToString());
}

private void OnBidonRewardedAdClosed(object sender, BidonAdClosedEventArgs args)
{
AdHelper.Log("[BidonRewardedAdProvider] [Callback] OnAdClosed()");

InstantiateRewardedAd();
OnAdClosed?.Invoke(this, true);
}

private void OnBidonRewardedAdRevenueReceived(object sender, BidonAdRevenueReceivedEventArgs args)
{
AdHelper.Log("[BidonRewardedAdProvider] [Callback] OnAdRevenueReceived()");

var info = new AdInfo
{
AdType = args.Ad.AdType.ToString(),
AdUnitId = AuctionKey,
CurrencyCode = args.AdValue.CurrencyCode,
MediationId = Name,
NetworkAdUnitId = args.Ad.AdUnit.Uid,
NetworkName = args.Ad.NetworkName,
Revenue = args.AdValue.AdRevenue,
RevenuePrecision = args.AdValue.RevenuePrecision.ToString(),
};
OnAdRevenueReceived?.Invoke(this, new AdRevenueReceivedEventArgs(info));
}
}


BidonInterstitialAdProvider

Implements Bidon InterstitialAd logic.

BidonInterstitialAdProvider.cs
using System;
using Bidon.Mediation;

public class BidonInterstitialAdProvider : IInterstitialAdProvider
{
public string Name { get => "bidon"; }

public event EventHandler<double> OnAdLoaded;
public event EventHandler<string> OnAdLoadFailed;
public event EventHandler OnAdShown;
public event EventHandler<string> OnAdShowFailed;
public event EventHandler OnAdClosed;
public event EventHandler<AdRevenueReceivedEventArgs> OnAdRevenueReceived;

private string AuctionKey { get; }

private BidonInterstitialAd _bidonInterstitialAd;
private BidonInterstitialAd _bidonInterstitialAdOld;

public BidonInterstitialAdProvider(string auctionKey)
{
AdHelper.Log("[BidonInterstitialAdProvider] [Constructor] BidonInterstitialAdProvider()");

AuctionKey = auctionKey;

InstantiateInterstitialAd();
}

public void LoadInterstitialAd(double priceFloor = 0d)
{
AdHelper.Log($"[BidonInterstitialAdProvider] [Method] LoadInterstitialAd(priceFloor: {priceFloor})");

_bidonInterstitialAd?.Load(priceFloor);
}

public void NotifyLoss(string winnerDemandId, double ecpm)
{
AdHelper.Log($"[BidonInterstitialAdProvider] [Method] NotifyLoss(winner: {winnerDemandId}, ecpm: {ecpm})");

_bidonInterstitialAd?.NotifyLoss(winnerDemandId, ecpm);
InstantiateInterstitialAd();
}

public void NotifyWin()
{
AdHelper.Log("[BidonInterstitialAdProvider] [Method] NotifyWin()");

_bidonInterstitialAd?.NotifyWin();
}

public void ShowInterstitialAd(string placementName)
{
AdHelper.Log("[BidonInterstitialAdProvider] [Method] ShowInterstitialAd()");

_bidonInterstitialAd?.SetExtraData("placement", placementName);
_bidonInterstitialAd?.Show();
}

private void InstantiateInterstitialAd()
{
AdHelper.Log("[BidonInterstitialAdProvider] [Method] InstantiateInterstitialAd()");

if (_bidonInterstitialAd != null)
{
UnsubscribeFromInterstitialAdEvents(_bidonInterstitialAdOld);
_bidonInterstitialAdOld?.Dispose();
_bidonInterstitialAdOld = _bidonInterstitialAd;
}

_bidonInterstitialAd = new BidonInterstitialAd(AuctionKey);
SubscribeToInterstitialAdEvents(_bidonInterstitialAd);
}

private void SubscribeToInterstitialAdEvents(BidonInterstitialAd ad)
{
if (ad == null) return;

AdHelper.Log("[BidonInterstitialAdProvider] [Method] SubscribeToInterstitialAdEvents()");

ad.OnAdLoaded += OnBidonInterstitialAdLoaded;
ad.OnAdLoadFailed += OnBidonInterstitialAdLoadFailed;
ad.OnAdShown += OnBidonInterstitialAdShown;
ad.OnAdShowFailed += OnBidonInterstitialAdShowFailed;
ad.OnAdClosed += OnBidonInterstitialAdClosed;
ad.OnAdRevenueReceived += OnBidonInterstitialAdRevenueReceived;
}

private void UnsubscribeFromInterstitialAdEvents(BidonInterstitialAd ad)
{
if (ad == null) return;

AdHelper.Log("[BidonInterstitialAdProvider] [Method] UnsubscribeFromInterstitialAdEvents()");

ad.OnAdLoaded -= OnBidonInterstitialAdLoaded;
ad.OnAdLoadFailed -= OnBidonInterstitialAdLoadFailed;
ad.OnAdShown -= OnBidonInterstitialAdShown;
ad.OnAdShowFailed -= OnBidonInterstitialAdShowFailed;
ad.OnAdClosed -= OnBidonInterstitialAdClosed;
ad.OnAdRevenueReceived -= OnBidonInterstitialAdRevenueReceived;
}

private void OnBidonInterstitialAdLoaded(object sender, BidonAdLoadedEventArgs args)
{
double price = AdHelper.GetRoundedEcpm(args.Ad.Price);
AdHelper.Log($"[BidonInterstitialAdProvider] [Callback] OnAdLoaded(ecpm: {price})");

OnAdLoaded?.Invoke(this, price);
}

private void OnBidonInterstitialAdLoadFailed(object sender, BidonAdLoadFailedEventArgs args)
{
AdHelper.Log($"[BidonInterstitialAdProvider] [Callback] OnAdLoadFailed(cause: {args.Cause})");

OnAdLoadFailed?.Invoke(this, args.Cause.ToString());
}

private void OnBidonInterstitialAdShown(object sender, BidonAdShownEventArgs args)
{
AdHelper.Log("[BidonInterstitialAdProvider] [Callback] OnAdShown()");

OnAdShown?.Invoke(this, EventArgs.Empty);
}

private void OnBidonInterstitialAdShowFailed(object sender, BidonAdShowFailedEventArgs args)
{
AdHelper.Log($"[BidonInterstitialAdProvider] [Callback] OnAdShowFailed(cause: {args.Cause})");

InstantiateInterstitialAd();
OnAdShowFailed?.Invoke(this, args.Cause.ToString());
}

private void OnBidonInterstitialAdClosed(object sender, BidonAdClosedEventArgs args)
{
AdHelper.Log("[BidonInterstitialAdProvider] [Callback] OnAdClosed()");

InstantiateInterstitialAd();
OnAdClosed?.Invoke(this, EventArgs.Empty);
}

private void OnBidonInterstitialAdRevenueReceived(object sender, BidonAdRevenueReceivedEventArgs args)
{
AdHelper.Log("[BidonInterstitialAdProvider] [Callback] OnAdRevenueReceived()");

var info = new AdInfo
{
AdType = args.Ad.AdType.ToString(),
AdUnitId = AuctionKey,
CurrencyCode = args.AdValue.CurrencyCode,
MediationId = Name,
NetworkAdUnitId = args.Ad.AdUnit.Uid,
NetworkName = args.Ad.NetworkName,
Revenue = args.AdValue.AdRevenue,
RevenuePrecision = args.AdValue.RevenuePrecision.ToString(),
};
OnAdRevenueReceived?.Invoke(this, new AdRevenueReceivedEventArgs(info));
}
}


LevelPlaySdkProvider

Manages LevelPlay SDK initialization, and also creates LevelPlayAdProviders.

LevelPlaySdkProvider.cs
using System;
using Unity.Services.LevelPlay;

public class LevelPlaySdkProvider : ISdkProvider
{
public event EventHandler OnInitialized;

private IBannerAdProvider _bannerAdProvider;
private IRewardedAdProvider _rewardedAdProvider;
private IInterstitialAdProvider _interstitialAdProvider;

private string LevelPlayAppId = "LEVELPLAY_APP_KEY";

public void Initialize()
{
AdHelper.Log("[LevelPlaySdkProvider] [Method] Initialize()");

LevelPlay.OnInitSuccess += OnInitializationCompleted;
LevelPlay.OnInitFailed += OnInitializationFailed;

LevelPlay.Init(LevelPlayAppId);
}

public IBannerAdProvider GetBannerAdProvider()
{
AdHelper.Log("[LevelPlaySdkProvider] [Method] GetBannerAdProvider()");

return _bannerAdProvider ??= new LevelPlayBannerAdProvider(RemoteConfig.LevelPlayBannerAdUnitId);
}

public IRewardedAdProvider GetRewardedAdProvider()
{
AdHelper.Log("[LevelPlaySdkProvider] [Method] GetRewardedAdProvider()");

return _rewardedAdProvider ??= new LevelPlayRewardedAdProvider(RemoteConfig.LevelPlayRewardedAdUnitId);
}

public IInterstitialAdProvider GetInterstitialAdProvider()
{
AdHelper.Log("[LevelPlaySdkProvider] [Method] GetInterstitialAdProvider()");

return _interstitialAdProvider ??= new LevelPlayInterstitialAdProvider(RemoteConfig.LevelPlayInterstitialAdUnitId);
}

private void OnInitializationCompleted(LevelPlayConfiguration configuration)
{
AdHelper.Log("[LevelPlaySdkProvider] [Method] OnInitializationCompleted()");

UnityMainThreadDispatcher.Post(_ => OnInitialized?.Invoke(this, EventArgs.Empty));
}

private void OnInitializationFailed(LevelPlayInitError error)
{
AdHelper.Log($"[LevelPlaySdkProvider] [Method] OnInitializationFailed(error: {error})");

UnityMainThreadDispatcher.Post(_ => OnInitialized?.Invoke(this, EventArgs.Empty));
}
}


LevelPlayBannerAdProvider

Implements LevelPlay BannerAd logic.

LevelPlayBannerAdProvider.cs
using System;
using Unity.Services.LevelPlay;
using LevelPlayAdSize = com.unity3d.mediation.LevelPlayAdSize;
using LevelPlayBannerPosition = com.unity3d.mediation.LevelPlayBannerPosition;

public class LevelPlayBannerAdProvider : IBannerAdProvider
{
public string Name { get => "levelplay"; }

public event EventHandler<double> OnAdLoaded;
public event EventHandler<string> OnAdLoadFailed;
public event EventHandler OnAdShown;
public event EventHandler<AdRevenueReceivedEventArgs> OnAdRevenueReceived;

private string LevelPlayBannerAdUnitId { get; }

private LevelPlayBannerAd BannerAd { get; set; }

public LevelPlayBannerAdProvider(string adUnitId)
{
AdHelper.Log("[LevelPlayBannerAdProvider] [Constructor] LevelPlayBannerAdProvider()");

LevelPlayBannerAdUnitId = adUnitId;

InstantiateBannerAd();
}

public void InstantiateBannerAd()
{
AdHelper.Log("[LevelPlayBannerAdProvider] [Method] InstantiateBannerAd()");

if (BannerAd != null)
{
UnsubscribeFromBannerAdEvents(BannerAd);
BannerAd.DestroyAd();
}

BannerAd = new LevelPlayBannerAd(LevelPlayBannerAdUnitId, LevelPlayAdSize.BANNER, LevelPlayBannerPosition.BottomCenter, null, false);
BannerAd?.PauseAutoRefresh();
SubscribeToBannerAdEvents(BannerAd);
}

public void LoadBannerAd(double priceFloor = 0d)
{
AdHelper.Log("[LevelPlayBannerAdProvider] [Method] LoadBannerAd()");

BannerAd?.LoadAd();
}

public void NotifyLoss(string winnerDemandId, double ecpm)
{
AdHelper.Log($"[LevelPlayBannerAdProvider] [Method] NotifyLoss(winner: {winnerDemandId}, ecpm: {ecpm})");
}

public void NotifyWin()
{
AdHelper.Log("[LevelPlayBannerAdProvider] [Method] NotifyWin()");
}

public void ShowBannerAd()
{
AdHelper.Log("[LevelPlayBannerAdProvider] [Method] ShowBannerAd()");

BannerAd?.ShowAd();
}

public void HideBannerAd()
{
AdHelper.Log("[LevelPlayBannerAdProvider] [Method] HideBannerAd()");

BannerAd?.HideAd();
}

private void SubscribeToBannerAdEvents(LevelPlayBannerAd ad)
{
if (ad == null) return;

AdHelper.Log("[LevelPlayBannerAdProvider] [Method] SubscribeToBannerAdEvents()");

ad.OnAdLoaded += OnLevelPlayBannerAdLoaded;
ad.OnAdLoadFailed += OnLevelPlayBannerAdLoadFailed;
ad.OnAdDisplayed += OnLevelPlayBannerAdDisplayed;
}

private void UnsubscribeFromBannerAdEvents(LevelPlayBannerAd ad)
{
if (ad == null) return;

AdHelper.Log("[LevelPlayBannerAdProvider] [Method] UnsubscribeFromBannerAdEvents()");

ad.OnAdLoaded -= OnLevelPlayBannerAdLoaded;
ad.OnAdLoadFailed -= OnLevelPlayBannerAdLoadFailed;
ad.OnAdDisplayed -= OnLevelPlayBannerAdDisplayed;
}

private void OnLevelPlayBannerAdLoaded(LevelPlayAdInfo adInfo)
{
double ecpm = AdHelper.GetRoundedEcpm((adInfo.Revenue ?? 0d) * 1000);
AdHelper.Log($"[LevelPlayBannerAdProvider] [Method] OnLevelPlayBannerAdLoaded(ecpm: {ecpm}). Revenue is {adInfo.Revenue}");

OnAdLoaded?.Invoke(this, ecpm);
}

private void OnLevelPlayBannerAdLoadFailed(LevelPlayAdError error)
{
AdHelper.Log("[LevelPlayBannerAdProvider] [Method] OnLevelPlayBannerAdLoadFailed()");

OnAdLoadFailed?.Invoke(this, error.ToString());
}

private void OnLevelPlayBannerAdDisplayed(LevelPlayAdInfo adInfo)
{
AdHelper.Log("[LevelPlayBannerAdProvider] [Method] OnLevelPlayBannerAdDisplayed()");

OnAdShown?.Invoke(this, EventArgs.Empty);

var info = new AdInfo
{
AdType = "Banner",
AdUnitId = adInfo.AdUnitId,
CurrencyCode = "USD",
MediationId = Name,
NetworkAdUnitId = adInfo.InstanceId,
NetworkName = adInfo.AdNetwork,
Revenue = adInfo.Revenue ?? 0d,
RevenuePrecision = adInfo.Precision,
};
OnAdRevenueReceived?.Invoke(this, new AdRevenueReceivedEventArgs(info));
}
}


LevelPlayRewardedAdProvider

Implements LevelPlay RewardedAd logic.

LevelPlayRewardedAdProvider.cs
using System;
using Unity.Services.LevelPlay;

public class LevelPlayRewardedAdProvider : IRewardedAdProvider
{
public string Name { get => "levelplay"; }

public event EventHandler<double> OnAdLoaded;
public event EventHandler<string> OnAdLoadFailed;
public event EventHandler OnAdShown;
public event EventHandler<string> OnAdShowFailed;
public event EventHandler<bool> OnAdClosed;
public event EventHandler<AdRevenueReceivedEventArgs> OnAdRevenueReceived;

private string LevelPlayRewardedAdUnitId { get; }

private LevelPlayRewardedAd RewardedAd { get; set; }

public LevelPlayRewardedAdProvider(string adUnitId)
{
AdHelper.Log("[LevelPlayRewardedAdProvider] [Constructor] LevelPlayRewardedAdProvider()");

LevelPlayRewardedAdUnitId = adUnitId;

InstantiateRewardedAd();
}

public void LoadRewardedAd(double priceFloor = 0d)
{
AdHelper.Log($"[LevelPlayRewardedAdProvider] [Method] LoadRewardedAd(priceFloor: {priceFloor})");

RewardedAd?.LoadAd();
}

public void NotifyLoss(string winnerDemandId, double ecpm)
{
AdHelper.Log($"[LevelPlayRewardedAdProvider] [Method] NotifyLoss(winner: {winnerDemandId}, ecpm: {ecpm})");

InstantiateRewardedAd();
}

public void NotifyWin()
{
AdHelper.Log("[LevelPlayRewardedAdProvider] [Method] NotifyWin()");
}

public void ShowRewardedAd(string placementName)
{
AdHelper.Log("[LevelPlayRewardedAdProvider] [Method] ShowRewardedAd()");

RewardedAd?.ShowAd(placementName);
}

private void InstantiateRewardedAd()
{
AdHelper.Log("[LevelPlayRewardedAdProvider] [Method] InstantiateRewardedAd()");

if (RewardedAd != null)
{
UnsubscribeFromRewardedAdEvents(RewardedAd);
RewardedAd.DestroyAd();
}

RewardedAd = new LevelPlayRewardedAd(LevelPlayRewardedAdUnitId);
SubscribeToRewardedAdEvents(RewardedAd);
}

private void SubscribeToRewardedAdEvents(LevelPlayRewardedAd ad)
{
if (ad == null) return;

AdHelper.Log("[LevelPlayRewardedAdProvider] [Method] SubscribeToRewardedAdEvents()");

ad.OnAdLoaded += OnLevelPlayRewardedAdLoaded;
ad.OnAdLoadFailed += OnLevelPlayRewardedAdLoadFailed;
ad.OnAdDisplayed += OnLevelPlayRewardedAdDisplayed;
ad.OnAdDisplayFailed += OnLevelPlayRewardedAdDisplayFailed;
ad.OnAdClosed += OnLevelPlayRewardedAdClosed;
}

private void UnsubscribeFromRewardedAdEvents(LevelPlayRewardedAd ad)
{
if (ad == null) return;

AdHelper.Log("[LevelPlayRewardedAdProvider] [Method] UnsubscribeFromRewardedAdEvents()");

ad.OnAdLoaded -= OnLevelPlayRewardedAdLoaded;
ad.OnAdLoadFailed -= OnLevelPlayRewardedAdLoadFailed;
ad.OnAdDisplayed -= OnLevelPlayRewardedAdDisplayed;
ad.OnAdDisplayFailed -= OnLevelPlayRewardedAdDisplayFailed;
ad.OnAdClosed -= OnLevelPlayRewardedAdClosed;
}

private void OnLevelPlayRewardedAdLoaded(LevelPlayAdInfo adInfo)
{
double ecpm = AdHelper.GetRoundedEcpm((adInfo.Revenue ?? 0d) * 1000);
AdHelper.Log($"[LevelPlayRewardedAdProvider] [Method] OnLevelPlayRewardedAdLoaded(ecpm: {ecpm}). Revenue is {adInfo.Revenue}");

OnAdLoaded?.Invoke(this, ecpm);
}

private void OnLevelPlayRewardedAdLoadFailed(LevelPlayAdError error)
{
AdHelper.Log("[LevelPlayRewardedAdProvider] [Method] OnLevelPlayRewardedAdLoadFailed()");

OnAdLoadFailed?.Invoke(this, error.ToString());
}

private void OnLevelPlayRewardedAdDisplayed(LevelPlayAdInfo adInfo)
{
AdHelper.Log("[LevelPlayRewardedAdProvider] [Method] OnLevelPlayRewardedAdDisplayed()");

OnAdShown?.Invoke(this, EventArgs.Empty);

var info = new AdInfo
{
AdType = "Rewarded",
AdUnitId = adInfo.AdUnitId,
CurrencyCode = "USD",
MediationId = Name,
NetworkAdUnitId = adInfo.InstanceId,
NetworkName = adInfo.AdNetwork,
Revenue = adInfo.Revenue ?? 0d,
RevenuePrecision = adInfo.Precision,
};

OnAdRevenueReceived?.Invoke(this, new AdRevenueReceivedEventArgs(info));
}

private void OnLevelPlayRewardedAdDisplayFailed(LevelPlayAdDisplayInfoError error)
{
AdHelper.Log("[LevelPlayRewardedAdProvider] [Method] OnLevelPlayRewardedAdDisplayFailed()");

InstantiateRewardedAd();
OnAdShowFailed?.Invoke(this, error.ToString());
}

private void OnLevelPlayRewardedAdClosed(LevelPlayAdInfo adInfo)
{
AdHelper.Log("[LevelPlayRewardedAdProvider] [Method] OnLevelPlayRewardedAdClosed()");

InstantiateRewardedAd();
OnAdClosed?.Invoke(this, true);
}
}


LevelPlayInterstitialAdProvider

Implements LevelPlay InterstitialAd logic.

LevelPlayInterstitialAdProvider.cs
using System;
using Unity.Services.LevelPlay;

public class LevelPlayInterstitialAdProvider : IInterstitialAdProvider
{
public string Name { get => "levelplay"; }

public event EventHandler<double> OnAdLoaded;
public event EventHandler<string> OnAdLoadFailed;
public event EventHandler OnAdShown;
public event EventHandler<string> OnAdShowFailed;
public event EventHandler OnAdClosed;
public event EventHandler<AdRevenueReceivedEventArgs> OnAdRevenueReceived;

private string LevelPlayInterstitialAdUnitId { get; }

private LevelPlayInterstitialAd InterstitialAd { get; set; }

public LevelPlayInterstitialAdProvider(string adUnitId)
{
AdHelper.Log("[LevelPlayInterstitialAdProvider] [Constructor] LevelPlayInterstitialAdProvider()");

LevelPlayInterstitialAdUnitId = adUnitId;

InstantiateInterstitialAd();
}

public void LoadInterstitialAd(double priceFloor = 0d)
{
AdHelper.Log($"[LevelPlayInterstitialAdProvider] [Method] LoadInterstitialAd(priceFloor: {priceFloor})");

InterstitialAd?.LoadAd();
}

public void NotifyLoss(string winnerDemandId, double ecpm)
{
AdHelper.Log($"[LevelPlayInterstitialAdProvider] [Method] NotifyLoss(winner: {winnerDemandId}, ecpm: {ecpm})");

InstantiateInterstitialAd();
}

public void NotifyWin()
{
AdHelper.Log("[LevelPlayInterstitialAdProvider] [Method] NotifyWin()");
}

public void ShowInterstitialAd(string placementName)
{
AdHelper.Log("[LevelPlayInterstitialAdProvider] [Method] ShowInterstitialAd()");

InterstitialAd?.ShowAd(placementName);
}

private void InstantiateInterstitialAd()
{
AdHelper.Log("[LevelPlayInterstitialAdProvider] [Method] InstantiateInterstitialAd()");

if (InterstitialAd != null)
{
UnsubscribeFromInterstitialAdEvents(InterstitialAd);
InterstitialAd.DestroyAd();
}

InterstitialAd = new LevelPlayInterstitialAd(LevelPlayInterstitialAdUnitId);
SubscribeToInterstitialAdEvents(InterstitialAd);
}

private void SubscribeToInterstitialAdEvents(LevelPlayInterstitialAd ad)
{
if (ad == null) return;

AdHelper.Log("[LevelPlayInterstitialAdProvider] [Method] SubscribeToInterstitialAdEvents()");

ad.OnAdLoaded += OnLevelPlayInterstitialAdLoaded;
ad.OnAdLoadFailed += OnLevelPlayInterstitialAdLoadFailed;
ad.OnAdDisplayed += OnLevelPlayInterstitialAdDisplayed;
ad.OnAdDisplayFailed += OnLevelPlayInterstitialAdDisplayFailed;
ad.OnAdClosed += OnLevelPlayInterstitialAdClosed;
}

private void UnsubscribeFromInterstitialAdEvents(LevelPlayInterstitialAd ad)
{
if (ad == null) return;

AdHelper.Log("[LevelPlayInterstitialAdProvider] [Method] UnsubscribeFromInterstitialAdEvents()");

ad.OnAdLoaded -= OnLevelPlayInterstitialAdLoaded;
ad.OnAdLoadFailed -= OnLevelPlayInterstitialAdLoadFailed;
ad.OnAdDisplayed -= OnLevelPlayInterstitialAdDisplayed;
ad.OnAdDisplayFailed -= OnLevelPlayInterstitialAdDisplayFailed;
ad.OnAdClosed -= OnLevelPlayInterstitialAdClosed;
}

private void OnLevelPlayInterstitialAdLoaded(LevelPlayAdInfo adInfo)
{
double ecpm = AdHelper.GetRoundedEcpm((adInfo.Revenue ?? 0d) * 1000);
AdHelper.Log($"[LevelPlayInterstitialAdProvider] [Callback] OnLevelPlayInterstitialAdLoaded(ecpm: {ecpm}). Revenue is {adInfo.Revenue}");

OnAdLoaded?.Invoke(this, ecpm);
}

private void OnLevelPlayInterstitialAdLoadFailed(LevelPlayAdError error)
{
AdHelper.Log("[LevelPlayInterstitialAdProvider] [Callback] OnLevelPlayInterstitialAdLoadFailed()");

OnAdLoadFailed?.Invoke(this, error.ToString());
}

private void OnLevelPlayInterstitialAdDisplayed(LevelPlayAdInfo adInfo)
{
AdHelper.Log("[LevelPlayInterstitialAdProvider] [Callback] OnLevelPlayInterstitialAdDisplayed()");

OnAdShown?.Invoke(this, EventArgs.Empty);

var info = new AdInfo
{
AdType = "Interstitial",
AdUnitId = adInfo.AdUnitId,
CurrencyCode = "USD",
MediationId = Name,
NetworkAdUnitId = adInfo.InstanceId,
NetworkName = adInfo.AdNetwork,
Revenue = adInfo.Revenue ?? 0d,
RevenuePrecision = adInfo.Precision,
};
OnAdRevenueReceived?.Invoke(this, new AdRevenueReceivedEventArgs(info));
}

private void OnLevelPlayInterstitialAdDisplayFailed(LevelPlayAdDisplayInfoError infoError)
{
AdHelper.Log("[LevelPlayInterstitialAdProvider] [Callback] OnLevelPlayInterstitialAdDisplayFailed()");

InstantiateInterstitialAd();
OnAdShowFailed?.Invoke(this, infoError.ToString());
}

private void OnLevelPlayInterstitialAdClosed(LevelPlayAdInfo adInfo)
{
AdHelper.Log("[LevelPlayInterstitialAdProvider] [Callback] OnLevelPlayInterstitialAdClosed()");

InstantiateInterstitialAd();
OnAdClosed?.Invoke(this, EventArgs.Empty);
}
}


IBannerAdController

An interface for different BannerAdProviders is used for DI.

IBannerAdController.cs
public interface IBannerAdController
{
void ShowBanner();
void HideBanner();
}


IRewardedAdController

An interface for different RewardedAdProviders is used for DI.

IRewardedAdController.cs
using System;

public interface IRewardedAdController
{
event EventHandler OnAdShowFailed;
event EventHandler<bool> OnAdClosed;
bool IsLoaded();
void ShowRewarded(string placementName);
}


IInterstitialAdController

An interface for different InterstitialAdProviders is used for DI.

IInterstitialAdController.cs
using System;

public interface IInterstitialAdController
{
event EventHandler OnAdShowFailed;
event EventHandler OnAdClosed;
bool IsLoaded();
void ShowInterstitial(string placementName);
}


PostBidBannerAdController

BannerAd controller implementing SecondProvider in PostBid after FirstProvider.

PostBidBannerAdController.cs
using System;
using System.Threading.Tasks;

public class PostBidBannerAdController : IBannerAdController
{
private IBannerAdProvider FirstAdProvider { get; }
private IBannerAdProvider SecondAdProvider { get; }

private readonly int _refreshInterval;

private readonly double _defaultPriceFloor;

private readonly PriceFloorIncrementStrategy _strategy;
private readonly double _step;

private bool _shouldShowBanner;

private int _bannerAdLoadRetryAttempt;

private bool _isBannerShowing;
private AdProvider _activeBannerAd;

private double _firstAdProviderEcpm;
private double _secondAdProviderEcpm;

private bool _isFirstAdProviderLoaded;
private bool _isSecondAdProviderLoaded;

public PostBidBannerAdController(IBannerAdProvider firstProvider, IBannerAdProvider secondProvider)
{
AdHelper.Log("[PostBidBannerAdController] [Constructor] PostBidBannerAdController()");

FirstAdProvider = firstProvider;
SecondAdProvider = secondProvider;

_refreshInterval = AdConfig.BannerAdRefreshInterval;

_defaultPriceFloor = AdConfig.DefaultBannerAdPriceFloor;

_strategy = AdConfig.BannerAdPriceFloorIncrementStrategy;
_step = AdConfig.BannerAdPriceFloorIncrementStep;

SubscribeToAdEvents();

FirstAdProvider?.LoadBannerAd();
}

public void ShowBanner()
{
if (_shouldShowBanner) return;

AdHelper.Log("[PostBidBannerAdController] [Method] ShowBanner()");

_shouldShowBanner = true;
ShowBannerAd();
}

public void HideBanner()
{
if (!_shouldShowBanner) return;

AdHelper.Log("[PostBidBannerAdController] [Method] HideBanner()");

_shouldShowBanner = false;
HideBannerAd();
}

private void ShowBannerAd()
{
AdHelper.Log("[PostBidBannerAdController] [Method] ShowBannerAd()");

if (!_isFirstAdProviderLoaded && !_isSecondAdProviderLoaded)
{
AdHelper.Log($"[PostBidBannerAdController] {FirstAdProvider?.Name} is NOT loaded, {SecondAdProvider?.Name} is NOT loaded, Skip Show");
}
else if (_isFirstAdProviderLoaded && !_isSecondAdProviderLoaded)
{
AdHelper.Log($"[PostBidBannerAdController] {FirstAdProvider?.Name} is loaded, {SecondAdProvider?.Name} is NOT loaded, Show {FirstAdProvider?.Name}");

FirstAdProvider?.NotifyWin();
SecondAdProvider?.NotifyLoss(FirstAdProvider?.Name, _firstAdProviderEcpm);

FirstAdProvider?.ShowBannerAd();
RefreshBanner(_refreshInterval);

_isBannerShowing = true;
_activeBannerAd = AdProvider.First;
}
else if (!_isFirstAdProviderLoaded && _isSecondAdProviderLoaded)
{
AdHelper.Log($"[PostBidBannerAdController] {FirstAdProvider?.Name} is NOT loaded, {SecondAdProvider?.Name} is loaded, Show {SecondAdProvider?.Name}");

FirstAdProvider?.NotifyLoss(SecondAdProvider?.Name, _secondAdProviderEcpm);
SecondAdProvider?.NotifyWin();

SecondAdProvider?.ShowBannerAd();
RefreshBanner(_refreshInterval);

_isBannerShowing = true;
_activeBannerAd = AdProvider.Second;
}
else
{
if (_firstAdProviderEcpm > _secondAdProviderEcpm)
{
AdHelper.Log($"[PostBidBannerAdController] {FirstAdProvider?.Name} is loaded(ecpm: {_firstAdProviderEcpm}), {SecondAdProvider?.Name} is loaded(ecpm: {_secondAdProviderEcpm}), Show {FirstAdProvider?.Name}");

FirstAdProvider?.NotifyWin();
SecondAdProvider?.NotifyLoss(FirstAdProvider?.Name, _firstAdProviderEcpm);

FirstAdProvider?.ShowBannerAd();
RefreshBanner(_refreshInterval);

_isBannerShowing = true;
_activeBannerAd = AdProvider.First;
}
else
{
AdHelper.Log($"[PostBidBannerAdController] {FirstAdProvider?.Name} is loaded(ecpm: {_firstAdProviderEcpm}), {SecondAdProvider?.Name} is loaded(ecpm: {_secondAdProviderEcpm}), Show {SecondAdProvider?.Name}");

FirstAdProvider?.NotifyLoss(SecondAdProvider?.Name, _secondAdProviderEcpm);
SecondAdProvider?.NotifyWin();

SecondAdProvider?.ShowBannerAd();
RefreshBanner(_refreshInterval);

_isBannerShowing = true;
_activeBannerAd = AdProvider.Second;
}
}
}

private void HideBannerAd()
{
if (!_isBannerShowing) return;

AdHelper.Log("[PostBidBannerAdController] [Method] HideBannerAd()");

switch (_activeBannerAd)
{
case AdProvider.First:
FirstAdProvider?.HideBannerAd();
break;
case AdProvider.Second:
SecondAdProvider?.HideBannerAd();
break;
default:
AdHelper.LogError("[PostBidBannerAdController] AdProvider variable is out of range");
break;
}

_firstAdProviderEcpm = 0d;
_isFirstAdProviderLoaded = false;
FirstAdProvider?.InstantiateBannerAd();

_secondAdProviderEcpm = 0d;
_isSecondAdProviderLoaded = false;
SecondAdProvider?.InstantiateBannerAd();

FirstAdProvider?.LoadBannerAd();

_isBannerShowing = false;
}

private async void RefreshBanner(int refreshInterval)
{
await Task.Delay(refreshInterval * 1000);

AdHelper.Log("[PostBidBannerAdController] [Method] RefreshBanner()");

HideBannerAd();
}

private void SubscribeToAdEvents()
{
AdHelper.Log("[PostBidBannerAdController] [Method] SubscribeToAdEvents()");

if (FirstAdProvider == null) return;
FirstAdProvider.OnAdLoaded += OnFirstAdProviderLoaded;
FirstAdProvider.OnAdLoadFailed += OnFirstAdProviderLoadFailed;
FirstAdProvider.OnAdShown += OnFirstAdProviderShown;
FirstAdProvider.OnAdRevenueReceived += (sender, args) => AdHelper.LogAdRevenue(args.Info);

if (SecondAdProvider == null) return;
SecondAdProvider.OnAdLoaded += OnSecondAdProviderLoaded;
SecondAdProvider.OnAdLoadFailed += OnSecondAdProviderLoadFailed;
SecondAdProvider.OnAdShown += OnSecondAdProviderShown;
SecondAdProvider.OnAdRevenueReceived += (sender, args) => AdHelper.LogAdRevenue(args.Info);
}

private async void LoadFirstAdProviderAfterDelay(int time)
{
AdHelper.Log($"[PostBidBannerAdController] [Method] LoadFirstAdProviderAfterDelay(time: {time})");

await Task.Delay(time * 1000);
FirstAdProvider?.LoadBannerAd();
}

private void OnFirstAdProviderLoaded(object sender, double ecpm)
{
AdHelper.Log($"[PostBidBannerAdController] [Callback] OnFirstAdProviderLoaded(ecpm: {ecpm})");

_firstAdProviderEcpm = ecpm;
_isFirstAdProviderLoaded = true;
_bannerAdLoadRetryAttempt = 0;

SecondAdProvider?.LoadBannerAd(AdHelper.GetIncrementedPriceFloor(_firstAdProviderEcpm, _defaultPriceFloor, _strategy, _step));
}

private void OnSecondAdProviderLoaded(object sender, double ecpm)
{
AdHelper.Log($"[PostBidBannerAdController] [Callback] OnSecondAdProviderLoaded(ecpm: {ecpm})");

_secondAdProviderEcpm = ecpm;
_isSecondAdProviderLoaded = true;
_bannerAdLoadRetryAttempt = 0;

if (_shouldShowBanner && !_isBannerShowing)
{
ShowBannerAd();
}
}

private void OnFirstAdProviderLoadFailed(object sender, string cause)
{
AdHelper.Log($"[PostBidBannerAdController] [Callback] OnFirstAdProviderLoadFailed(cause: {cause})");

_firstAdProviderEcpm = 0d;
_isFirstAdProviderLoaded = false;

SecondAdProvider?.LoadBannerAd(_defaultPriceFloor);
}

private void OnSecondAdProviderLoadFailed(object sender, string cause)
{
AdHelper.Log($"[PostBidBannerAdController] [Callback] OnSecondAdProviderLoadFailed(cause: {cause})");

_secondAdProviderEcpm = 0d;
_isSecondAdProviderLoaded = false;

if (_isFirstAdProviderLoaded)
{
if (_shouldShowBanner && !_isBannerShowing)
{
ShowBannerAd();
}
return;
}

_bannerAdLoadRetryAttempt++;
int retryDelay = (int)Math.Pow(2, Math.Min(6, _bannerAdLoadRetryAttempt));

LoadFirstAdProviderAfterDelay(retryDelay);
}

private void OnFirstAdProviderShown(object sender, EventArgs eventArgs)
{
AdHelper.Log("[PostBidBannerAdController] [Callback] OnFirstAdProviderShown()");
}

private void OnSecondAdProviderShown(object sender, EventArgs eventArgs)
{
AdHelper.Log("[PostBidBannerAdController] [Callback] OnSecondAdProviderShown()");
}
}


PostBidRewardedAdController

RewardedAd controller implementing SecondProvider in PostBid after FirstProvider.

PostBidRewardedAdController.cs
using System;
using System.Threading.Tasks;

public class PostBidRewardedAdController : IRewardedAdController
{
public event EventHandler OnAdShowFailed;
public event EventHandler<bool> OnAdClosed;

private IRewardedAdProvider FirstAdProvider { get; }
private IRewardedAdProvider SecondAdProvider { get; }

private readonly double _defaultPriceFloor;

private readonly PriceFloorIncrementStrategy _strategy;
private readonly double _step;

private int _rewardedAdLoadRetryAttempt;

private double _firstAdProviderEcpm;
private double _secondAdProviderEcpm;

private bool _isFirstAdProviderLoaded;
private bool _isSecondAdProviderLoaded;

public PostBidRewardedAdController(IRewardedAdProvider firstProvider, IRewardedAdProvider secondProvider)
{
AdHelper.Log("[PostBidRewardedAdController] [Constructor] PostBidRewardedAdController()");

FirstAdProvider = firstProvider;
SecondAdProvider = secondProvider;

_defaultPriceFloor = AdConfig.DefaultRewardedAdPriceFloor;

_strategy = AdConfig.RewardedAdPriceFloorIncrementStrategy;
_step = AdConfig.RewardedAdPriceFloorIncrementStep;

SubscribeToAdEvents();

FirstAdProvider?.LoadRewardedAd();
}

public bool IsLoaded() => _isFirstAdProviderLoaded || _isSecondAdProviderLoaded;

public void ShowRewarded(string placementName)
{
AdHelper.Log($"[PostBidRewardedAdController] [Method] ShowRewarded(placement: {placementName})");

if (!_isFirstAdProviderLoaded && !_isSecondAdProviderLoaded)
{
AdHelper.Log($"[PostBidRewardedAdController] {FirstAdProvider?.Name} is NOT loaded, {SecondAdProvider?.Name} is NOT loaded, Skip Show");
}
else if (_isFirstAdProviderLoaded && !_isSecondAdProviderLoaded)
{
AdHelper.Log($"[PostBidRewardedAdController] {FirstAdProvider?.Name} is loaded, {SecondAdProvider?.Name} is NOT loaded, Show {FirstAdProvider?.Name} with placement: {placementName}");

FirstAdProvider?.NotifyWin();
SecondAdProvider?.NotifyLoss(FirstAdProvider?.Name, _firstAdProviderEcpm);
_isSecondAdProviderLoaded = false;
_secondAdProviderEcpm = 0d;

FirstAdProvider?.ShowRewardedAd(placementName);
}
else if (!_isFirstAdProviderLoaded && _isSecondAdProviderLoaded)
{
AdHelper.Log($"[PostBidRewardedAdController] {FirstAdProvider?.Name} is NOT loaded, {SecondAdProvider?.Name} is loaded, Show {SecondAdProvider?.Name} with placement: {placementName}");

FirstAdProvider?.NotifyLoss(SecondAdProvider?.Name, _secondAdProviderEcpm);
SecondAdProvider?.NotifyWin();

SecondAdProvider?.ShowRewardedAd(placementName);
}
else
{
if (_firstAdProviderEcpm > _secondAdProviderEcpm)
{
AdHelper.Log($"[PostBidRewardedAdController] {FirstAdProvider?.Name} is loaded(ecpm: {_firstAdProviderEcpm}), {SecondAdProvider?.Name} is loaded(ecpm: {_secondAdProviderEcpm}), Show {FirstAdProvider?.Name} with placement: {placementName}");

FirstAdProvider?.NotifyWin();
SecondAdProvider?.NotifyLoss(FirstAdProvider?.Name, _firstAdProviderEcpm);
_isSecondAdProviderLoaded = false;
_secondAdProviderEcpm = 0d;

FirstAdProvider?.ShowRewardedAd(placementName);
}
else
{
AdHelper.Log($"[PostBidRewardedAdController] {FirstAdProvider?.Name} is loaded(ecpm: {_firstAdProviderEcpm}), {SecondAdProvider?.Name} is loaded(ecpm: {_secondAdProviderEcpm}), Show {SecondAdProvider?.Name} with placement: {placementName}");

FirstAdProvider?.NotifyLoss(SecondAdProvider?.Name, _secondAdProviderEcpm);
SecondAdProvider?.NotifyWin();

SecondAdProvider?.ShowRewardedAd(placementName);
}
}
}

private void SubscribeToAdEvents()
{
AdHelper.Log("[PostBidRewardedAdController] [Method] SubscribeToAdEvents()");

if (FirstAdProvider == null) return;
FirstAdProvider.OnAdLoaded += OnFirstAdProviderLoaded;
FirstAdProvider.OnAdLoadFailed += OnFirstAdProviderLoadFailed;
FirstAdProvider.OnAdShown += OnFirstAdProviderShown;
FirstAdProvider.OnAdShowFailed += OnFirstAdProviderShowFailed;
FirstAdProvider.OnAdClosed += OnFirstAdProviderClosed;
FirstAdProvider.OnAdRevenueReceived += (sender, args) => AdHelper.LogAdRevenue(args.Info);

if (SecondAdProvider == null) return;
SecondAdProvider.OnAdLoaded += OnSecondAdProviderLoaded;
SecondAdProvider.OnAdLoadFailed += OnSecondAdProviderLoadFailed;
SecondAdProvider.OnAdShown += OnSecondAdProviderShown;
SecondAdProvider.OnAdShowFailed += OnSecondAdProviderShowFailed;
SecondAdProvider.OnAdClosed += OnSecondAdProviderClosed;
SecondAdProvider.OnAdRevenueReceived += (sender, args) => AdHelper.LogAdRevenue(args.Info);
}

private async void LoadFirstAdProviderAfterDelay(int time)
{
AdHelper.Log($"[PostBidRewardedAdController] [Method] LoadFirstAdProviderAfterDelay(time: {time})");

await Task.Delay(time * 1000);
FirstAdProvider?.LoadRewardedAd();
}

private void OnFirstAdProviderLoaded(object sender, double ecpm)
{
AdHelper.Log($"[PostBidRewardedAdController] [Callback] OnFirstAdProviderLoaded(ecpm: {ecpm})");

_firstAdProviderEcpm = ecpm;
_isFirstAdProviderLoaded = true;
_rewardedAdLoadRetryAttempt = 0;

SecondAdProvider?.LoadRewardedAd(AdHelper.GetIncrementedPriceFloor(_firstAdProviderEcpm, _defaultPriceFloor, _strategy, _step));
}

private void OnSecondAdProviderLoaded(object sender, double ecpm)
{
AdHelper.Log($"[PostBidRewardedAdController] [Callback] OnSecondAdProviderLoaded(ecpm: {ecpm})");

_secondAdProviderEcpm = ecpm;
_isSecondAdProviderLoaded = true;
_rewardedAdLoadRetryAttempt = 0;
}

private void OnFirstAdProviderLoadFailed(object sender, string cause)
{
AdHelper.Log($"[PostBidRewardedAdController] [Callback] OnFirstAdProviderLoadFailed(cause: {cause})");

_firstAdProviderEcpm = 0d;
_isFirstAdProviderLoaded = false;

SecondAdProvider?.LoadRewardedAd(_defaultPriceFloor);
}

private void OnSecondAdProviderLoadFailed(object sender, string cause)
{
AdHelper.Log($"[PostBidRewardedAdController] [Callback] OnSecondAdProviderLoadFailed(cause: {cause})");

_secondAdProviderEcpm = 0d;
_isSecondAdProviderLoaded = false;

if (_isFirstAdProviderLoaded) return;

_rewardedAdLoadRetryAttempt++;
int retryDelay = (int)Math.Pow(2, Math.Min(6, _rewardedAdLoadRetryAttempt));

LoadFirstAdProviderAfterDelay(retryDelay);
}

private void OnFirstAdProviderShown(object sender, EventArgs eventArgs)
{
AdHelper.Log("[PostBidRewardedAdController] [Callback] OnFirstAdProviderShown()");
}

private void OnSecondAdProviderShown(object sender, EventArgs eventArgs)
{
AdHelper.Log("[PostBidRewardedAdController] [Callback] OnSecondAdProviderShown()");
}

private void OnFirstAdProviderShowFailed(object sender, string cause)
{
AdHelper.Log($"[PostBidRewardedAdController] [Callback] OnFirstAdProviderShowFailed(cause: {cause})");

_firstAdProviderEcpm = 0d;
_isFirstAdProviderLoaded = false;

OnAdShowFailed?.Invoke(this, EventArgs.Empty);

FirstAdProvider?.LoadRewardedAd();
}

private void OnSecondAdProviderShowFailed(object sender, string cause)
{
AdHelper.Log($"[PostBidRewardedAdController] [Callback] OnSecondAdProviderShowFailed(cause: {cause})");

_isSecondAdProviderLoaded = false;
_secondAdProviderEcpm = 0d;

OnAdShowFailed?.Invoke(this, EventArgs.Empty);

if (_isFirstAdProviderLoaded)
{
SecondAdProvider?.LoadRewardedAd(AdHelper.GetIncrementedPriceFloor(_firstAdProviderEcpm, _defaultPriceFloor, _strategy, _step));
}
else
{
FirstAdProvider?.LoadRewardedAd();
}
}

private void OnFirstAdProviderClosed(object sender, bool shouldReward)
{
AdHelper.Log("[PostBidRewardedAdController] [Callback] OnFirstAdProviderClosed()");

_firstAdProviderEcpm = 0d;
_isFirstAdProviderLoaded = false;

OnAdClosed?.Invoke(this, shouldReward);

FirstAdProvider?.LoadRewardedAd();
}

private void OnSecondAdProviderClosed(object sender, bool shouldReward)
{
AdHelper.Log("[PostBidRewardedAdController] [Callback] OnSecondAdProviderClosed()");

_isSecondAdProviderLoaded = false;
_secondAdProviderEcpm = 0d;

OnAdClosed?.Invoke(this, shouldReward);

if (_isFirstAdProviderLoaded)
{
SecondAdProvider?.LoadRewardedAd(AdHelper.GetIncrementedPriceFloor(_firstAdProviderEcpm, _defaultPriceFloor, _strategy, _step));
}
else
{
FirstAdProvider?.LoadRewardedAd();
}
}
}


PostBidInterstitialAdController

InterstitialAd controller implementing SecondProvider in PostBid after FirstProvider.

PostBidInterstitialAdController.cs
using System;
using System.Threading.Tasks;

public class PostBidInterstitialAdController : IInterstitialAdController
{
public event EventHandler OnAdShowFailed;
public event EventHandler OnAdClosed;

private IInterstitialAdProvider FirstAdProvider { get; }
private IInterstitialAdProvider SecondAdProvider { get; }

private readonly double _defaultPriceFloor;

private readonly PriceFloorIncrementStrategy _strategy;
private readonly double _step;

private int _interstitialAdLoadRetryAttempt;

private double _firstAdProviderEcpm;
private double _secondAdProviderEcpm;

private bool _isFirstAdProviderLoaded;
private bool _isSecondAdProviderLoaded;

public PostBidInterstitialAdController(IInterstitialAdProvider firstProvider, IInterstitialAdProvider secondProvider)
{
AdHelper.Log("[PostBidInterstitialAdController] [Constructor] PostBidInterstitialAdController()");

FirstAdProvider = firstProvider;
SecondAdProvider = secondProvider;

_defaultPriceFloor = AdConfig.DefaultInterstitialAdPriceFloor;

_strategy = AdConfig.InterstitialAdPriceFloorIncrementStrategy;
_step = AdConfig.InterstitialAdPriceFloorIncrementStep;

SubscribeToAdEvents();

FirstAdProvider?.LoadInterstitialAd();
}

public bool IsLoaded() => _isFirstAdProviderLoaded || _isSecondAdProviderLoaded;

public void ShowInterstitial(string placementName)
{
AdHelper.Log($"[PostBidInterstitialAdController] [Method] ShowInterstitial(placement: {placementName})");

if (!_isFirstAdProviderLoaded && !_isSecondAdProviderLoaded)
{
AdHelper.Log($"[PostBidInterstitialAdController] {FirstAdProvider?.Name} is NOT loaded, {SecondAdProvider?.Name} is NOT loaded, Skip Show");
}
else if (_isFirstAdProviderLoaded && !_isSecondAdProviderLoaded)
{
AdHelper.Log($"[PostBidInterstitialAdController] {FirstAdProvider?.Name} is loaded, {SecondAdProvider?.Name} is NOT loaded, Show {FirstAdProvider?.Name} with placement: {placementName}");

FirstAdProvider?.NotifyWin();
SecondAdProvider?.NotifyLoss(FirstAdProvider?.Name, _firstAdProviderEcpm);
_isSecondAdProviderLoaded = false;
_secondAdProviderEcpm = 0d;

FirstAdProvider?.ShowInterstitialAd(placementName);
}
else if (!_isFirstAdProviderLoaded && _isSecondAdProviderLoaded)
{
AdHelper.Log($"[PostBidInterstitialAdController] {FirstAdProvider?.Name} is NOT loaded, {SecondAdProvider?.Name} is loaded, Show {SecondAdProvider?.Name} with placement: {placementName}");

FirstAdProvider?.NotifyLoss(SecondAdProvider?.Name, _secondAdProviderEcpm);
SecondAdProvider?.NotifyWin();

SecondAdProvider?.ShowInterstitialAd(placementName);
}
else
{
if (_firstAdProviderEcpm > _secondAdProviderEcpm)
{
AdHelper.Log($"[PostBidInterstitialAdController] {FirstAdProvider?.Name} is loaded(ecpm: {_firstAdProviderEcpm}), {SecondAdProvider?.Name} is loaded(ecpm: {_secondAdProviderEcpm}), Show {FirstAdProvider?.Name} with placement: {placementName}");

FirstAdProvider?.NotifyWin();
SecondAdProvider?.NotifyLoss(FirstAdProvider?.Name, _firstAdProviderEcpm);
_isSecondAdProviderLoaded = false;
_secondAdProviderEcpm = 0d;

FirstAdProvider?.ShowInterstitialAd(placementName);
}
else
{
AdHelper.Log($"[PostBidInterstitialAdController] {FirstAdProvider?.Name} is loaded(ecpm: {_firstAdProviderEcpm}), {SecondAdProvider?.Name} is loaded(ecpm: {_secondAdProviderEcpm}), Show {SecondAdProvider?.Name} with placement: {placementName}");

FirstAdProvider?.NotifyLoss(SecondAdProvider?.Name, _secondAdProviderEcpm);
SecondAdProvider?.NotifyWin();

SecondAdProvider?.ShowInterstitialAd(placementName);
}
}
}

private void SubscribeToAdEvents()
{
AdHelper.Log("[PostBidInterstitialAdController] [Method] SubscribeToAdEvents()");

if (FirstAdProvider == null) return;
FirstAdProvider.OnAdLoaded += OnFirstAdProviderLoaded;
FirstAdProvider.OnAdLoadFailed += OnFirstAdProviderLoadFailed;
FirstAdProvider.OnAdShown += OnFirstAdProviderShown;
FirstAdProvider.OnAdShowFailed += OnFirstAdProviderShowFailed;
FirstAdProvider.OnAdClosed += OnFirstAdProviderClosed;
FirstAdProvider.OnAdRevenueReceived += (sender, args) => AdHelper.LogAdRevenue(args.Info);

if (SecondAdProvider == null) return;
SecondAdProvider.OnAdLoaded += OnSecondAdProviderLoaded;
SecondAdProvider.OnAdLoadFailed += OnSecondAdProviderLoadFailed;
SecondAdProvider.OnAdShown += OnSecondAdProviderShown;
SecondAdProvider.OnAdShowFailed += OnSecondAdProviderShowFailed;
SecondAdProvider.OnAdClosed += OnSecondAdProviderClosed;
SecondAdProvider.OnAdRevenueReceived += (sender, args) => AdHelper.LogAdRevenue(args.Info);
}

private async void LoadFirstAdProviderAfterDelay(int time)
{
AdHelper.Log($"[PostBidInterstitialAdController] [Method] LoadFirstAdProviderAfterDelay(time: {time})");

await Task.Delay(time * 1000);
FirstAdProvider?.LoadInterstitialAd();
}

private void OnFirstAdProviderLoaded(object sender, double ecpm)
{
AdHelper.Log($"[PostBidInterstitialAdController] [Callback] OnFirstAdProviderLoaded(ecpm: {ecpm})");

_firstAdProviderEcpm = ecpm;
_isFirstAdProviderLoaded = true;
_interstitialAdLoadRetryAttempt = 0;

SecondAdProvider?.LoadInterstitialAd(AdHelper.GetIncrementedPriceFloor(_firstAdProviderEcpm, _defaultPriceFloor, _strategy, _step));
}

private void OnSecondAdProviderLoaded(object sender, double ecpm)
{
AdHelper.Log($"[PostBidInterstitialAdController] [Callback] OnSecondAdProviderLoaded(ecpm: {ecpm})");

_secondAdProviderEcpm = ecpm;
_isSecondAdProviderLoaded = true;
_interstitialAdLoadRetryAttempt = 0;
}

private void OnFirstAdProviderLoadFailed(object sender, string cause)
{
AdHelper.Log($"[PostBidInterstitialAdController] [Callback] OnFirstAdProviderLoadFailed(cause: {cause})");

_firstAdProviderEcpm = 0d;
_isFirstAdProviderLoaded = false;

SecondAdProvider?.LoadInterstitialAd(_defaultPriceFloor);
}

private void OnSecondAdProviderLoadFailed(object sender, string cause)
{
AdHelper.Log($"[PostBidInterstitialAdController] [Callback] OnSecondAdProviderLoadFailed(cause: {cause})");

_secondAdProviderEcpm = 0d;
_isSecondAdProviderLoaded = false;

if (_isFirstAdProviderLoaded) return;

_interstitialAdLoadRetryAttempt++;
int retryDelay = (int)Math.Pow(2, Math.Min(6, _interstitialAdLoadRetryAttempt));

LoadFirstAdProviderAfterDelay(retryDelay);
}

private void OnFirstAdProviderShown(object sender, EventArgs eventArgs)
{
AdHelper.Log("[PostBidInterstitialAdController] [Callback] OnFirstAdProviderShown()");
}

private void OnSecondAdProviderShown(object sender, EventArgs eventArgs)
{
AdHelper.Log("[PostBidInterstitialAdController] [Callback] OnSecondAdProviderShown()");
}

private void OnFirstAdProviderShowFailed(object sender, string cause)
{
AdHelper.Log($"[PostBidInterstitialAdController] [Callback] OnFirstAdProviderShowFailed(cause: {cause})");

_firstAdProviderEcpm = 0d;
_isFirstAdProviderLoaded = false;

OnAdShowFailed?.Invoke(this, EventArgs.Empty);

FirstAdProvider?.LoadInterstitialAd();
}

private void OnSecondAdProviderShowFailed(object sender, string cause)
{
AdHelper.Log($"[PostBidInterstitialAdController] [Callback] OnSecondAdProviderShowFailed(cause: {cause})");

_isSecondAdProviderLoaded = false;
_secondAdProviderEcpm = 0d;

OnAdShowFailed?.Invoke(this, EventArgs.Empty);

if (_isFirstAdProviderLoaded)
{
SecondAdProvider?.LoadInterstitialAd(AdHelper.GetIncrementedPriceFloor(_firstAdProviderEcpm, _defaultPriceFloor, _strategy, _step));
}
else
{
FirstAdProvider?.LoadInterstitialAd();
}
}

private void OnFirstAdProviderClosed(object sender, EventArgs eventArgs)
{
AdHelper.Log("[PostBidInterstitialAdController] [Callback] OnFirstAdProviderClosed()");

_firstAdProviderEcpm = 0d;
_isFirstAdProviderLoaded = false;

OnAdClosed?.Invoke(this, EventArgs.Empty);

FirstAdProvider?.LoadInterstitialAd();
}

private void OnSecondAdProviderClosed(object sender, EventArgs eventArgs)
{
AdHelper.Log("[PostBidInterstitialAdController] [Callback] OnSecondAdProviderClosed()");

_isSecondAdProviderLoaded = false;
_secondAdProviderEcpm = 0d;

OnAdClosed?.Invoke(this, EventArgs.Empty);

if (_isFirstAdProviderLoaded)
{
SecondAdProvider?.LoadInterstitialAd(AdHelper.GetIncrementedPriceFloor(_firstAdProviderEcpm, _defaultPriceFloor, _strategy, _step));
}
else
{
FirstAdProvider?.LoadInterstitialAd();
}
}
}


UnityMainThreadDispatcher

UnityMainThreadDispatcher is a class that allows to run code on the main thread from other threads.

UnityMainThreadDispatcher.cs
using System.Threading;
using UnityEngine;

internal static class UnityMainThreadDispatcher
{
private static SynchronizationContext _unityContext;

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void SetContext() => _unityContext = SynchronizationContext.Current;

public static void Post(SendOrPostCallback d, object state = null) => _unityContext?.Post(d, state);

public static void Send(SendOrPostCallback d, object state = null) => _unityContext?.Send(d, state);
}

SessionController

The SessionController class manages session information, including a unique session UUID and session uptime.

SessionController.cs
using System;
using System.Diagnostics;
using UnityEngine;

public class SessionController : MonoBehaviour
{
private Stopwatch _stopWatch;

private static SessionController _instance;
private static readonly object _lock = new object();

public static SessionController Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
var gameObject = new GameObject(nameof(SessionController));
_instance = gameObject.AddComponent<SessionController>();
DontDestroyOnLoad(gameObject);
}
}
}
return _instance;
}
}

public string SessionUuid { get; private set; }
public long SessionStartTimestamp { get; private set; }
public long SessionUptime { get => _stopWatch?.ElapsedMilliseconds ?? 0; }

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
public static void StartSession()
{
Instance.SessionUuid = Guid.NewGuid().ToString();

Instance.SessionStartTimestamp = DateTime.Now.ToBinary();

Instance._stopWatch = new Stopwatch();
Instance._stopWatch?.Start();
}
}