using System; using System.Collections.Generic; using Dirichlet.Mediation; using Runtime.ADAggregator; using UnityEngine; public sealed class TapadnAwardVideoPlayer : ADPlayer, IDirichletRewardVideoAutoAdListener { private const float RewardCloseSettleDelaySeconds = 0.25f; private readonly Dictionary _slotCaches = new Dictionary(StringComparer.Ordinal); private DirichletAdNative _adNative; private string _defaultSlotId; private string _activeSlotId; private bool _rewardVerified; private bool _rewardVerifyReceived; private bool _closePendingRewardVerify; private bool _showSettled; private AdTimeHandler _rewardCloseSettleHandler; public override int MaxLoadAttempts => TapadnAdController.CurrentOptions?.RewardedMaxLoadAttempts ?? base.MaxLoadAttempts; public override float LoadRetryDelaySeconds => Math.Max(0f, (TapadnAdController.CurrentOptions?.RewardedLoadRetryDelayMs ?? 500) / 1000f); public override float ShowPendingTimeoutSeconds => Math.Max(1f, (TapadnAdController.CurrentOptions?.RewardedShowTimeoutMs ?? 20000) / 1000f); public override bool AutoPreloadOnInit => TapadnAdController.CurrentOptions?.RewardedPrewarmOnInit ?? false; public override void OnInit() { _defaultSlotId = Key; _adNative = DirichletAdManager.CreateAdNative(); } public override bool IsReadly() { var slotId = ResolveCurrentSlotId(); if (UseAutoLoad()) { return TapadnAdRequestFactory.TryParseSlotId(slotId, out _); } var cache = GetCache(slotId); if (IsCacheReady(cache)) { curState = 2; return true; } if (cache != null && cache.Loading) { curState = 1; return false; } if (cache != null) { RemoveCache(slotId); } curState = 0; return false; } public override void LoadAD() { var slotId = ResolveCurrentSlotId(); if (!TapadnAdRequestFactory.TryParseSlotId(slotId, out _)) { Debug.LogError($"[TapADN] Invalid rewarded slot id: {slotId}"); curState = 0; return; } if (UseAutoLoad()) { curState = 2; try { _adNative.PreLoad(TapadnAdRequestFactory.BuildRewarded(slotId, TapadnAdController.CurrentOptions), 3); } catch (Exception exception) { Debug.LogWarning($"[TapADN] Rewarded preload skipped: {exception.Message}"); } return; } var cache = GetCache(slotId); if (cache != null && (cache.Loading || IsCacheReady(cache))) { curState = cache.Loading ? 1 : 2; return; } cache = new RewardedSlotCache { Loading = true }; _slotCaches[slotId] = cache; curState = 1; TapadnSmartLoadOrchestrator.OnLoadStarted(AD_Type.AwardVideo, AdScene, slotId); _adNative.LoadRewardVideoAd( TapadnAdRequestFactory.BuildRewarded(slotId, TapadnAdController.CurrentOptions), ad => { TapadnSmartLoadOrchestrator.OnLoadResult(AD_Type.AwardVideo, AdScene, true, slotId); DestroyCachedAd(cache); cache.Ad = ad; cache.Loading = false; cache.LoadedUnix = GetNowUnixSeconds(); RegisterManualEvents(slotId, cache.Ad); ScheduleCacheExpiration(slotId, cache); curState = string.Equals(slotId, ResolveCurrentSlotId(), StringComparison.Ordinal) ? 2 : curState; Debug.Log($"[TapADN] Rewarded loaded. scene={NormalizeScenario(AdScene)}, slot={slotId}"); }, error => { TapadnSmartLoadOrchestrator.OnLoadResult(AD_Type.AwardVideo, AdScene, false, slotId); RemoveCache(slotId); curState = string.Equals(slotId, ResolveCurrentSlotId(), StringComparison.Ordinal) ? 0 : curState; Debug.LogError($"[TapADN] Rewarded load failed. slot={slotId}, code={error.Code}, message={error.Message}"); }); } public override void ShowAD(Action onClose, Action onVideoComplete) { adListener.onClose = onClose; adListener.onVideoComplete = onVideoComplete; _rewardVerified = false; _rewardVerifyReceived = false; _closePendingRewardVerify = false; _showSettled = false; _rewardCloseSettleHandler?.Kill(); _rewardCloseSettleHandler = null; _activeSlotId = ResolveCurrentSlotId(); Key = _activeSlotId; curState = 0; if (UseAutoLoad()) { _adNative.ShowRewardVideoAutoAd(TapadnAdRequestFactory.BuildRewarded(_activeSlotId, TapadnAdController.CurrentOptions), this); return; } var cache = GetCache(_activeSlotId); if (!IsCacheReady(cache) || cache.Ad == null || !cache.Ad.Show()) { RemoveCache(_activeSlotId); OnError(new DirichletError("show_failed", "ShowRewardVideoAd returned false")); return; } } public void OnError(DirichletError error) { if (_showSettled) { return; } _showSettled = true; _rewardCloseSettleHandler?.Kill(); _rewardCloseSettleHandler = null; curState = 0; TapadnSmartLoadOrchestrator.OnShowError(AD_Type.AwardVideo, AdScene, _activeSlotId); Debug.LogError($"[TapADN] Rewarded show failed. slot={_activeSlotId}, code={error?.Code}, message={error?.Message}"); adListener.OnShowError(); } public void OnAdShow() { TapadnSmartLoadOrchestrator.OnShowStart(AD_Type.AwardVideo, AdScene, _activeSlotId); NotifyShowStarted(); } public void OnAdClose() { if (_showSettled) { return; } if (_rewardVerifyReceived) { CompleteRewardedClose(); return; } _closePendingRewardVerify = true; _rewardCloseSettleHandler?.Kill(); _rewardCloseSettleHandler = ADManager.Instance.CreateTimer(RewardCloseSettleDelaySeconds, CompleteRewardedClose); } public void OnRewardVerify(DirichletRewardVerificationEventArgs args) { _rewardVerifyReceived = true; _rewardVerified = args != null && args.IsVerified; if (_closePendingRewardVerify) { CompleteRewardedClose(); } } public void OnAdClick() { } public override void OnPlayRequestStarted() { var slotId = ResolveCurrentSlotId(); Key = slotId; TapadnSmartLoadOrchestrator.OnPlayRequestStarted(AD_Type.AwardVideo, AdScene, !UseAutoLoad() && IsReadly(), slotId); } public override void EnterAdScenario(string scenario) { AdScene = NormalizeScenario(scenario); var slotId = ResolveCurrentSlotId(); Key = slotId; TapadnSmartLoadOrchestrator.OnEnterAdScenario(AD_Type.AwardVideo, AdScene, slotId); } private string ResolveCurrentSlotId() { var slotId = TapadnAdController.CurrentOptions?.ResolveRewardedSlotId(_defaultSlotId, AdScene) ?? _defaultSlotId; return string.IsNullOrWhiteSpace(slotId) ? _defaultSlotId : slotId.Trim(); } private bool UseAutoLoad() { return TapadnAdController.CurrentOptions?.RewardedAutoLoad ?? true; } private RewardedSlotCache GetCache(string slotId) { return !string.IsNullOrWhiteSpace(slotId) && _slotCaches.TryGetValue(slotId, out var cache) ? cache : null; } private bool IsCacheReady(RewardedSlotCache cache) { if (cache == null || cache.Loading || cache.Ad == null || !cache.Ad.IsLoaded || !cache.Ad.IsValid) { return false; } var maxAgeSeconds = TapadnAdController.CurrentOptions?.RewardedCacheMaxAgeSeconds ?? 0; return maxAgeSeconds <= 0 || GetNowUnixSeconds() - cache.LoadedUnix <= maxAgeSeconds; } private void RegisterManualEvents(string slotId, DirichletRewardVideoAd ad) { if (ad == null) { return; } ad.Shown += () => OnManualShown(slotId); ad.Clicked += OnManualClicked; ad.ShowFailed += error => OnManualShowFailed(slotId, error); ad.RewardVerified += OnManualRewardVerify; ad.Closed += () => OnManualClosed(slotId); } private void ScheduleCacheExpiration(string slotId, RewardedSlotCache cache) { cache.ExpireHandler?.Kill(); cache.ExpireHandler = null; var maxAgeSeconds = TapadnAdController.CurrentOptions?.RewardedCacheMaxAgeSeconds ?? 600; if (maxAgeSeconds <= 0) { return; } cache.ExpireHandler = ADManager.Instance.CreateTimer(maxAgeSeconds, () => { if (!ReferenceEquals(GetCache(slotId), cache)) { return; } Debug.Log($"[TapADN] Rewarded cache expired. slot={slotId}, maxAgeSeconds={maxAgeSeconds}"); RemoveCache(slotId); if (string.Equals(slotId, ResolveCurrentSlotId(), StringComparison.Ordinal)) { curState = 0; } }); } private void OnManualShown(string slotId) { _activeSlotId = slotId; OnAdShow(); } private void OnManualClicked() { } private void OnManualShowFailed(string slotId, DirichletError error) { RemoveCache(slotId); OnError(error); } private void OnManualRewardVerify(DirichletRewardVerificationEventArgs args) { OnRewardVerify(args); } private void OnManualClosed(string slotId) { RemoveCache(slotId); OnAdClose(); } private void RemoveCache(string slotId) { var cache = GetCache(slotId); if (cache == null) { return; } DestroyCachedAd(cache); _slotCaches.Remove(slotId); } private static void DestroyCachedAd(RewardedSlotCache cache) { if (cache?.Ad == null) { return; } try { cache.ExpireHandler?.Kill(); cache.ExpireHandler = null; cache.Ad.Destroy(); } catch (Exception exception) { Debug.LogWarning($"[TapADN] Rewarded Destroy failed: {exception.Message}"); } finally { cache.Ad = null; cache.Loading = false; cache.LoadedUnix = 0; } } private void CompleteRewardedClose() { if (_showSettled) { return; } _showSettled = true; _closePendingRewardVerify = false; _rewardCloseSettleHandler?.Kill(); _rewardCloseSettleHandler = null; adListener.OnRewardVerify(_rewardVerified, TapadnAdController.CurrentOptions?.RewardAmount ?? 1, TapadnAdController.CurrentOptions?.RewardName ?? string.Empty); adListener.OnAdClose(); } private static string NormalizeScenario(string scenario) { return string.IsNullOrWhiteSpace(scenario) ? "__default__" : scenario.Trim(); } private static long GetNowUnixSeconds() { return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds; } private sealed class RewardedSlotCache { public DirichletRewardVideoAd Ad; public bool Loading; public long LoadedUnix; public AdTimeHandler ExpireHandler; } }