【Unity/iOS】How to solve disappearing audio after AdMob ad played on real device

12 min
【Unity/iOS】How to solve disappearing audio after AdMob ad played on real device

This article is a memorandum of how to deal with the mysterious phenomenon of implementing AdMob in Unity.

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.

EnvironmentVersion
Unity2020.1.10f1
Unity AdMob SDK6.1.2
Test deviceiPhone 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.
Minimum app appearance

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());
}
Add ‘Audio Rest’ button

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!

みどー

みどー

2020年2月からフリーランスエンジニア。スタートアップで週4稼働フルリモートでまったりお仕事中。個人開発・ブログ運営・IT技術情報について発信していきます。

FOLLOW

カテゴリー:
タグ:
関連記事