You've already forked Commercialization.tapadn
Add iOS support for TapADN package
This commit is contained in:
@@ -5,5 +5,7 @@
|
|||||||
* 接入 Dirichlet/TapADN 聚合 Unity SDK `4.2.5.0`。
|
* 接入 Dirichlet/TapADN 聚合 Unity SDK `4.2.5.0`。
|
||||||
* 新增 `TapadnAdController`、激励视频、插屏、开屏播放器,实现 `CC-Framework.Commercialization` 抽象层。
|
* 新增 `TapadnAdController`、激励视频、插屏、开屏播放器,实现 `CC-Framework.Commercialization` 抽象层。
|
||||||
* 新增 Android 构建后处理:Manifest 权限、TapADN FileProvider、微信 OpenSDK WXEntryActivity/queries、AndroidX/Jetifier 属性。
|
* 新增 Android 构建后处理:Manifest 权限、TapADN FileProvider、微信 OpenSDK WXEntryActivity/queries、AndroidX/Jetifier 属性。
|
||||||
|
* 新增 iOS 构建后处理:CocoaPods、SKAdNetwork、ATT 文案、AppTrackingTransparency 弱链接、GDT 动态 framework 嵌入。
|
||||||
|
* iOS 激励、插屏、开屏 auto API 增加 load-then-show 兼容 fallback,并补齐展示失败回调。
|
||||||
* 新增 `TapadnCommercialization` 便捷入口,由实现模块创建 controller 并初始化 `ADManager`。
|
* 新增 `TapadnCommercialization` 便捷入口,由实现模块创建 controller 并初始化 `ADManager`。
|
||||||
* 官方 Demo Sample 从主包剔除,调试内容改为可选 `Samples~`。
|
* 官方 Demo Sample 从主包剔除,调试内容改为可选 `Samples~`。
|
||||||
|
|||||||
@@ -25,12 +25,17 @@ namespace Dirichlet.Mediation.Editor
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DirichletMediationIOSPostProcessor
|
public class DirichletMediationIOSPostProcessor
|
||||||
{
|
{
|
||||||
private const string SDKVersion = "4.2.0.2";
|
private const string DefaultIOSSDKVersion = "4.2.0.1";
|
||||||
private const string MinIOSVersion = "11.0";
|
private const string MinIOSVersion = "11.0";
|
||||||
|
private const string DefaultTrackingUsageDescription = "该标识符将用于向您投放个性化广告";
|
||||||
|
|
||||||
// 环境变量 override(仅在解析失败或接入方工程极端定制时使用)
|
// 环境变量 override(仅在解析失败或接入方工程极端定制时使用)
|
||||||
private const string ENV_FRAMEWORK_TARGET = "DIRICHLET_UNITY_FRAMEWORK_TARGET";
|
private const string ENV_FRAMEWORK_TARGET = "DIRICHLET_UNITY_FRAMEWORK_TARGET";
|
||||||
private const string ENV_APP_TARGET = "DIRICHLET_UNITY_APP_TARGET";
|
private const string ENV_APP_TARGET = "DIRICHLET_UNITY_APP_TARGET";
|
||||||
|
private const string ENV_IOS_SDK_VERSION = "DIRICHLET_IOS_SDK_VERSION";
|
||||||
|
private const string ENV_ATT_DESCRIPTION = "DIRICHLET_IOS_ATT_DESCRIPTION";
|
||||||
|
private const string PrefKeyIOSSDKVersion = "Dirichlet.iOS.SDKVersion";
|
||||||
|
private const string PrefKeyATTDescription = "Dirichlet.iOS.TrackingUsageDescription";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解析出的 target 信息
|
/// 解析出的 target 信息
|
||||||
@@ -363,6 +368,7 @@ namespace Dirichlet.Mediation.Editor
|
|||||||
// Note: DirichletAdSDK (DRA adapter) is always enabled as core SDK
|
// Note: DirichletAdSDK (DRA adapter) is always enabled as core SDK
|
||||||
var enableCsj = EditorPrefs.GetBool("Dirichlet.iOS.EnableCSJ", true);
|
var enableCsj = EditorPrefs.GetBool("Dirichlet.iOS.EnableCSJ", true);
|
||||||
var enableGdt = EditorPrefs.GetBool("Dirichlet.iOS.EnableGDT", true);
|
var enableGdt = EditorPrefs.GetBool("Dirichlet.iOS.EnableGDT", true);
|
||||||
|
var sdkVersion = ResolveIOSSDKVersion();
|
||||||
|
|
||||||
var podfileContent = new StringBuilder();
|
var podfileContent = new StringBuilder();
|
||||||
podfileContent.AppendLine("# Generated by Dirichlet Mediation Unity Plugin");
|
podfileContent.AppendLine("# Generated by Dirichlet Mediation Unity Plugin");
|
||||||
@@ -379,20 +385,20 @@ namespace Dirichlet.Mediation.Editor
|
|||||||
// 所有 pods 统一放到 Framework target
|
// 所有 pods 统一放到 Framework target
|
||||||
// SDK 通过 NSClassFromString 查找 Adapter 类,必须在同一 target 中
|
// SDK 通过 NSClassFromString 查找 Adapter 类,必须在同一 target 中
|
||||||
podfileContent.AppendLine($"target '{targetInfo.FrameworkTargetName}' do");
|
podfileContent.AppendLine($"target '{targetInfo.FrameworkTargetName}' do");
|
||||||
podfileContent.AppendLine($" pod 'DirichletMediationSDK', '{SDKVersion}'");
|
podfileContent.AppendLine($" pod 'DirichletMediationSDK', '{sdkVersion}'");
|
||||||
|
|
||||||
if (enableCsj)
|
if (enableCsj)
|
||||||
{
|
{
|
||||||
podfileContent.AppendLine($" pod 'DirichletMediationAdapterCSJ', '{SDKVersion}'");
|
podfileContent.AppendLine($" pod 'DirichletMediationAdapterCSJ', '{sdkVersion}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableGdt)
|
if (enableGdt)
|
||||||
{
|
{
|
||||||
podfileContent.AppendLine($" pod 'DirichletMediationAdapterGDT', '{SDKVersion}'");
|
podfileContent.AppendLine($" pod 'DirichletMediationAdapterGDT', '{sdkVersion}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
// DirichletAdSDK (DRA adapter) is always included as core SDK
|
// DirichletAdSDK (DRA adapter) is always included as core SDK
|
||||||
podfileContent.AppendLine($" pod 'DirichletMediationAdapterDRA', '{SDKVersion}'");
|
podfileContent.AppendLine($" pod 'DirichletMediationAdapterDRA', '{sdkVersion}'");
|
||||||
|
|
||||||
podfileContent.AppendLine("end");
|
podfileContent.AppendLine("end");
|
||||||
podfileContent.AppendLine();
|
podfileContent.AppendLine();
|
||||||
@@ -411,7 +417,24 @@ namespace Dirichlet.Mediation.Editor
|
|||||||
File.WriteAllText(podfilePath, podfileContent.ToString());
|
File.WriteAllText(podfilePath, podfileContent.ToString());
|
||||||
|
|
||||||
Debug.Log($"[DirichletMediation] Generated Podfile at {podfilePath}");
|
Debug.Log($"[DirichletMediation] Generated Podfile at {podfilePath}");
|
||||||
Debug.Log($"[DirichletMediation] All pods allocated to {targetInfo.FrameworkTargetName} (CSJ={enableCsj}, GDT={enableGdt}, DRA=always)");
|
Debug.Log($"[DirichletMediation] All pods allocated to {targetInfo.FrameworkTargetName} (version={sdkVersion}, CSJ={enableCsj}, GDT={enableGdt}, DRA=always)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveIOSSDKVersion()
|
||||||
|
{
|
||||||
|
var envVersion = System.Environment.GetEnvironmentVariable(ENV_IOS_SDK_VERSION);
|
||||||
|
if (!string.IsNullOrWhiteSpace(envVersion))
|
||||||
|
{
|
||||||
|
return envVersion.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefVersion = EditorPrefs.GetString(PrefKeyIOSSDKVersion, string.Empty);
|
||||||
|
if (!string.IsNullOrWhiteSpace(prefVersion))
|
||||||
|
{
|
||||||
|
return prefVersion.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultIOSSDKVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -466,13 +489,15 @@ namespace Dirichlet.Mediation.Editor
|
|||||||
Debug.Log($" Framework target: {targetInfo.FrameworkTargetName} (GUID: {targetGuid})");
|
Debug.Log($" Framework target: {targetInfo.FrameworkTargetName} (GUID: {targetGuid})");
|
||||||
Debug.Log($" App target: {targetInfo.AppTargetName} (GUID: {mainTargetGuid})");
|
Debug.Log($" App target: {targetInfo.AppTargetName} (GUID: {mainTargetGuid})");
|
||||||
|
|
||||||
// NOTE: System frameworks (AdSupport, AVFoundation, WebKit, CoreVideo, etc.)
|
// NOTE: Most system frameworks (AdSupport, AVFoundation, WebKit, CoreVideo, etc.)
|
||||||
// are declared in SDK podspecs and will be automatically linked by CocoaPods.
|
// are declared in SDK podspecs and will be automatically linked by CocoaPods.
|
||||||
// No need to manually add them here.
|
// AppTrackingTransparency is referenced by the Unity bridge, so add it explicitly and weak-link it for iOS 11+.
|
||||||
// - DirichletAdSDK.podspec: AdSupport, SystemConfiguration, Security
|
// - DirichletAdSDK.podspec: AdSupport, SystemConfiguration, Security
|
||||||
// - DirichletCoreSDK.podspec: SystemConfiguration, Security
|
// - DirichletCoreSDK.podspec: SystemConfiguration, Security
|
||||||
// - DirichletMediationAdapterCSJ.podspec: CoreVideo
|
// - DirichletMediationAdapterCSJ.podspec: CoreVideo
|
||||||
// - Third-party SDKs (Ads-CN, GDTMobSDK) declare their own framework dependencies.
|
// - Third-party SDKs (Ads-CN, GDTMobSDK) declare their own framework dependencies.
|
||||||
|
pbxProject.AddFrameworkToProject(targetGuid, "AppTrackingTransparency.framework", true);
|
||||||
|
pbxProject.AddFrameworkToProject(targetGuid, "AdSupport.framework", true);
|
||||||
|
|
||||||
// Set build settings for framework target
|
// Set build settings for framework target
|
||||||
pbxProject.SetBuildProperty(targetGuid, "ENABLE_BITCODE", "NO");
|
pbxProject.SetBuildProperty(targetGuid, "ENABLE_BITCODE", "NO");
|
||||||
@@ -591,16 +616,16 @@ namespace Dirichlet.Mediation.Editor
|
|||||||
// Note: The following Info.plist keys should be configured by the developer manually
|
// Note: The following Info.plist keys should be configured by the developer manually
|
||||||
// to avoid potential App Store review issues:
|
// to avoid potential App Store review issues:
|
||||||
// - NSAppTransportSecurity: Configure based on your app's network requirements
|
// - NSAppTransportSecurity: Configure based on your app's network requirements
|
||||||
// - NSUserTrackingUsageDescription: Required for iOS 14+ IDFA access, use your custom description
|
|
||||||
// - NSLocationWhenInUseUsageDescription: Only add if your app uses location services
|
// - NSLocationWhenInUseUsageDescription: Only add if your app uses location services
|
||||||
//
|
//
|
||||||
// Reference: https://ssp.dirichlet.cn/docs/dirichlet-mediation-sdk/dirichlet-mediation-sdk-guide-ios/
|
// Reference: https://ssp.dirichlet.cn/docs/dirichlet-mediation-sdk/dirichlet-mediation-sdk-guide-ios/
|
||||||
|
|
||||||
// Add SKAdNetwork identifiers for attribution tracking
|
// Add SKAdNetwork identifiers for attribution tracking
|
||||||
AddSKAdNetworkIds(rootDict);
|
AddSKAdNetworkIds(rootDict);
|
||||||
|
AddTrackingUsageDescription(rootDict);
|
||||||
|
|
||||||
plist.WriteToFile(plistPath);
|
plist.WriteToFile(plistPath);
|
||||||
Debug.Log("[DirichletMediation] Modified Info.plist (SKAdNetwork IDs only)");
|
Debug.Log("[DirichletMediation] Modified Info.plist (SKAdNetwork IDs + ATT description)");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddSKAdNetworkIds(PlistElementDict rootDict)
|
private static void AddSKAdNetworkIds(PlistElementDict rootDict)
|
||||||
@@ -631,6 +656,35 @@ namespace Dirichlet.Mediation.Editor
|
|||||||
Debug.Log($"[DirichletMediation] Added {commonSkAdNetworkIds.Length} SKAdNetwork IDs to Info.plist");
|
Debug.Log($"[DirichletMediation] Added {commonSkAdNetworkIds.Length} SKAdNetwork IDs to Info.plist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddTrackingUsageDescription(PlistElementDict rootDict)
|
||||||
|
{
|
||||||
|
if (rootDict.values.ContainsKey("NSUserTrackingUsageDescription"))
|
||||||
|
{
|
||||||
|
Debug.Log("[DirichletMediation] NSUserTrackingUsageDescription already exists, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootDict.SetString("NSUserTrackingUsageDescription", ResolveTrackingUsageDescription());
|
||||||
|
Debug.Log("[DirichletMediation] Added NSUserTrackingUsageDescription to Info.plist");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveTrackingUsageDescription()
|
||||||
|
{
|
||||||
|
var envDescription = System.Environment.GetEnvironmentVariable(ENV_ATT_DESCRIPTION);
|
||||||
|
if (!string.IsNullOrWhiteSpace(envDescription))
|
||||||
|
{
|
||||||
|
return envDescription.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefDescription = EditorPrefs.GetString(PrefKeyATTDescription, string.Empty);
|
||||||
|
if (!string.IsNullOrWhiteSpace(prefDescription))
|
||||||
|
{
|
||||||
|
return prefDescription.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultTrackingUsageDescription;
|
||||||
|
}
|
||||||
|
|
||||||
private static void RunPodInstall(string projectPath)
|
private static void RunPodInstall(string projectPath)
|
||||||
{
|
{
|
||||||
var podfilePath = Path.Combine(projectPath, "Podfile");
|
var podfilePath = Path.Combine(projectPath, "Podfile");
|
||||||
|
|||||||
@@ -366,6 +366,10 @@ namespace Dirichlet.Mediation
|
|||||||
private readonly IDirichletPlatformBridge bridge;
|
private readonly IDirichletPlatformBridge bridge;
|
||||||
private static readonly object RewardAutoSessionLock = new object();
|
private static readonly object RewardAutoSessionLock = new object();
|
||||||
private static readonly Dictionary<string, AutoRewardVideoSession> RewardAutoSessions = new Dictionary<string, AutoRewardVideoSession>(StringComparer.Ordinal);
|
private static readonly Dictionary<string, AutoRewardVideoSession> RewardAutoSessions = new Dictionary<string, AutoRewardVideoSession>(StringComparer.Ordinal);
|
||||||
|
private static readonly object InterstitialAutoSessionLock = new object();
|
||||||
|
private static readonly Dictionary<string, AutoInterstitialSession> InterstitialAutoSessions = new Dictionary<string, AutoInterstitialSession>(StringComparer.Ordinal);
|
||||||
|
private static readonly object SplashAutoSessionLock = new object();
|
||||||
|
private static readonly Dictionary<string, AutoSplashSession> SplashAutoSessions = new Dictionary<string, AutoSplashSession>(StringComparer.Ordinal);
|
||||||
|
|
||||||
public static DirichletAdNative Create() => DirichletAdManager.CreateAdNative();
|
public static DirichletAdNative Create() => DirichletAdManager.CreateAdNative();
|
||||||
|
|
||||||
@@ -478,7 +482,7 @@ namespace Dirichlet.Mediation
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows an interstitial ad with automatic load-and-show logic.
|
/// Shows an interstitial ad with automatic load-and-show logic.
|
||||||
/// Android only - iOS receives OnError with not_supported.
|
/// Android uses the native AutoAd API; iOS/editor use a load-then-show fallback without native cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener)
|
public void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener)
|
||||||
{
|
{
|
||||||
@@ -487,13 +491,17 @@ namespace Dirichlet.Mediation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||||
bridge.ShowInterstitialAutoAd(request, listener);
|
bridge.ShowInterstitialAutoAd(request, listener);
|
||||||
|
#else
|
||||||
|
ShowInterstitialLoadAndShowInternal(request, listener);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows a banner ad with automatic load + rotation logic. Container is created internally and
|
/// Shows a banner ad with automatic load + rotation logic. Container is created internally and
|
||||||
/// anchored at the bottom of the screen by default. Use <see cref="DirichletAdRequest.Builder.WithSlideInterval"/>
|
/// anchored at the bottom of the screen by default. Use <see cref="DirichletAdRequest.Builder.WithSlideInterval"/>
|
||||||
/// to control rotation interval. Android only - iOS receives OnError with not_supported.
|
/// to control rotation interval. Native auto rotation is currently Android only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ShowBannerAutoAd(DirichletAdRequest request, IDirichletBannerAutoAdListener listener)
|
public void ShowBannerAutoAd(DirichletAdRequest request, IDirichletBannerAutoAdListener listener)
|
||||||
{
|
{
|
||||||
@@ -516,7 +524,7 @@ namespace Dirichlet.Mediation
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows a splash ad with automatic load logic. Container is a fullscreen overlay created internally.
|
/// Shows a splash ad with automatic load logic. Container is a fullscreen overlay created internally.
|
||||||
/// Android only - iOS receives OnError with not_supported.
|
/// Android uses the native AutoAd API; iOS/editor use a load-then-show fallback without native cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ShowSplashAutoAd(DirichletAdRequest request, IDirichletSplashAutoAdListener listener)
|
public void ShowSplashAutoAd(DirichletAdRequest request, IDirichletSplashAutoAdListener listener)
|
||||||
{
|
{
|
||||||
@@ -533,7 +541,11 @@ namespace Dirichlet.Mediation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||||
bridge.ShowSplashAutoAd(request, options ?? new DirichletAdShowOptions(), listener);
|
bridge.ShowSplashAutoAd(request, options ?? new DirichletAdShowOptions(), listener);
|
||||||
|
#else
|
||||||
|
ShowSplashLoadAndShowInternal(request, options ?? new DirichletAdShowOptions(), listener);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -566,6 +578,7 @@ namespace Dirichlet.Mediation
|
|||||||
var session = new AutoRewardVideoSession(sessionId, ad, listener);
|
var session = new AutoRewardVideoSession(sessionId, ad, listener);
|
||||||
RegisterRewardAutoSession(session);
|
RegisterRewardAutoSession(session);
|
||||||
ad.SetInteractionListener(session);
|
ad.SetInteractionListener(session);
|
||||||
|
ad.ShowFailed += session.OnShowFailed;
|
||||||
|
|
||||||
// Load succeeded; show immediately.
|
// Load succeeded; show immediately.
|
||||||
var shown = ad.Show();
|
var shown = ad.Show();
|
||||||
@@ -580,6 +593,66 @@ namespace Dirichlet.Mediation
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShowInterstitialLoadAndShowInternal(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener)
|
||||||
|
{
|
||||||
|
LoadInterstitialAd(
|
||||||
|
request,
|
||||||
|
ad =>
|
||||||
|
{
|
||||||
|
if (ad == null)
|
||||||
|
{
|
||||||
|
listener?.OnError(new DirichletError("invalid_ad", "Load callback returned null ad"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessionId = Guid.NewGuid().ToString("N");
|
||||||
|
var session = new AutoInterstitialSession(sessionId, ad, listener);
|
||||||
|
RegisterInterstitialAutoSession(session);
|
||||||
|
ad.SetInteractionListener(session);
|
||||||
|
ad.ShowFailed += session.OnShowFailed;
|
||||||
|
|
||||||
|
var shown = ad.Show();
|
||||||
|
if (!shown)
|
||||||
|
{
|
||||||
|
session.FailAndDispose(new DirichletError("show_failed", "ShowInterstitialAd returned false"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error =>
|
||||||
|
{
|
||||||
|
listener?.OnError(error ?? new DirichletError("load_failed", "LoadInterstitialAd failed"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowSplashLoadAndShowInternal(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener)
|
||||||
|
{
|
||||||
|
LoadSplashAd(
|
||||||
|
request,
|
||||||
|
ad =>
|
||||||
|
{
|
||||||
|
if (ad == null)
|
||||||
|
{
|
||||||
|
listener?.OnError(new DirichletError("invalid_ad", "Load callback returned null ad"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessionId = Guid.NewGuid().ToString("N");
|
||||||
|
var session = new AutoSplashSession(sessionId, ad, listener);
|
||||||
|
RegisterSplashAutoSession(session);
|
||||||
|
ad.SetInteractionListener(session);
|
||||||
|
ad.ShowFailed += session.OnShowFailed;
|
||||||
|
|
||||||
|
var shown = ad.Show();
|
||||||
|
if (!shown)
|
||||||
|
{
|
||||||
|
session.FailAndDispose(new DirichletError("show_failed", "ShowSplashAd returned false"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error =>
|
||||||
|
{
|
||||||
|
listener?.OnError(error ?? new DirichletError("load_failed", "LoadSplashAd failed"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static bool ValidateRequest(DirichletAdRequest request, Action<DirichletError> onFailure)
|
private static bool ValidateRequest(DirichletAdRequest request, Action<DirichletError> onFailure)
|
||||||
{
|
{
|
||||||
if (request == null)
|
if (request == null)
|
||||||
@@ -628,6 +701,58 @@ namespace Dirichlet.Mediation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RegisterInterstitialAutoSession(AutoInterstitialSession session)
|
||||||
|
{
|
||||||
|
if (session == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (InterstitialAutoSessionLock)
|
||||||
|
{
|
||||||
|
InterstitialAutoSessions[session.SessionId] = session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UnregisterInterstitialAutoSession(string sessionId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(sessionId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (InterstitialAutoSessionLock)
|
||||||
|
{
|
||||||
|
InterstitialAutoSessions.Remove(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterSplashAutoSession(AutoSplashSession session)
|
||||||
|
{
|
||||||
|
if (session == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (SplashAutoSessionLock)
|
||||||
|
{
|
||||||
|
SplashAutoSessions[session.SessionId] = session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UnregisterSplashAutoSession(string sessionId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(sessionId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (SplashAutoSessionLock)
|
||||||
|
{
|
||||||
|
SplashAutoSessions.Remove(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class AutoRewardVideoSession : IDirichletRewardAdInteractionListener
|
private sealed class AutoRewardVideoSession : IDirichletRewardAdInteractionListener
|
||||||
{
|
{
|
||||||
public string SessionId { get; }
|
public string SessionId { get; }
|
||||||
@@ -684,6 +809,11 @@ namespace Dirichlet.Mediation
|
|||||||
listener?.OnRewardVerify(args);
|
listener?.OnRewardVerify(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnShowFailed(DirichletError error)
|
||||||
|
{
|
||||||
|
FailAndDispose(error ?? new DirichletError("show_error", "Reward video ad failed to show"));
|
||||||
|
}
|
||||||
|
|
||||||
public void FailAndDispose(DirichletError error)
|
public void FailAndDispose(DirichletError error)
|
||||||
{
|
{
|
||||||
if (disposed)
|
if (disposed)
|
||||||
@@ -707,6 +837,8 @@ namespace Dirichlet.Mediation
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
ad.ShowFailed -= OnShowFailed;
|
||||||
|
ad.SetInteractionListener(null);
|
||||||
ad?.Destroy();
|
ad?.Destroy();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -715,6 +847,176 @@ namespace Dirichlet.Mediation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class AutoInterstitialSession : IDirichletInterstitialAdInteractionListener
|
||||||
|
{
|
||||||
|
public string SessionId { get; }
|
||||||
|
|
||||||
|
private readonly DirichletInterstitialAd ad;
|
||||||
|
private readonly IDirichletInterstitialAutoAdListener listener;
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
|
public AutoInterstitialSession(string sessionId, DirichletInterstitialAd ad, IDirichletInterstitialAutoAdListener listener)
|
||||||
|
{
|
||||||
|
SessionId = string.IsNullOrEmpty(sessionId) ? Guid.NewGuid().ToString("N") : sessionId;
|
||||||
|
this.ad = ad ?? throw new ArgumentNullException(nameof(ad));
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAdShow()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener?.OnAdShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAdClick()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener?.OnAdClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAdClose()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener?.OnAdClose();
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnShowFailed(DirichletError error)
|
||||||
|
{
|
||||||
|
FailAndDispose(error ?? new DirichletError("show_error", "Interstitial ad failed to show"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FailAndDispose(DirichletError error)
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener?.OnError(error ?? new DirichletError("unknown_error", "Unknown error"));
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
disposed = true;
|
||||||
|
UnregisterInterstitialAutoSession(SessionId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ad.ShowFailed -= OnShowFailed;
|
||||||
|
ad.SetInteractionListener(null);
|
||||||
|
ad?.Destroy();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[Dirichlet] Auto interstitial ad Destroy failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class AutoSplashSession : IDirichletSplashAdInteractionListener
|
||||||
|
{
|
||||||
|
public string SessionId { get; }
|
||||||
|
|
||||||
|
private readonly DirichletSplashAd ad;
|
||||||
|
private readonly IDirichletSplashAutoAdListener listener;
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
|
public AutoSplashSession(string sessionId, DirichletSplashAd ad, IDirichletSplashAutoAdListener listener)
|
||||||
|
{
|
||||||
|
SessionId = string.IsNullOrEmpty(sessionId) ? Guid.NewGuid().ToString("N") : sessionId;
|
||||||
|
this.ad = ad ?? throw new ArgumentNullException(nameof(ad));
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAdShow()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener?.OnAdShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAdClick()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener?.OnAdClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAdClose()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener?.OnAdClose();
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnShowFailed(DirichletError error)
|
||||||
|
{
|
||||||
|
FailAndDispose(error ?? new DirichletError("show_error", "Splash ad failed to show"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FailAndDispose(DirichletError error)
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener?.OnError(error ?? new DirichletError("unknown_error", "Unknown error"));
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
disposed = true;
|
||||||
|
UnregisterSplashAutoSession(SessionId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ad.ShowFailed -= OnShowFailed;
|
||||||
|
ad.SetInteractionListener(null);
|
||||||
|
ad?.Destroy();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[Dirichlet] Auto splash ad Destroy failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1189,7 +1491,7 @@ namespace Dirichlet.Mediation
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Listener interface for auto interstitial ad callbacks.
|
/// Listener interface for auto interstitial ad callbacks.
|
||||||
/// Android only - iOS will receive OnError with not_supported error.
|
/// Android uses native auto cache; iOS/editor can emulate load-then-show.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IDirichletInterstitialAutoAdListener
|
public interface IDirichletInterstitialAutoAdListener
|
||||||
{
|
{
|
||||||
@@ -1201,7 +1503,7 @@ namespace Dirichlet.Mediation
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Listener interface for auto banner ad callbacks.
|
/// Listener interface for auto banner ad callbacks.
|
||||||
/// Android only - iOS will receive OnError with not_supported error.
|
/// Native auto rotation is Android only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IDirichletBannerAutoAdListener
|
public interface IDirichletBannerAutoAdListener
|
||||||
{
|
{
|
||||||
@@ -1213,7 +1515,7 @@ namespace Dirichlet.Mediation
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Listener interface for auto splash ad callbacks.
|
/// Listener interface for auto splash ad callbacks.
|
||||||
/// Android only - iOS will receive OnError with not_supported error.
|
/// Android uses native auto cache; iOS/editor can emulate load-then-show.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IDirichletSplashAutoAdListener
|
public interface IDirichletSplashAutoAdListener
|
||||||
{
|
{
|
||||||
@@ -1226,7 +1528,7 @@ namespace Dirichlet.Mediation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Listener interface for auto reward video ad callbacks.
|
/// Listener interface for auto reward video ad callbacks.
|
||||||
/// Used with ShowRewardVideoAutoAd which combines load and show into one operation.
|
/// Used with ShowRewardVideoAutoAd which combines load and show into one operation.
|
||||||
/// Android only - iOS will receive OnError with not_supported error.
|
/// Android uses native auto cache; iOS/editor can emulate load-then-show.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IDirichletRewardVideoAutoAdListener
|
public interface IDirichletRewardVideoAutoAdListener
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -580,12 +580,12 @@ namespace Dirichlet.Mediation
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows a reward video ad with automatic load-and-show logic.
|
/// Shows a reward video ad with automatic load-and-show logic.
|
||||||
/// Android only - iOS will call onFailure with not_supported error.
|
/// Android bridge maps to native auto cache; higher layers may emulate load-then-show elsewhere.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void ShowRewardVideoAutoAd(DirichletAdRequest request, IDirichletRewardVideoAutoAdListener listener);
|
void ShowRewardVideoAutoAd(DirichletAdRequest request, IDirichletRewardVideoAutoAdListener listener);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows an interstitial ad with automatic load-and-show logic. Android only.
|
/// Shows an interstitial ad with automatic load-and-show logic. Android bridge maps to native auto cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener);
|
void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener);
|
||||||
|
|
||||||
@@ -595,7 +595,7 @@ namespace Dirichlet.Mediation
|
|||||||
void ShowBannerAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletBannerAutoAdListener listener);
|
void ShowBannerAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletBannerAutoAdListener listener);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows a splash ad with automatic load logic. Android only.
|
/// Shows a splash ad with automatic load logic. Android bridge maps to native auto cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void ShowSplashAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener);
|
void ShowSplashAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener);
|
||||||
|
|
||||||
@@ -1563,20 +1563,9 @@ namespace Dirichlet.Mediation
|
|||||||
}
|
}
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
jsonBuilder.Append($"\"{kv.Key}\":");
|
AppendJsonString(jsonBuilder, kv.Key);
|
||||||
|
jsonBuilder.Append(":");
|
||||||
if (kv.Value is string)
|
AppendJsonValue(jsonBuilder, kv.Value);
|
||||||
{
|
|
||||||
jsonBuilder.Append($"\"{kv.Value}\"");
|
|
||||||
}
|
|
||||||
else if (kv.Value is bool)
|
|
||||||
{
|
|
||||||
jsonBuilder.Append(((bool)kv.Value) ? "true" : "false");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
jsonBuilder.Append(kv.Value.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonBuilder.Append("}");
|
jsonBuilder.Append("}");
|
||||||
@@ -1589,6 +1578,84 @@ namespace Dirichlet.Mediation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AppendJsonValue(System.Text.StringBuilder builder, object value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
builder.Append("null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is string stringValue)
|
||||||
|
{
|
||||||
|
AppendJsonString(builder, stringValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is bool boolValue)
|
||||||
|
{
|
||||||
|
builder.Append(boolValue ? "true" : "false");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is IFormattable formattable)
|
||||||
|
{
|
||||||
|
builder.Append(formattable.ToString(null, CultureInfo.InvariantCulture));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendJsonString(builder, value.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendJsonString(System.Text.StringBuilder builder, string value)
|
||||||
|
{
|
||||||
|
builder.Append('"');
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
foreach (var c in value)
|
||||||
|
{
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '\\':
|
||||||
|
builder.Append("\\\\");
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
builder.Append("\\\"");
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
builder.Append("\\b");
|
||||||
|
break;
|
||||||
|
case '\f':
|
||||||
|
builder.Append("\\f");
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
builder.Append("\\n");
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
builder.Append("\\r");
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
builder.Append("\\t");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (char.IsControl(c))
|
||||||
|
{
|
||||||
|
builder.Append("\\u");
|
||||||
|
builder.Append(((int)c).ToString("x4", CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.Append(c);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append('"');
|
||||||
|
}
|
||||||
|
|
||||||
private void RemoveLoadCallback(string handleId)
|
private void RemoveLoadCallback(string handleId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(handleId))
|
if (string.IsNullOrEmpty(handleId))
|
||||||
@@ -2032,5 +2099,3 @@ namespace Dirichlet.Mediation
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#import "DirichletMediationUnityBridge.h"
|
#import "DirichletMediationUnityBridge.h"
|
||||||
#import <DirichletMediationSDK/DirichletMediationSDK.h>
|
#import <DirichletMediationSDK/DirichletMediationSDK.h>
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <AppTrackingTransparency/AppTrackingTransparency.h>
|
||||||
|
|
||||||
// Unity callback interface
|
// Unity callback interface
|
||||||
extern "C" void UnitySendMessage(const char* obj, const char* method, const char* msg);
|
extern "C" void UnitySendMessage(const char* obj, const char* method, const char* msg);
|
||||||
@@ -119,6 +120,88 @@ static NSDictionary* ParseExtras(const char* extrasJson) {
|
|||||||
return error ? nil : dict;
|
return error ? nil : dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void SendLoadErrorToUnity(NSString* handleId, NSString* adType, NSError* error, NSString* fallbackMessage) {
|
||||||
|
NSInteger code = error ? error.code : -1;
|
||||||
|
NSString* message = error.localizedDescription ?: fallbackMessage ?: @"Unknown error";
|
||||||
|
NSDictionary* errorData = @{
|
||||||
|
@"code": @(code),
|
||||||
|
@"message": message
|
||||||
|
};
|
||||||
|
SendLoadCallbackToUnity(handleId, @"load_error", adType, errorData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SendShowErrorToUnity(NSString* handleId, NSString* adType, NSInteger code, NSString* message) {
|
||||||
|
NSDictionary* errorData = @{
|
||||||
|
@"code": @(code),
|
||||||
|
@"message": message ?: @"Ad failed to show"
|
||||||
|
};
|
||||||
|
SendEventToUnity(handleId, @"show_error", adType ?: @"unknown", errorData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static UIViewController* TopViewController(UIViewController* rootViewController) {
|
||||||
|
UIViewController* top = rootViewController;
|
||||||
|
while (top.presentedViewController) {
|
||||||
|
top = top.presentedViewController;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([top isKindOfClass:[UINavigationController class]]) {
|
||||||
|
return TopViewController(((UINavigationController*)top).visibleViewController);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([top isKindOfClass:[UITabBarController class]]) {
|
||||||
|
return TopViewController(((UITabBarController*)top).selectedViewController);
|
||||||
|
}
|
||||||
|
|
||||||
|
return top;
|
||||||
|
}
|
||||||
|
|
||||||
|
static UIViewController* CurrentRootViewController(void) {
|
||||||
|
UIWindow* keyWindow = nil;
|
||||||
|
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
for (UIScene* scene in [UIApplication sharedApplication].connectedScenes) {
|
||||||
|
if (scene.activationState != UISceneActivationStateForegroundActive ||
|
||||||
|
![scene isKindOfClass:[UIWindowScene class]]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIWindowScene* windowScene = (UIWindowScene*)scene;
|
||||||
|
for (UIWindow* window in windowScene.windows) {
|
||||||
|
if (window.isKeyWindow) {
|
||||||
|
keyWindow = window;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyWindow) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keyWindow) {
|
||||||
|
keyWindow = [UIApplication sharedApplication].keyWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TopViewController(keyWindow.rootViewController);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSString* AdTypeForObject(id ad) {
|
||||||
|
if ([ad isKindOfClass:[DRMRewardVideoAd class]]) {
|
||||||
|
return @"reward_video";
|
||||||
|
}
|
||||||
|
if ([ad isKindOfClass:[DRMInterstitialAd class]]) {
|
||||||
|
return @"interstitial";
|
||||||
|
}
|
||||||
|
if ([ad isKindOfClass:[DRMBannerAd class]]) {
|
||||||
|
return @"banner";
|
||||||
|
}
|
||||||
|
if ([ad isKindOfClass:[DRMSplashAd class]]) {
|
||||||
|
return @"splash";
|
||||||
|
}
|
||||||
|
return @"unknown";
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Ad Instance Manager
|
#pragma mark - Ad Instance Manager
|
||||||
|
|
||||||
@interface DirichletMediationInstanceManager : NSObject
|
@interface DirichletMediationInstanceManager : NSObject
|
||||||
@@ -336,6 +419,7 @@ bool DirichletMediationUnityBridge_Initialize(
|
|||||||
// Check if SDK is already initialized
|
// Check if SDK is already initialized
|
||||||
if ([DirichletMediation isInitialized]) {
|
if ([DirichletMediation isInitialized]) {
|
||||||
NSLog(@"[DirichletMediationUnityBridge] SDK already initialized");
|
NSLog(@"[DirichletMediationUnityBridge] SDK already initialized");
|
||||||
|
SendInitCallbackToUnity(YES, nil, @"already_initialized");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,8 +472,29 @@ bool DirichletMediationUnityBridge_Initialize(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DirichletMediationUnityBridge_RequestPermissionIfNeeded(void) {
|
void DirichletMediationUnityBridge_RequestPermissionIfNeeded(void) {
|
||||||
// iOS 14+ ATT permission is handled internally by the SDK
|
|
||||||
NSLog(@"[DirichletMediationUnityBridge] RequestPermissionIfNeeded called");
|
NSLog(@"[DirichletMediationUnityBridge] RequestPermissionIfNeeded called");
|
||||||
|
|
||||||
|
if (@available(iOS 14.0, *)) {
|
||||||
|
void (^requestBlock)(void) = ^{
|
||||||
|
ATTrackingManagerAuthorizationStatus status = [ATTrackingManager trackingAuthorizationStatus];
|
||||||
|
if (status != ATTrackingManagerAuthorizationStatusNotDetermined) {
|
||||||
|
NSLog(@"[DirichletMediationUnityBridge] ATT status already determined: %lu", (unsigned long)status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
|
||||||
|
NSLog(@"[DirichletMediationUnityBridge] ATT request completed with status: %lu", (unsigned long)status);
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
if ([NSThread isMainThread]) {
|
||||||
|
requestBlock();
|
||||||
|
} else {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), requestBlock);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NSLog(@"[DirichletMediationUnityBridge] ATT is not required below iOS 14");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* DirichletMediationUnityBridge_GetSdkVersion(void) {
|
const char* DirichletMediationUnityBridge_GetSdkVersion(void) {
|
||||||
@@ -438,11 +543,7 @@ const char* DirichletMediationUnityBridge_LoadRewardVideoAd(long long spaceId, c
|
|||||||
SendLoadCallbackToUnity(handleId, @"load_success", @"reward_video", nil);
|
SendLoadCallbackToUnity(handleId, @"load_success", @"reward_video", nil);
|
||||||
} else {
|
} else {
|
||||||
NSLog(@"[DirichletMediationUnityBridge] RewardVideoAd load failed: %@", error.localizedDescription);
|
NSLog(@"[DirichletMediationUnityBridge] RewardVideoAd load failed: %@", error.localizedDescription);
|
||||||
NSDictionary* errorData = @{
|
SendLoadErrorToUnity(handleId, @"reward_video", error, @"RewardVideoAd load failed");
|
||||||
@"code": @(error.code),
|
|
||||||
@"message": error.localizedDescription ?: @"Unknown error"
|
|
||||||
};
|
|
||||||
SendLoadCallbackToUnity(handleId, @"load_error", @"reward_video", errorData);
|
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -475,11 +576,7 @@ const char* DirichletMediationUnityBridge_LoadInterstitialAd(long long spaceId,
|
|||||||
SendLoadCallbackToUnity(handleId, @"load_success", @"interstitial", nil);
|
SendLoadCallbackToUnity(handleId, @"load_success", @"interstitial", nil);
|
||||||
} else {
|
} else {
|
||||||
NSLog(@"[DirichletMediationUnityBridge] InterstitialAd load failed: %@", error.localizedDescription);
|
NSLog(@"[DirichletMediationUnityBridge] InterstitialAd load failed: %@", error.localizedDescription);
|
||||||
NSDictionary* errorData = @{
|
SendLoadErrorToUnity(handleId, @"interstitial", error, @"InterstitialAd load failed");
|
||||||
@"code": @(error.code),
|
|
||||||
@"message": error.localizedDescription ?: @"Unknown error"
|
|
||||||
};
|
|
||||||
SendLoadCallbackToUnity(handleId, @"load_error", @"interstitial", errorData);
|
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -520,11 +617,7 @@ const char* DirichletMediationUnityBridge_LoadBannerAd(long long spaceId, const
|
|||||||
SendLoadCallbackToUnity(handleId, @"load_success", @"banner", nil);
|
SendLoadCallbackToUnity(handleId, @"load_success", @"banner", nil);
|
||||||
} else {
|
} else {
|
||||||
NSLog(@"[DirichletMediationUnityBridge] BannerAd load failed: %@", error.localizedDescription);
|
NSLog(@"[DirichletMediationUnityBridge] BannerAd load failed: %@", error.localizedDescription);
|
||||||
NSDictionary* errorData = @{
|
SendLoadErrorToUnity(handleId, @"banner", error, @"BannerAd load failed");
|
||||||
@"code": @(error.code),
|
|
||||||
@"message": error.localizedDescription ?: @"Unknown error"
|
|
||||||
};
|
|
||||||
SendLoadCallbackToUnity(handleId, @"load_error", @"banner", errorData);
|
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -565,11 +658,7 @@ const char* DirichletMediationUnityBridge_LoadSplashAd(long long spaceId, const
|
|||||||
SendLoadCallbackToUnity(handleId, @"load_success", @"splash", nil);
|
SendLoadCallbackToUnity(handleId, @"load_success", @"splash", nil);
|
||||||
} else {
|
} else {
|
||||||
NSLog(@"[DirichletMediationUnityBridge] SplashAd load failed: %@", error.localizedDescription);
|
NSLog(@"[DirichletMediationUnityBridge] SplashAd load failed: %@", error.localizedDescription);
|
||||||
NSDictionary* errorData = @{
|
SendLoadErrorToUnity(handleId, @"splash", error, @"SplashAd load failed");
|
||||||
@"code": @(error.code),
|
|
||||||
@"message": error.localizedDescription ?: @"Unknown error"
|
|
||||||
};
|
|
||||||
SendLoadCallbackToUnity(handleId, @"load_error", @"splash", errorData);
|
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -584,12 +673,16 @@ bool DirichletMediationUnityBridge_ShowAd(const char* handleId, const char* extr
|
|||||||
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Ad not found for handle %@", nsHandleId);
|
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Ad not found for handle %@", nsHandleId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure show is always called on main thread (aligned with Ad Unity implementation)
|
__block BOOL didStartShow = NO;
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
NSString* adType = AdTypeForObject(ad);
|
||||||
UIViewController* rootVC = [[[UIApplication sharedApplication] keyWindow] rootViewController];
|
NSDictionary* extrasDict = ParseExtras(extras);
|
||||||
|
|
||||||
|
void (^showBlock)(void) = ^{
|
||||||
|
UIViewController* rootVC = CurrentRootViewController();
|
||||||
if (!rootVC) {
|
if (!rootVC) {
|
||||||
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Root view controller not found");
|
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Root view controller not found");
|
||||||
|
SendShowErrorToUnity(nsHandleId, adType, -2, @"Root view controller not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,52 +690,86 @@ bool DirichletMediationUnityBridge_ShowAd(const char* handleId, const char* extr
|
|||||||
DRMRewardVideoAd* rewardAd = (DRMRewardVideoAd*)ad;
|
DRMRewardVideoAd* rewardAd = (DRMRewardVideoAd*)ad;
|
||||||
if ([rewardAd isReady]) {
|
if ([rewardAd isReady]) {
|
||||||
[rewardAd showFromViewController:rootVC];
|
[rewardAd showFromViewController:rootVC];
|
||||||
|
didStartShow = YES;
|
||||||
NSLog(@"[DirichletMediationUnityBridge] Showing reward video ad: %@", nsHandleId);
|
NSLog(@"[DirichletMediationUnityBridge] Showing reward video ad: %@", nsHandleId);
|
||||||
|
} else {
|
||||||
|
SendShowErrorToUnity(nsHandleId, @"reward_video", -3, @"Reward video ad is not ready");
|
||||||
}
|
}
|
||||||
} else if ([ad isKindOfClass:[DRMInterstitialAd class]]) {
|
} else if ([ad isKindOfClass:[DRMInterstitialAd class]]) {
|
||||||
DRMInterstitialAd* interstitialAd = (DRMInterstitialAd*)ad;
|
DRMInterstitialAd* interstitialAd = (DRMInterstitialAd*)ad;
|
||||||
if ([interstitialAd isReady]) {
|
if ([interstitialAd isReady]) {
|
||||||
[interstitialAd showFromViewController:rootVC];
|
[interstitialAd showFromViewController:rootVC];
|
||||||
|
didStartShow = YES;
|
||||||
NSLog(@"[DirichletMediationUnityBridge] Showing interstitial ad: %@", nsHandleId);
|
NSLog(@"[DirichletMediationUnityBridge] Showing interstitial ad: %@", nsHandleId);
|
||||||
|
} else {
|
||||||
|
SendShowErrorToUnity(nsHandleId, @"interstitial", -3, @"Interstitial ad is not ready");
|
||||||
}
|
}
|
||||||
} else if ([ad isKindOfClass:[DRMBannerAd class]]) {
|
} else if ([ad isKindOfClass:[DRMBannerAd class]]) {
|
||||||
DRMBannerAd* bannerAd = (DRMBannerAd*)ad;
|
DRMBannerAd* bannerAd = (DRMBannerAd*)ad;
|
||||||
UIView* bannerView = bannerAd.view;
|
UIView* bannerView = bannerAd.view;
|
||||||
if (bannerView) {
|
if (bannerView) {
|
||||||
// Banner 广告需要将 view 添加到视图控制器上
|
[bannerView removeFromSuperview];
|
||||||
// 注意:Unity 侧需要通过 Unity UI 系统来处理 Banner 视图
|
|
||||||
// 这里我们发送一个事件通知 Unity 侧,让 Unity 侧来处理视图的展示
|
|
||||||
// 或者直接将视图添加到根视图控制器上(临时方案)
|
|
||||||
[rootVC.view addSubview:bannerView];
|
[rootVC.view addSubview:bannerView];
|
||||||
bannerView.translatesAutoresizingMaskIntoConstraints = NO;
|
bannerView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||||
// 设置约束,让 Banner 显示在底部
|
|
||||||
|
NSInteger baseline = [extrasDict[@"banner_baseline"] integerValue];
|
||||||
|
CGFloat offset = extrasDict[@"banner_offset"] ? [extrasDict[@"banner_offset"] floatValue] : 0;
|
||||||
|
NSLayoutYAxisAnchor* verticalAnchor = baseline == 0
|
||||||
|
? rootVC.view.safeAreaLayoutGuide.topAnchor
|
||||||
|
: rootVC.view.safeAreaLayoutGuide.bottomAnchor;
|
||||||
|
NSLayoutConstraint* verticalConstraint = baseline == 0
|
||||||
|
? [bannerView.topAnchor constraintEqualToAnchor:verticalAnchor constant:offset]
|
||||||
|
: [bannerView.bottomAnchor constraintEqualToAnchor:verticalAnchor constant:-offset];
|
||||||
|
|
||||||
[NSLayoutConstraint activateConstraints:@[
|
[NSLayoutConstraint activateConstraints:@[
|
||||||
[bannerView.leadingAnchor constraintEqualToAnchor:rootVC.view.leadingAnchor],
|
[bannerView.centerXAnchor constraintEqualToAnchor:rootVC.view.centerXAnchor],
|
||||||
[bannerView.trailingAnchor constraintEqualToAnchor:rootVC.view.trailingAnchor],
|
[bannerView.widthAnchor constraintLessThanOrEqualToAnchor:rootVC.view.widthAnchor],
|
||||||
[bannerView.bottomAnchor constraintEqualToAnchor:rootVC.view.safeAreaLayoutGuide.bottomAnchor],
|
verticalConstraint,
|
||||||
[bannerView.heightAnchor constraintEqualToConstant:bannerAd.size.height > 0 ? bannerAd.size.height : 50]
|
[bannerView.heightAnchor constraintEqualToConstant:bannerAd.size.height > 0 ? bannerAd.size.height : 50]
|
||||||
]];
|
]];
|
||||||
|
didStartShow = YES;
|
||||||
NSLog(@"[DirichletMediationUnityBridge] Showing banner ad: %@", nsHandleId);
|
NSLog(@"[DirichletMediationUnityBridge] Showing banner ad: %@", nsHandleId);
|
||||||
} else {
|
} else {
|
||||||
NSLog(@"[DirichletMediationUnityBridge] Banner ad view not available: %@", nsHandleId);
|
NSLog(@"[DirichletMediationUnityBridge] Banner ad view not available: %@", nsHandleId);
|
||||||
|
SendShowErrorToUnity(nsHandleId, @"banner", -4, @"Banner ad view not available");
|
||||||
}
|
}
|
||||||
} else if ([ad isKindOfClass:[DRMSplashAd class]]) {
|
} else if ([ad isKindOfClass:[DRMSplashAd class]]) {
|
||||||
DRMSplashAd* splashAd = (DRMSplashAd*)ad;
|
DRMSplashAd* splashAd = (DRMSplashAd*)ad;
|
||||||
if ([splashAd isReady]) {
|
if ([splashAd isReady]) {
|
||||||
[splashAd showFromViewController:rootVC];
|
[splashAd showFromViewController:rootVC];
|
||||||
|
didStartShow = YES;
|
||||||
NSLog(@"[DirichletMediationUnityBridge] Showing splash ad: %@", nsHandleId);
|
NSLog(@"[DirichletMediationUnityBridge] Showing splash ad: %@", nsHandleId);
|
||||||
|
} else {
|
||||||
|
SendShowErrorToUnity(nsHandleId, @"splash", -3, @"Splash ad is not ready");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Ad not ready or unknown type");
|
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Ad not ready or unknown type");
|
||||||
|
SendShowErrorToUnity(nsHandleId, @"unknown", -5, @"Unknown ad type");
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if ([NSThread isMainThread]) {
|
||||||
|
showBlock();
|
||||||
|
} else {
|
||||||
|
dispatch_sync(dispatch_get_main_queue(), showBlock);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return didStartShow;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DirichletMediationUnityBridge_DestroyAd(const char* handleId) {
|
void DirichletMediationUnityBridge_DestroyAd(const char* handleId) {
|
||||||
NSString* nsHandleId = CreateNSString(handleId);
|
NSString* nsHandleId = CreateNSString(handleId);
|
||||||
NSLog(@"[DirichletMediationUnityBridge] Destroying ad: %@", nsHandleId);
|
NSLog(@"[DirichletMediationUnityBridge] Destroying ad: %@", nsHandleId);
|
||||||
|
|
||||||
|
id ad = [[DirichletMediationInstanceManager shared] adForHandle:nsHandleId];
|
||||||
|
if ([ad isKindOfClass:[DRMBannerAd class]]) {
|
||||||
|
DRMBannerAd* bannerAd = (DRMBannerAd*)ad;
|
||||||
|
if (bannerAd.view.superview) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[bannerAd.view removeFromSuperview];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove ad instance
|
// Remove ad instance
|
||||||
[[DirichletMediationInstanceManager shared] removeAdForHandle:nsHandleId];
|
[[DirichletMediationInstanceManager shared] removeAdForHandle:nsHandleId];
|
||||||
@@ -679,4 +806,3 @@ bool DirichletMediationUnityBridge_IsAdValid(const char* handleId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,10 @@
|
|||||||
|
|
||||||
* `Assets/DirichletMediation`: 官方聚合 Unity SDK `4.2.5.0`,已删除官方 Sample。
|
* `Assets/DirichletMediation`: 官方聚合 Unity SDK `4.2.5.0`,已删除官方 Sample。
|
||||||
* `Assets/Plugins/Android`: 官方 Android AAR、Manifest、ProGuard、本地微信 OpenSDK AAR。
|
* `Assets/Plugins/Android`: 官方 Android AAR、Manifest、ProGuard、本地微信 OpenSDK AAR。
|
||||||
* `Assets/Plugins/iOS`: 官方 iOS bridge。
|
* `Assets/Plugins/iOS`: iOS Objective-C++ bridge。
|
||||||
* `Assets/Tapadn_Adapter/Runtime/Scripts`: 商业化抽象层适配。
|
* `Assets/Tapadn_Adapter/Runtime/Scripts`: 商业化抽象层适配。
|
||||||
* `Assets/Tapadn_Adapter/Editor`: Android 构建后处理和依赖声明。
|
* `Assets/Tapadn_Adapter/Editor`: Android 构建后处理和依赖声明。
|
||||||
|
* `Assets/DirichletMediation/Editor`: Android Gradle 后处理与 iOS Xcode/CocoaPods 后处理。
|
||||||
* `Assets/Samples~`: 可选调试样例预留,不随主包自动进入业务项目。
|
* `Assets/Samples~`: 可选调试样例预留,不随主包自动进入业务项目。
|
||||||
|
|
||||||
## Runtime 设计
|
## Runtime 设计
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
|
|
||||||
不确定点:
|
不确定点:
|
||||||
|
|
||||||
* 官方文档说明 auto-ad 目前主要是 Android 能力,iOS auto-ad 会返回 `not_supported`。本模块保留手动 fallback,但真正 iOS 出包前需要用 TapADN iOS 账号和广告位做真机验证。
|
* 官方文档说明 auto-ad 目前主要是 Android 能力。本模块在 iOS 对激励、插屏、开屏 auto API 做 load-then-show 兼容 fallback,但不承诺 Android native auto 缓存语义;真正 iOS 出包前需要用 TapADN iOS 账号和广告位做真机验证。
|
||||||
* `AD_Type` 抽象层没有 Banner 类型,所以本轮没有将 TapADN Banner 暴露到 `ADManager`。如果抽象层后续新增 Banner,可以直接复用官方 `ShowBannerAutoAd`。
|
* `AD_Type` 抽象层没有 Banner 类型,所以本轮没有将 TapADN Banner 暴露到 `ADManager`。如果抽象层后续新增 Banner,可以直接复用官方 `ShowBannerAutoAd`。
|
||||||
|
|
||||||
## 配置设计
|
## 配置设计
|
||||||
@@ -82,6 +83,22 @@
|
|||||||
* 方案 B:只放本地微信 AAR。风险是宿主 EDM4U 依赖图不可见。
|
* 方案 B:只放本地微信 AAR。风险是宿主 EDM4U 依赖图不可见。
|
||||||
* 当前选择:本地 AAR + `WXDependencies.xml` 同时保留。这样最接近 TopOn 当前工程,也能覆盖无 EDM4U 的构建场景。
|
* 当前选择:本地 AAR + `WXDependencies.xml` 同时保留。这样最接近 TopOn 当前工程,也能覆盖无 EDM4U 的构建场景。
|
||||||
|
|
||||||
|
## iOS 构建设计
|
||||||
|
|
||||||
|
保留 iOS Objective-C++ bridge,并通过 `DirichletMediationIOSPostProcessor` 自动生成 Xcode 集成:
|
||||||
|
|
||||||
|
* Pod 默认版本为官方 iOS 聚合 SDK `4.2.0.1`,可用 `DIRICHLET_IOS_SDK_VERSION` 或 `EditorPrefs("Dirichlet.iOS.SDKVersion")` 覆盖。
|
||||||
|
* 所有 Dirichlet iOS Pods 放到 Unity Framework target,保持 bridge、SDK、adapter 在同一二进制上下文。
|
||||||
|
* 自动补 `SKAdNetworkItems`、`NSUserTrackingUsageDescription`、`AppTrackingTransparency.framework`、`AdSupport.framework`。
|
||||||
|
* 构建后执行 `pod install`,缺失 CocoaPods 或 Pod 源异常时让 Unity 构建失败,避免产出半配置 Xcode 工程。
|
||||||
|
* GDT 动态 framework 在 `pod install` 后嵌入 App target。
|
||||||
|
|
||||||
|
iOS runtime 桥接负责:
|
||||||
|
|
||||||
|
* 初始化时传入 `MediaId`、`MediaKey`、`MediaName`、`gameChannel`、`shakeEnabled`、`allowIDFAAccess`、`aTags`。
|
||||||
|
* `RequestPermissionIfNecessary()` 在 iOS 14+ 请求 ATT。
|
||||||
|
* 加载/展示激励、插屏、开屏;展示前检查 root view controller 和 `isReady`,失败时回传 `show_error`。
|
||||||
|
|
||||||
## 编辑器可见性
|
## 编辑器可见性
|
||||||
|
|
||||||
本模块不提供默认可见面板。构建自动化通过 `IPostGenerateGradleAndroidProject` 静默执行;调试能力放入 `Samples~`,由业务项目显式导入。
|
本模块不提供默认可见面板。构建自动化通过 `IPostGenerateGradleAndroidProject` 静默执行;调试能力放入 `Samples~`,由业务项目显式导入。
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -147,6 +147,24 @@ ADManager.Instance.Init(callback, userId, adConfig, new TapadnAdController());
|
|||||||
|
|
||||||
包内不默认暴露可视化编辑面板;调试样例通过 `Samples~` 作为可选导入内容。
|
包内不默认暴露可视化编辑面板;调试样例通过 `Samples~` 作为可选导入内容。
|
||||||
|
|
||||||
|
## iOS 构建
|
||||||
|
|
||||||
|
iOS 侧通过 `DirichletMediationIOSPostProcessor` 在 Unity 导出 Xcode 工程后自动处理:
|
||||||
|
|
||||||
|
* 生成 `Podfile`,默认接入 `DirichletMediationSDK`、`DirichletMediationAdapterDRA`、`DirichletMediationAdapterCSJ`、`DirichletMediationAdapterGDT` 的 iOS `4.2.0.1` Pod。
|
||||||
|
* 将 Pods 放到 Unity Framework target,避免 adapter 被 strip 后运行时找不到类。
|
||||||
|
* 补齐 `SKAdNetworkItems`、`NSUserTrackingUsageDescription`、`AppTrackingTransparency.framework`、`AdSupport.framework`。
|
||||||
|
* 执行 `pod install`;若构建机没有 CocoaPods,会在日志里给出手动执行路径。
|
||||||
|
* 将 GDT 的动态 framework 嵌入 App target。
|
||||||
|
|
||||||
|
可选覆盖:
|
||||||
|
|
||||||
|
* `DIRICHLET_IOS_SDK_VERSION` 或 `EditorPrefs("Dirichlet.iOS.SDKVersion")`:临时切换 iOS Pod 版本。
|
||||||
|
* `DIRICHLET_IOS_ATT_DESCRIPTION` 或 `EditorPrefs("Dirichlet.iOS.TrackingUsageDescription")`:替换 ATT 弹窗文案。
|
||||||
|
* `DIRICHLET_UNITY_FRAMEWORK_TARGET` / `DIRICHLET_UNITY_APP_TARGET`:极端自定义 Xcode target 名称时手动指定。
|
||||||
|
|
||||||
|
iOS 的 auto-ad 原生接口仍按官方口径视为 Android 能力;本模块在 iOS 上对激励、插屏、开屏做了“load 成功后立即 show”的兼容 fallback,不承诺 native 缓存语义。正式联调仍需要用 TapADN iOS 媒体账号和 iOS 广告位做真机验证,重点看初始化、ATT、三类广告 load/show/close/reward 回调,以及无填充/未 ready 的失败收口。
|
||||||
|
|
||||||
## 智能预加载敏感度验收(默认次留 35%)
|
## 智能预加载敏感度验收(默认次留 35%)
|
||||||
|
|
||||||
本模块包含一套本地仿真脚本,用于模拟 IAA 场景下不同 `PreloadThreshold` 与 `CooldownSeconds` 的收益差异,输出完整 CSV 与变化曲线。
|
本模块包含一套本地仿真脚本,用于模拟 IAA 场景下不同 `PreloadThreshold` 与 `CooldownSeconds` 的收益差异,输出完整 CSV 与变化曲线。
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ TapADN / Dirichlet Unity 文档对 auto 的描述主要是“加载和展示合
|
|||||||
本地 SDK 注释进一步约束了能力边界:
|
本地 SDK 注释进一步约束了能力边界:
|
||||||
|
|
||||||
* `PreLoad` 当前只有 type=3,即激励视频,会被 native SDK 处理。
|
* `PreLoad` 当前只有 type=3,即激励视频,会被 native SDK 处理。
|
||||||
* iOS auto-ad / preload 在当前 wrapper 中返回不支持或 noop。
|
* iOS 没有官方 native auto 缓存语义;当前模块会对激励、插屏、开屏 auto API 做 load-then-show 兼容 fallback,`PreLoad` 仍为 noop。
|
||||||
* Android bridge 中为 auto ad 保留了 singleton `DirichletAdNative`,用于保持 native 侧自动广告缓存。
|
* Android bridge 中为 auto ad 保留了 singleton `DirichletAdNative`,用于保持 native 侧自动广告缓存。
|
||||||
|
|
||||||
当前接入严格遵循了这些 TapADN API,但没有也不能补出 TopOn 那种场景入口,因为当前 SDK wrapper 没有暴露同类接口。`ADManager.EnterAdScenario(...)` 对 TapADN 玩家侧目前没有平台上报效果,只能作为项目自身策略或日志的入口继续扩展。
|
当前接入严格遵循了这些 TapADN API,但没有也不能补出 TopOn 那种场景入口,因为当前 SDK wrapper 没有暴露同类接口。`ADManager.EnterAdScenario(...)` 对 TapADN 玩家侧目前没有平台上报效果,只能作为项目自身策略或日志的入口继续扩展。
|
||||||
@@ -73,7 +73,7 @@ TapADN / Dirichlet Unity 文档对 auto 的描述主要是“加载和展示合
|
|||||||
* 开屏在中国安卓渠道更常见,但冷启动时长、首屏流失和合规压力都更敏感。若 auto show 触发后再加载,失败或超时都直接影响进主场景体验;生产上更适合手动加载、严格超时和无广告快速进入。
|
* 开屏在中国安卓渠道更常见,但冷启动时长、首屏流失和合规压力都更敏感。若 auto show 触发后再加载,失败或超时都直接影响进主场景体验;生产上更适合手动加载、严格超时和无广告快速进入。
|
||||||
* 顶级 IAA 调优更关注 placement / scenario 维度,而不是“这个类型是不是 auto”。同一个激励视频,复活、翻倍金币、免费抽奖的 eCPM、转化率和留存影响都可能不同,需要可分桶、可频控、可 A/B。
|
* 顶级 IAA 调优更关注 placement / scenario 维度,而不是“这个类型是不是 auto”。同一个激励视频,复活、翻倍金币、免费抽奖的 eCPM、转化率和留存影响都可能不同,需要可分桶、可频控、可 A/B。
|
||||||
|
|
||||||
所以,模块层应该把 auto 当作“TapADN Android 的一种展示通道”,而不是全局商业化策略本身。
|
所以,模块层应该把 auto 当作“TapADN Android 的一种展示通道”;iOS 兼容 fallback 只能保证回调链路完整,不应被当成同等的 native 缓存策略。
|
||||||
|
|
||||||
## 推荐默认配置
|
## 推荐默认配置
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ tapadn.splash_prewarm_on_init=false
|
|||||||
|
|
||||||
## 当前实现评价
|
## 当前实现评价
|
||||||
|
|
||||||
当前 TapADN auto 接入本身是合规且完整的 API 级接入,但默认策略偏激进。它适合样例工程、快速调试和 Android 单平台验证;对于正式 IAA 游戏商业化,建议把默认认知改成:
|
当前 TapADN auto 接入本身是合规且完整的 API 级接入,但默认策略偏激进。它适合样例工程和快速调试;对于 Android/iOS 同步上线的正式 IAA 游戏商业化,建议把默认认知改成:
|
||||||
|
|
||||||
* TapADN auto 负责“SDK 内部 load + show + 缓存复用”。
|
* TapADN auto 负责“SDK 内部 load + show + 缓存复用”。
|
||||||
* 项目商业化策略仍然负责“场景进入、ready gating、频控、冷却、奖励发放、A/B 分桶和兜底路径”。
|
* 项目商业化策略仍然负责“场景进入、ready gating、频控、冷却、奖励发放、A/B 分桶和兜底路径”。
|
||||||
|
|||||||
Reference in New Issue
Block a user