This article is a memorandum of how to deal with the mysterious phenomenon of implementing AdMob in Unity.
Table of Contents(目次)
Summary
Here is a summary of this article.
- In iOS real device, game audio has gone after AdMob ad played.
- If you simply stop and restart the audio of AudioSource, you cannot solve this problem.
- By using ‘AudioSettings.Reset’ method after ad played, you can restart audio.
Problem that occurred:audio has gone after AdMob ad played(iOS)
I implement my game to show ad using AdMob with Unity.
Then, I faced a problem that if user play movie ad, game audio completely disappeared in read iOS device.
I include two types of movie ad in my game below.
- Interstitial Ad
- Reward Ad
My environment is as follows. And I register my iPhone as test device in AdMob Web Console.
Environment | Version |
---|---|
Unity | 2020.1.10f1 |
Unity AdMob SDK | 6.1.2 |
Test device | iPhone XR iOS 14.8 |
Create a minimum app to reproduce the problem
App features
In this section, I introduce you the code that minimum app to reproduce the problem.
Features of this app is as follows.
- App has only one scene that has some buttons.
- On click ‘Audio Start’ button, start BGM.
- On click ‘Audio Stop’ button, stop BGM.
- On click ‘Interstitial Show’ button, Interstitial ad shows.
- On click ‘Reward Show’ button, Reward ad shows.
Source code: AudioController
This component handles starting or stopping audio.
using UnityEngine;
[RequireComponent(typeof(AudioSource))]
public class AudioController : SingletonMonoBehaviour<AudioController>
{
private AudioSource source;
public AudioClip clip;
public void Initialize()
{
source = GetComponent<AudioSource>();
}
public void StartBGM()
{
source.clip = clip;
source.Play();
}
public void StopBGM()
{
source.Stop();
}
}
In addition, SingletonMonoBehaviour, which is the inheritance source, is like below.
using UnityEngine;
using System;
public abstract class SingletonMonoBehaviour<T> : MonoBehaviour where T : SingletonMonoBehaviour<T>
{
private static T _instance;
public static T Instance
{
get
{
//nullチェック
if (_instance == null)
{
_instance = (T)FindObjectOfType(typeof(T));
}
return _instance;
}
}
//シーン間でもインスタンスのオブジェクトが1つになるようにする
protected virtual void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
}
else
{
_instance = GetComponent<T>();
}
}
}
Source code: AdMobInterstitialManager
This is a component to manage Interstitial Ad.
using System;
using GoogleMobileAds.Api;
using UnityEngine;
public class AdMobInterstitialManager : SingletonMonoBehaviour<AdMobInterstitialManager>
{
private InterstitialAd _interstitial;
public void LoadInterstitial()
{
RequestInterstitial();
}
private void RequestInterstitial()
{
#if UNITY_EDITOR
string adUnitId = "unused";
#elif UNITY_ANDROID
string adUnitId = "Your ad unit id";
#elif UNITY_IPHONE
string adUnitId = "Your ad unit id";
#else
string adUnitId = "unexpected_platform";
#endif
_interstitial = new InterstitialAd(adUnitId);
_interstitial.OnAdLoaded += HandleOnAdLoaded;
_interstitial.OnAdFailedToLoad += HandleOnAdFailedToLoad;
_interstitial.OnAdOpening += HandleOnAdOpened;
_interstitial.OnAdClosed += HandleOnAdClosed;
AdRequest request = new AdRequest.Builder()
.Build();
_interstitial.LoadAd(request);
}
public void UnloadInterstitial()
{
if (_interstitial != null)
{
_interstitial.Destroy();
}
}
public bool ShowInterstitial()
{
if (_interstitial == null)
{
return false;
}
if (_interstitial.IsLoaded())
{
Debug.Log("Show Interstitial Ad.");
_interstitial.Show();
return true;
}
else
{
Debug.Log("Interstitial Ad has not loaded yet.");
return false;
}
}
private void HandleOnAdLoaded(object sender, EventArgs args)
{
Debug.Log("AdMobInterstitial.HandleAdLoaded event received");
}
private void HandleOnAdFailedToLoad(object sender, AdFailedToLoadEventArgs args)
{
Debug.Log("AdMobInterstitial.HandleFailedToReceiveAd event received with message: " + args.LoadAdError.GetMessage());
}
private void HandleOnAdOpened(object sender, EventArgs args)
{
Debug.Log("AdMobInterstitial.HandleAdOpened event received");
}
private void HandleOnAdClosed(object sender, EventArgs args)
{
Debug.Log("AdMobInterstitial.HandleAdClosed event received");
UnloadInterstitial();
}
}
Source code: AdMobRewardManager
This is a component to manage Reward Ad.
using System;
using GoogleMobileAds.Api;
using UnityEngine;
public class AdMobRewardManager : SingletonMonoBehaviour<AdMobRewardManager>
{
private RewardedAd _rewardedAd;
public bool IsLoaded()
{
if (_rewardedAd == null) return false;
return _rewardedAd.IsLoaded();
}
public void LoadRewardAd()
{
#if UNITY_EDITOR
string adUnitId = "unused";
#elif UNITY_ANDROID
string adUnitId = "Your ad unit Id";
#elif UNITY_IPHONE
string adUnitId = "Your ad unit Id";
#else
string adUnitId = "unexpected_platform";
#endif
_rewardedAd = new RewardedAd(adUnitId);
_rewardedAd.OnAdLoaded += HandleRewardedAdLoaded;
_rewardedAd.OnAdFailedToLoad += HandleRewardedAdFailedToLoad;
_rewardedAd.OnAdOpening += HandleRewardedAdOpening;
_rewardedAd.OnAdFailedToShow += HandleRewardedAdFailedToShow;
_rewardedAd.OnUserEarnedReward += HandleUserEarnedReward;
_rewardedAd.OnAdClosed += HandleRewardedAdClosed;
AdRequest request = new AdRequest.Builder()
.Build();
_rewardedAd.LoadAd(request);
}
public bool ShowRewardAd()
{
if (_rewardedAd == null)
{
return false;
}
if (_rewardedAd.IsLoaded())
{
Debug.Log("Show Reward Ad");
_rewardedAd.Show();
return true;
}
else
{
Debug.Log("Reward Ad has not loaded yet.");
return false;
}
}
private void HandleRewardedAdLoaded(object sender, EventArgs args)
{
Debug.Log("AdMobRewardManager.HandleRewardedAdLoaded event received");
}
private void HandleRewardedAdFailedToLoad(object sender, AdFailedToLoadEventArgs args)
{
Debug.Log("AdMobRewardManager.HandleRewardedAdFailedToLoad event received with message: " + args.LoadAdError.GetMessage());
}
private void HandleRewardedAdOpening(object sender, EventArgs args)
{
Debug.Log("AdMobRewardManager.HandleRewardedAdOpening event received");
}
private void HandleRewardedAdFailedToShow(object sender, AdErrorEventArgs args)
{
Debug.Log(
"AdMobRewardManager.HandleRewardedAdFailedToShow event received with message: "
+ args.AdError.GetMessage());
}
private void HandleRewardedAdClosed(object sender, EventArgs args)
{
LoadRewardAd();
Debug.Log("AdMobRewardManager.HandleRewardedAdClosed event received");
}
private void HandleUserEarnedReward(object sender, Reward args)
{
string type = args.Type;
double amount = args.Amount;
Debug.Log("AdMobRewardManager.HandleRewardedAdRewarded event received for " + amount.ToString() + " " + type);
}
}
Source code: Main
This is application entry point that handle AdMob initialization and button click.
using GoogleMobileAds.Api;
using UnityEngine;
public class Main : MonoBehaviour
{
private void Start()
{
AudioController.Instance.Initialize();
MobileAds.Initialize(status =>
{
AdMobInterstitialManager.Instance.LoadInterstitial();
AdMobRewardManager.Instance.LoadRewardAd();
Debug.Log("Initialization End");
});
}
public void OnAudioStart()
{
Debug.Log("OnAudioStart");
AudioController.Instance.StartBGM();
}
public void OnAudioStop()
{
Debug.Log("OnAudioStop");
AudioController.Instance.StopBGM();
}
public void OnInterstitialShow()
{
Debug.Log("OnInterstitialShow");
AdMobInterstitialManager.Instance.ShowInterstitial();
}
public void OnRewardShow()
{
Debug.Log("OnRewardShow");
AdMobRewardManager.Instance.ShowRewardAd();
}
}
Reproduction step
Please attach the code above to gameobject in scene. And attach methods in Main.cs to buttons.
If you start BGM by clicking ‘Audio Start’ button before watching Interstitial or Reward ad, audio successfully starts.
But If you start BGM after watching Interstitial or Reward ad, audio never starts.
Solution: Reset audio after playing movie ad
Solution
The only solution I found
I tried various methods that came out by searching the Web, and the only one that worked was “Reset Audio”.
“Reset” here isn’t just about stopping the BGM playing in the AudioSource component and playing it again, it’s like rebuilding the audio playing in the game from the ground up.
You can reset audio by the code below.
AudioSettings.Reset(AudioSettings.GetConfiguration());
Please note that if you use the above code, all the audio that is already sounding will stop. So you will need to replay the BGM / SE to reproduce the state before the reset.
Try
Based on the above discussion, add the following functions to Main.cs and create a new “Audio Reset” button.
public void OnAudioReset()
{
Debug.Log("OnAudioReset");
AudioSettings.Reset(AudioSettings.GetConfiguration());
}
After installing the app on the actual device and watching the video advertisement, the audio remains off no matter how many times you press the play/stop button of the audio.
However, if you reset the audio and then try to play / stop the audio, you will find that it works fine.
Referenced information
The reason for arriving at this workaround was the log displayed during audio playback and the following two websites.
First, from the following log displayed in Xcode, it seems that the iOS Audio Session is doing something wrong.
2021-12-13 21:57:58.094198+0900 admob-sample[10880:1151670] [avas]
AVAudioSession_iOS.mm:1206 Deactivating an audio session that has running I/O.
All I/O should be stopped or paused prior to deactivating the audio session.
And, from the information obtained from the following two sites, I found that ‘AudioSettins.Reset’ method seems to be a solution.
- A complete audio reset might help
- To reset the audio in Unity, you need to call the native method “UnitySetAudioSessionActive”
- Unity’s ‘AudioSettings.Reset’ internally calls ‘UnitySetAudioSessionActive’
Finally
In this article, I introduced the solution to the problem of audio loss on the actual iOS device when watching an AdMob movie ad with an app created with Unity, and the process to get there.
It remains unclear what the root cause is and how to solve it.
If anyone knows, please let Mido (@ mido-app) know!