Trusted Arbiter
Compare CloudX bids with supported third-party bids in Unity games
Trusted Arbiter compares a loaded CloudX bid with supported third-party bids and returns the selected platform. Available from Unity SDK 4.1.0 (backed by Android SDK 4.1.1 and iOS SDK 3.4.1), it supports CloudX, Unity LevelPlay, PubMatic, AdMob, and Google Ad Manager bid inputs. Custom publisher-supplied bid inputs require Unity SDK 4.4.1 or later; AdMob and Google Ad Manager bid inputs require Unity SDK 4.5.0 or later.
When to run the arbiter
Run the arbiter when the candidate ads finish loading — never on the show path. The arbiter call is a network round trip, and a user who taps a button that should produce an ad must not sit waiting on it. By the time the placement is reached, the decision should already be made.
Fullscreen formats (interstitial, rewarded, app open): prepare ahead
Load every candidate in parallel. Once they have all settled — loaded or failed — run the arbiter and store the result. At the placement, show the stored winner immediately: no arbiter call, no network call, nothing to wait for.
private CloudXArbiterResult _nextWinner;
// Runs as soon as the candidates have settled, ahead of the placement.
private void PrepareWinner(IReadOnlyList<CloudXArbiterBid> bids)
{
CloudXSdk.Arbiter(bids, result => _nextWinner = result);
}
// Runs at the placement. No network call here.
private void ShowInterstitial(string placement)
{
switch (_nextWinner?.Platform)
{
case CloudXArbiterPlatform.CloudX:
CloudXSdk.ShowInterstitial("YOUR_CLOUDX_AD_UNIT_ID", placement);
break;
case CloudXArbiterPlatform.AdMob:
adMobInterstitial.Show();
break;
default:
break; // no winner prepared; continue without an ad
}
_nextWinner = null;
}Start the cycle again — load candidates, arbitrate, store — after the ad is shown or hidden, after a show failure, and whenever a candidate expires. If the placement arrives before a winner has been stored, either continue without an ad or show the single candidate that did load. That is an acceptable degraded path, never the primary one.
View formats (banner, MREC): arbitrate, then render
Nothing user-initiated is waiting on a banner or MREC, so here the arbiter completing is the trigger to show. Attaching and rendering the winner directly inside the completion callback is correct for these formats. Only the winner’s view may ever be attached — see Banner and MREC arbitration for the attachment and refresh rules.
Supported ad formats
Trusted Arbiter is format-agnostic — it takes any loaded CloudX ad, regardless of format.
- Fullscreen formats — interstitial, rewarded, app open. Prepare a winner ahead of the placement, as described in When to run the arbiter. The step-by-step walkthrough and controller example below follow that pattern.
- View formats — banner and MREC. Arbitrate, then render the winner. These need the additional handling covered in Banner and MREC arbitration below, since a losing bid’s view must never be attached.
Basic API
Create bid candidates from loaded ads, then pass them to CloudXSdk.Arbiter(). The example below shows CloudX, LevelPlay, and PubMatic; use the dedicated sections below for custom, AdMob, and Google Ad Manager bid inputs.
using System.Collections.Generic;
using CloudX;
using UnityEngine;
// cloudXAd is the CloudXAd from a CloudX OnAdLoadSuccess callback.
// Its AdValues map carries the trusted-payload keys the server uses to validate the bid.
// levelPlayNetwork / levelPlayRevenue / levelPlayPrecision come from the Unity LevelPlay ad info.
// pubMaticPrice and pubMaticPartner come from the PubMatic/OpenWrap bid object.
var bids = new List<CloudXArbiterBid>
{
new CloudXArbiterBid.CloudX(cloudXAd),
new CloudXArbiterBid.LevelPlay(
NetworkName: levelPlayNetwork,
Revenue: levelPlayRevenue,
Precision: levelPlayPrecision),
new CloudXArbiterBid.PubMatic(
Price: pubMaticPrice,
PartnerName: pubMaticPartner),
};
CloudXSdk.Arbiter(bids, result =>
{
Debug.Log($"Selected platform: {result.Platform}");
});CloudXArbiterBid.CloudX accepts the CloudXAd object from a CloudX load callback. CloudXArbiterBid.LevelPlay accepts Unity LevelPlay ad info values. CloudXArbiterBid.PubMatic accepts a PubMatic OpenWrap bid price and optional partner name. CloudXArbiterBid.AdMob and CloudXArbiterBid.Gam accept the loaded Google ad unit id and can be priced from reported revenue. The completion callback runs on the Unity main thread. For fullscreen formats, store the result there and show it at the placement — see When to run the arbiter; view formats may render the winner directly from it.
result.Platform is CloudXArbiterPlatform.CloudX, LevelPlay, PubMatic, Custom, AdMob, or Gam for the selected platform, or CloudXArbiterPlatform.None when no winner could be selected — either because no bids were supplied, or because no supplied candidate carried a locally comparable price during fallback. The result also exposes result.Id (the auction identifier), result.BidId (the winning bid identifier, null when the platform is None), and result.Extras (additional metadata from the winning network).
AdMob and Google Ad Manager
CloudX compares a loaded CloudX ad with a loaded AdMob or Google Ad Manager ad. AdMob and Ad Manager are separate demand sources, so both may bid in the same arbitration. This applies to publisher-managed mediation setups, including accounts described commercially as AdMob Pro; there is no separate AdMob Pro SDK API.
Google demand does not normally reveal the price of a loaded ad before it is shown, so there is no pre-bid price for the arbiter to compare against CloudX’s bid. CloudX estimates the bid price from the prior performance of similar ad units, so you do not need to supply a price. No pre-bid pricing API is required.
If your AdMob account exposes impression-level revenue data pre-bid, you can supply that exact price yourself instead of using the estimate — see Manual input with pre-bid ILRD below.
Report Google paid events back to CloudX (required)
Forwarding Google’s impression-level revenue is a required part of the Trusted Arbiter AdMob and Ad Manager integration. After you show an AdMob or Ad Manager ad that won an arbitration, forward the Google Mobile Ads Unity plugin’s OnAdPaid event into the CloudX SDK. Without it, CloudX never learns the realized price of Google demand, and the arbiter’s estimates for future rounds degrade.
Each ad object exposes OnAdPaid with an AdValue. AdValue.Value is reported in micro-units, so divide it by 1_000_000.0, map the precision, and pass the result to CloudXSdk.ReportRevenueData(). Use CloudXRevenuePlatform.AdMob for AdMob ads and CloudXRevenuePlatform.Gam for Ad Manager ads.
private static CloudXRevenuePrecision ToCloudXRevenuePrecision(AdValue.PrecisionType precision) => precision switch
{
AdValue.PrecisionType.Precise => CloudXRevenuePrecision.Exact,
AdValue.PrecisionType.Estimated => CloudXRevenuePrecision.Estimated,
AdValue.PrecisionType.PublisherProvided => CloudXRevenuePrecision.PublisherDefined,
_ => CloudXRevenuePrecision.Undefined,
};
private static bool ReportGooglePaidEvent(
CloudXRevenuePlatform platform,
AdValue adValue,
string adFormat,
string adUnitId)
{
// adValue.Value is in micro-units of adValue.CurrencyCode.
return CloudXSdk.ReportRevenueData(new CloudXRevenueData(
Platform: platform,
Revenue: adValue.Value / 1_000_000.0,
AdFormat: adFormat,
CurrencyCode: adValue.CurrencyCode,
Precision: ToCloudXRevenuePrecision(adValue.Precision),
AdUnitId: adUnitId));
}
// AdMob ad that competed in the arbitration.
adMobInterstitial.OnAdPaid += adValue =>
{
ReportGooglePaidEvent(CloudXRevenuePlatform.AdMob, adValue, "interstitial", adMobAdUnitId);
};
// Ad Manager ad that competed in the arbitration.
adManagerInterstitial.OnAdPaid += adValue =>
{
ReportGooglePaidEvent(CloudXRevenuePlatform.Gam, adValue, "interstitial", adManagerAdUnitId);
};See Publisher-Reported Revenue Data for the full reporting setup, including the other supported platforms and every optional field.
Create the bid from the ad unit id of the ad you loaded:
// NetworkName is optional; pass the winning ad source when you know it.
var adMobBid = new CloudXArbiterBid.AdMob(
AdUnitId: adMobAdUnitId,
NetworkName: adMobNetworkName ?? "admob");
// An Ad Manager ad unit id takes the form /NNNNNNN/placement/name.
var adManagerBid = new CloudXArbiterBid.Gam(AdUnitId: "/21775744923/example/interstitial");
var bids = new List<CloudXArbiterBid>
{
new CloudXArbiterBid.CloudX(cloudXAd),
adMobBid,
adManagerBid,
};
// Runs once the candidates have settled, ahead of the placement.
CloudXSdk.Arbiter(bids, result => _nextWinner = result);Then, at the placement, show the stored winner. No arbiter call and no network call happens here:
private CloudXArbiterResult _nextWinner;
private void ShowInterstitial(string placement)
{
switch (_nextWinner?.Platform)
{
case CloudXArbiterPlatform.CloudX:
CloudXSdk.ShowInterstitial("YOUR_CLOUDX_AD_UNIT_ID", placement);
break;
case CloudXArbiterPlatform.AdMob:
adMobInterstitial.Show();
break;
case CloudXArbiterPlatform.Gam:
adManagerInterstitial.Show();
break;
default:
break; // no winner prepared; continue without an ad
}
_nextWinner = null;
}A winning Google bid reports its own platform — CloudXArbiterPlatform.AdMob or CloudXArbiterPlatform.Gam. You no longer need to inspect result.PlatformName to tell these sources apart, as you would for a custom bid.
Pass a blank ad unit id and the bid still builds rather than crashing your app, but it carries no usable identity: it is never priced and the server rejects it.
Manual input with pre-bid ILRD
Some AdMob accounts expose impression-level revenue data pre-bid: the AdValue for the loaded ad is available at load time, before the ad is shown. This is a legacy, account-gated capability, so check with your Google account team whether it is enabled for your account. An exact per-impression price that you know before show is the one case where supplying your own price beats CloudX’s estimate.
Pass the pre-bid ad value as ManualRevenuePerImpressionUSD and it overrides the estimate:
var adMobBid = new CloudXArbiterBid.AdMob(
AdUnitId: adMobAdUnitId,
NetworkName: adMobNetworkName ?? "admob",
// AdValue.Value is in micros: 1,000,000 micros is one currency unit.
// Do not also divide by 1,000 — this is already a per-impression value, not a CPM.
ManualRevenuePerImpressionUSD: preBidAdValue.Value / 1_000_000.0);How the value is treated:
0.0is a real price. It means this bid is worth nothing — not that the price is missing.- Negative and non-finite values are not prices, so they are treated as absent and logged.
- A blank ad unit id drops the manual price entirely, because a bid with no identity cannot be validated.
ManualRevenuePerImpressionUSD is revenue for a single impression, in USD — not CPM. Convert whatever your source reports:
- A Google ad value is reported in micros by the Unity Google Mobile Ads SDK, so divide by 1,000,000. An
AdValue.Valueof5000is0.005per impression. Do not confuse a micros value with an eCPM and divide by 1,000 as well. - A non-USD amount must be converted to USD first.
Step-by-step: arbitrate CloudX and LevelPlay
This walkthrough shows exactly which Unity LevelPlay callback to read and which values to pass into CloudXSdk.Arbiter(). It uses an interstitial, but the same field mapping applies to any format — see Supported ad formats for the handling each format needs.
Load both candidates
Create the LevelPlay interstitial and start a load on each platform. Subscribe to the load callbacks before loading.
var levelPlayAd = new LevelPlayInterstitialAd("YOUR_LEVELPLAY_AD_UNIT_ID");
levelPlayAd.OnAdLoaded += OnLevelPlayLoaded;
levelPlayAd.OnAdLoadFailed += OnLevelPlayLoadFailed;
levelPlayAd.LoadAd();
CloudXAdsCallbacks.Interstitial.OnAdLoadSuccess += OnCloudXLoaded;
CloudXSdk.LoadInterstitial("YOUR_CLOUDX_AD_UNIT_ID");Capture each platform's loaded ad
LevelPlay delivers a LevelPlayAdInfo in its OnAdLoaded callback; CloudX delivers a CloudXAd in OnAdLoadSuccess. Hold onto both — you read the arbiter inputs from them in the next step.
private CloudXAd _cloudXAd;
private LevelPlayAdInfo _levelPlayInfo;
private void OnCloudXLoaded(CloudXAd ad) => _cloudXAd = ad;
private void OnLevelPlayLoaded(LevelPlayAdInfo info) => _levelPlayInfo = info;Map the values into bids
Read the LevelPlay fields off LevelPlayAdInfo and pass them to CloudXArbiterBid.LevelPlay. The CloudX bid takes the CloudXAd directly.
LevelPlayAdInfo field | Type | CloudXArbiterBid.LevelPlay parameter |
|---|---|---|
adNetwork | string | NetworkName |
revenue | double? | Revenue (coalesce with ?? 0) |
precision | string | Precision |
var bids = new List<CloudXArbiterBid>
{
new CloudXArbiterBid.CloudX(_cloudXAd),
new CloudXArbiterBid.LevelPlay(
NetworkName: _levelPlayInfo.adNetwork, // LevelPlayAdInfo.adNetwork
Revenue: _levelPlayInfo.revenue ?? 0, // LevelPlayAdInfo.revenue is double?
Precision: _levelPlayInfo.precision), // LevelPlayAdInfo.precision
};Run the arbiter and store the winner
Run the arbiter as soon as both candidates have settled — well before the placement — and store the result. The callback runs on the Unity main thread.
private CloudXArbiterResult _nextWinner;
private void PrepareWinner(IReadOnlyList<CloudXArbiterBid> bids)
{
CloudXSdk.Arbiter(bids, OnArbiterCompleted);
}
private void OnArbiterCompleted(CloudXArbiterResult result) => _nextWinner = result;Show the stored winner at the placement
At the placement, switch on the stored Platform and show that platform’s ad immediately. There is no arbiter call here. A stored CloudXArbiterPlatform.None — or no stored result at all — means no winner is prepared, so continue without showing an ad.
private void ShowInterstitial(string placement)
{
switch (_nextWinner?.Platform)
{
case CloudXArbiterPlatform.CloudX:
CloudXSdk.ShowInterstitial("YOUR_CLOUDX_AD_UNIT_ID", placement);
break;
case CloudXArbiterPlatform.LevelPlay:
levelPlayAd.ShowAd(placement);
break;
default:
break; // no winner prepared; continue without an ad
}
_nextWinner = null;
}The ArbiterInterstitialController below packages these same steps into a reusable component — it is the reference implementation of the prepare-ahead rule.
Interstitial example
This interstitial example is the reference implementation of the prepare-ahead rule, arbitrating between two platforms: CloudX and Unity LevelPlay.
- Load CloudX and LevelPlay in parallel.
- Wait until both platforms have loaded or failed.
- Submit only loaded candidates to Trusted Arbiter.
- Cache the selected platform.
- At the placement, show the cached winner immediately.
If both platforms fail, start another load cycle. If the placement is reached before a winner is prepared, continue the app flow without showing an ad.
using System.Collections.Generic;
using CloudX;
using UnityEngine;
/// <summary>
/// Prepares a Trusted Arbiter winner ahead of time so an interstitial can be shown
/// instantly when a placement is reached.
///
/// Loads the CloudX and LevelPlay interstitials in parallel, waits until both have
/// finished loading or failing, submits the loaded candidates to CloudXSdk.Arbiter,
/// and caches the selected CloudXArbiterPlatform in _nextWinner.
/// </summary>
public class ArbiterInterstitialController : MonoBehaviour
{
private const string CloudXAdUnitId = "interstitial_main";
// Set by the host once the LevelPlay Unity SDK reports a loaded interstitial.
public string LevelPlayNetwork;
public double LevelPlayRevenue;
public string LevelPlayPrecision;
// Invoked once the arbiter has selected a platform for the next show.
public System.Action<CloudXArbiterPlatform> OnWinnerPrepared;
private CloudXAd _cloudXAd;
private bool _cloudXLoadDone;
private bool _levelPlayLoaded;
private bool _levelPlayLoadDone;
private CloudXArbiterPlatform? _nextWinner;
private void OnEnable()
{
CloudXAdsCallbacks.Interstitial.OnAdLoadSuccess += OnCloudXLoaded;
CloudXAdsCallbacks.Interstitial.OnAdLoadFailed += OnCloudXLoadFailed;
CloudXAdsCallbacks.Interstitial.OnAdHidden += OnCloudXHidden;
CloudXAdsCallbacks.Interstitial.OnAdShowFailed += OnCloudXShowFailed;
}
private void OnDisable()
{
CloudXAdsCallbacks.Interstitial.OnAdLoadSuccess -= OnCloudXLoaded;
CloudXAdsCallbacks.Interstitial.OnAdLoadFailed -= OnCloudXLoadFailed;
CloudXAdsCallbacks.Interstitial.OnAdHidden -= OnCloudXHidden;
CloudXAdsCallbacks.Interstitial.OnAdShowFailed -= OnCloudXShowFailed;
}
/// <summary>Starts a load for each platform that does not currently hold a cached ad.</summary>
public void LoadMissingAds()
{
if (_cloudXAd == null) CloudXSdk.LoadInterstitial(CloudXAdUnitId);
if (!_levelPlayLoaded) LoadLevelPlayInterstitial();
}
/// <summary>
/// Shows the prepared winner, returning true only when a show call was made.
///
/// Returns false when no winner is ready or the cached ad is no longer available, in which
/// case a fresh load cycle is started.
/// </summary>
public bool ShowAtPlacement(string placement)
{
switch (_nextWinner)
{
case CloudXArbiterPlatform.CloudX:
return ShowCloudX(placement);
case CloudXArbiterPlatform.LevelPlay:
return ShowLevelPlay(placement);
default:
return false;
}
}
/// <summary>
/// Runs the arbiter once both platforms have settled, then caches the winning platform.
///
/// Returns early until both loads complete. If neither platform loaded, it restarts the
/// load cycle; otherwise it submits the loaded candidates to CloudXSdk.Arbiter.
/// </summary>
private void MaybePrepareWinner()
{
if (!_cloudXLoadDone || !_levelPlayLoadDone) return;
if (_cloudXAd == null && !_levelPlayLoaded)
{
_cloudXLoadDone = false;
_levelPlayLoadDone = false;
LoadMissingAds();
return;
}
var bids = new List<CloudXArbiterBid>();
if (_cloudXAd != null)
{
bids.Add(new CloudXArbiterBid.CloudX(_cloudXAd));
}
if (_levelPlayLoaded)
{
bids.Add(new CloudXArbiterBid.LevelPlay(
NetworkName: LevelPlayNetwork,
Revenue: LevelPlayRevenue,
Precision: LevelPlayPrecision));
}
CloudXSdk.Arbiter(bids, result =>
{
_nextWinner = result.Platform;
OnWinnerPrepared?.Invoke(result.Platform);
});
}
private bool ShowCloudX(string placement)
{
if (CloudXSdk.IsInterstitialReady(CloudXAdUnitId))
{
CloudXSdk.ShowInterstitial(CloudXAdUnitId, placement);
return true;
}
ClearCloudXAndLoadMissingAds();
return false;
}
private bool ShowLevelPlay(string placement)
{
if (IsLevelPlayInterstitialReady())
{
ShowLevelPlayInterstitial(placement);
return true;
}
ClearLevelPlayAndLoadMissingAds();
return false;
}
private void OnCloudXLoaded(CloudXAd ad)
{
_cloudXAd = ad;
_cloudXLoadDone = true;
MaybePrepareWinner();
}
private void OnCloudXLoadFailed(string adUnitId, CloudXError error)
{
_cloudXAd = null;
_cloudXLoadDone = true;
MaybePrepareWinner();
}
private void OnCloudXHidden(CloudXAd ad) => ClearCloudXAndLoadMissingAds();
private void OnCloudXShowFailed(CloudXAd ad, CloudXError error) => ClearCloudXAndLoadMissingAds();
private void ClearCloudXAndLoadMissingAds()
{
_cloudXAd = null;
_cloudXLoadDone = false;
_nextWinner = null;
LoadMissingAds();
}
private void ClearLevelPlayAndLoadMissingAds()
{
_levelPlayLoaded = false;
_levelPlayLoadDone = false;
_nextWinner = null;
LoadMissingAds();
}
/*
* The following members wrap the LevelPlay Unity SDK. Replace the bodies with calls into
* the LevelPlay SDK you integrate, and route its load callbacks into the fields above:
* on load success, set _levelPlayLoaded = true and the LevelPlay* values, then set
* _levelPlayLoadDone = true and call MaybePrepareWinner(); on load failure, set
* _levelPlayLoaded = false, _levelPlayLoadDone = true, and call MaybePrepareWinner().
* On ad close or show failure, call ClearLevelPlayAndLoadMissingAds() to drop the
* consumed ad and start a fresh cycle.
*/
private void LoadLevelPlayInterstitial() { /* LevelPlay.LoadInterstitial(...) */ }
private bool IsLevelPlayInterstitialReady() => _levelPlayLoaded;
private void ShowLevelPlayInterstitial(string placement) { /* LevelPlay.ShowInterstitial(...) */ }
}ShowAtPlacement() returns true only when an ad show call was made. Keep the cached LevelPlay candidate values up to date while the LevelPlay ad remains loaded.
For PubMatic OpenWrap, create a third-party bid with new CloudXArbiterBid.PubMatic(price, partnerName). If the arbiter service is unavailable or the call times out, the SDK falls back to the highest comparable USD bid among the supplied inputs — you never need a fallback of your own.
Banner and MREC arbitration
Banner and MREC are view formats: unlike the fullscreen formats, more than one candidate can be loaded and attached to a view at the same time, so arbitration needs extra handling beyond the basic API above. Nothing user-initiated is waiting here, so rendering the winner from inside the completion callback is the correct pattern for these formats. MREC uses the same flow as banner — this section covers both.
Disable auto-refresh
Both platforms auto-refresh independently by default, which would race against arbitration and reload ads out from under the arbiter. Disable it on every arbitrated network before arbitrating:
- Turn off auto-refresh for the ad unit in the CloudX dashboard, and call
CloudXSdk.StopBannerAutoRefresh(adUnitId)(orCloudXSdk.StopMrecAutoRefresh(adUnitId)for MREC) after creating the ad. - Disable auto-refresh on the other arbitrated networks’ banner APIs as well.
View attachment
Only the winning bid’s banner may be shown or displayed. Showing a losing ad renders it and fires an impression on a bid the arbiter didn’t select, so losing candidates must stay loaded but hidden.
Refresh cycle
Because banner and MREC are shown continuously rather than at a single placement, arbitration repeats on an interval instead of running once. The recommended flow:
- Run the parallel loads, then the arbiter, then show the winner.
- After the winner’s impression fires, immediately start loading a new fill from the winning network.
- Retain the non-winning networks’ filled ads for the next arbitration round; re-request loads only from networks that did not fill in the previous round.
- Once the outstanding load responses return, run the arbiter again.
- Refresh the displayed ad on a 20–30 second interval, swapping in the new winner. Intervals shorter than 20 seconds decrease CPM performance.
Banner example
This banner example arbitrates between CloudX and Unity LevelPlay banners on a recurring refresh cycle:
- Disable auto-refresh on both platforms and load both banners in parallel.
- Run the arbiter once both have loaded or failed, and show the winner’s view.
- On the winner’s impression, load a fresh fill from the winning network; keep the loser’s current fill for the next round.
- Re-request loads only from networks that did not fill.
- Once outstanding loads settle, run the arbiter again and swap the displayed view to the new winner.
- Repeat on a 20–30 second timer.
using System.Collections.Generic;
using CloudX;
using UnityEngine;
/// <summary>
/// Runs Trusted Arbiter on a recurring interval to decide which of the CloudX or
/// LevelPlay banners is shown for "banner_main".
///
/// Auto-refresh is disabled on both networks so arbitration controls loading and
/// display directly: only the arbiter's winner is ever shown, the loser is kept
/// loaded but hidden, and the winner's own impression triggers its next fill.
/// </summary>
public class ArbiterBannerController : MonoBehaviour
{
private const string CloudXAdUnitId = "banner_main";
private const float RefreshIntervalSeconds = 25f;
// Set by the host once the LevelPlay Unity SDK reports a loaded banner.
public string LevelPlayNetwork;
public double LevelPlayRevenue;
public string LevelPlayPrecision;
private CloudXAd _cloudXAd;
private bool _cloudXLoaded;
private bool _cloudXLoadDone;
private bool _levelPlayLoaded;
private bool _levelPlayLoadDone;
private CloudXArbiterPlatform? _shownPlatform;
private float _refreshTimer;
private void OnEnable()
{
var config = new CloudXAdViewConfiguration(CloudXAdViewConfiguration.AdViewPosition.BottomCenter);
CloudXSdk.CreateBanner(CloudXAdUnitId, config);
CloudXSdk.StopBannerAutoRefresh(CloudXAdUnitId);
CloudXAdsCallbacks.Banner.OnAdLoadSuccess += OnCloudXLoaded;
CloudXAdsCallbacks.Banner.OnAdLoadFailed += OnCloudXLoadFailed;
CloudXAdsCallbacks.Banner.OnAdRevenuePaid += OnCloudXImpression;
LoadMissingAds();
}
private void OnDisable()
{
CloudXAdsCallbacks.Banner.OnAdLoadSuccess -= OnCloudXLoaded;
CloudXAdsCallbacks.Banner.OnAdLoadFailed -= OnCloudXLoadFailed;
CloudXAdsCallbacks.Banner.OnAdRevenuePaid -= OnCloudXImpression;
}
private void Update()
{
if (_shownPlatform == null) return;
_refreshTimer += Time.deltaTime;
if (_refreshTimer >= RefreshIntervalSeconds)
{
_refreshTimer = 0f;
RunArbiter();
}
}
/// <summary>Starts a load for each network that does not currently hold a filled ad.</summary>
private void LoadMissingAds()
{
if (!_cloudXLoaded) CloudXSdk.LoadBanner(CloudXAdUnitId);
if (!_levelPlayLoaded) LoadLevelPlayBanner();
}
/// <summary>
/// Submits the currently filled candidates to CloudXSdk.Arbiter and shows only the
/// winner's view, hiding the loser so it never renders or fires an impression.
/// </summary>
private void RunArbiter()
{
if (!_cloudXLoadDone || !_levelPlayLoadDone) return;
var bids = new List<CloudXArbiterBid>();
if (_cloudXLoaded) bids.Add(new CloudXArbiterBid.CloudX(_cloudXAd));
if (_levelPlayLoaded)
{
bids.Add(new CloudXArbiterBid.LevelPlay(
NetworkName: LevelPlayNetwork,
Revenue: LevelPlayRevenue,
Precision: LevelPlayPrecision));
}
if (bids.Count == 0) return;
CloudXSdk.Arbiter(bids, result =>
{
_shownPlatform = result.Platform;
ShowWinnerHideLoser(result.Platform);
});
}
private void ShowWinnerHideLoser(CloudXArbiterPlatform winner)
{
if (winner == CloudXArbiterPlatform.CloudX)
{
CloudXSdk.ShowBanner(CloudXAdUnitId);
HideLevelPlayBanner();
}
else if (winner == CloudXArbiterPlatform.LevelPlay)
{
CloudXSdk.HideBanner(CloudXAdUnitId);
ShowLevelPlayBanner();
}
}
private void OnCloudXLoaded(CloudXAd ad)
{
_cloudXAd = ad;
_cloudXLoaded = true;
_cloudXLoadDone = true;
RunArbiter();
}
private void OnCloudXLoadFailed(string adUnitId, CloudXError error)
{
_cloudXLoaded = false;
_cloudXLoadDone = true;
RunArbiter();
}
/// <summary>
/// Fires once the shown CloudX banner's impression is confirmed. Immediately starts
/// loading the next fill from the winning network; the non-winning network's current
/// fill is retained until the next arbitration round.
/// </summary>
private void OnCloudXImpression(CloudXAd ad)
{
if (_shownPlatform != CloudXArbiterPlatform.CloudX) return;
_cloudXLoaded = false;
_cloudXLoadDone = false;
CloudXSdk.LoadBanner(CloudXAdUnitId);
}
/*
* The following members wrap the LevelPlay Unity SDK. Replace the bodies with calls
* into the LevelPlay SDK you integrate, and route its callbacks into the fields above:
* on load success, set _levelPlayLoaded = true and the LevelPlay* values, then set
* _levelPlayLoadDone = true and call RunArbiter(); on load failure, set
* _levelPlayLoaded = false, _levelPlayLoadDone = true, and call RunArbiter(). On the
* LevelPlay banner's impression callback, when it is the shown platform, set
* _levelPlayLoaded = false, _levelPlayLoadDone = false, and start its next load —
* mirroring OnCloudXImpression above. Disable LevelPlay banner auto-refresh alongside
* CloudX's in OnEnable.
*/
private void LoadLevelPlayBanner() { /* LevelPlay.LoadBanner(...); disable its auto-refresh */ }
private void ShowLevelPlayBanner() { /* LevelPlay banner view.Show() */ }
private void HideLevelPlayBanner() { /* LevelPlay banner view.Hide() */ }
}RunArbiter() only shows the winner’s view — the losing network’s ad stays loaded but hidden until it wins a later round. For MREC, use CloudXSdk.CreateMrec, CloudXSdk.LoadMrec, CloudXSdk.ShowMrec, CloudXSdk.HideMrec, and CloudXSdk.StopMrecAutoRefresh in place of the banner equivalents — the arbitration flow is otherwise identical.
Custom Bid Inputs
Use CloudXArbiterBid.Custom when you want Trusted Arbiter to compare CloudX with a third-party platform that does not have a dedicated bid type.
var customBid = new CloudXArbiterBid.Custom(
PlatformName: "my_mediation_platform",
NetworkName: "winning_demand_source",
RevenuePerImpressionUSD: 0.00125,
Precision: "EXACT",
Extras: new Dictionary<string, string> { ["ad_unit"] = "third-party-ad-unit-id" });
var bids = new List<CloudXArbiterBid>
{
new CloudXArbiterBid.CloudX(cloudXAd),
customBid,
};
CloudXSdk.Arbiter(bids, result =>
{
Debug.Log($"Selected platform: {result.Platform}");
});When a custom bid wins, result.Platform is CloudXArbiterPlatform.Custom and result.PlatformName contains the PlatformName supplied on the bid. Pass RevenuePerImpressionUSD as revenue for one impression in USD, not CPM. Use "EXACT", "ESTIMATED", "PUBLISHER_DEFINED", or "UNDEFINED" for Precision to describe that revenue value. A custom bid missing a non-blank PlatformName, revenue, or precision is dropped with a warning and does not compete.