【Unity/iOS】実機でAdMob動画広告視聴後にゲーム音声が消える問題の対応策

10 min
【Unity/iOS】実機でAdMob動画広告視聴後にゲーム音声が消える問題の対応策

この記事は、UnityでAdmobを実装した謎の現象への対応策を備忘録として記録したものです。

サマリー

急いでいる方のためにまずは要約をば。

  • iOSの実機にて、AdMobの動画広告再生後にオーディオが消失する現象が起こる
  • 単にオーディオを停止→再度再生して解決しようとしてもオーディオは鳴らない
  • AudioSettings.Resetを実行することにより、消失したオーディオをリセットでき、その後であればオーディオを再生できる

発生した問題: AdMob動画広告視聴後に音声が消える(iOS)

UnityでAdMobを導入し、エディタではうまくテスト広告が表示されていたものの、いざiOS実機に入れてみると動画広告視聴後にゲーム音声が消えてしまうという問題に直面しました。

動画広告はAdMobに用意されている以下の2種類の広告を取り入れていました。

  • インタースティシャル広告
  • リワード広告

なお、問題が起こった際の私の環境は以下の通りです。なお、テストデバイスはAdMobのWebコンソールからきちんとテストデバイスとして登録してある状態で動作確認していました。

環境バージョン
Unity2020.1.10f1
AdMob6.1.2
実機テストデバイスiPhone XR iOS 14.8

問題を再現するために最小限のアプリを作成

アプリの機能

問題の原因調査のために、最小限の機能を持つアプリを作成したので、そのコードをここで紹介します。

アプリの持つ機能は以下のようなものです。

  • シーンはボタンが並んでいる1シーンのみ
  • 「Audio Start」ボタンを押すとBGMが流れる
  • 「Audio Stop」ボタンを押すとBGMが止まる
  • 「Interstitial Show」ボタンを押すとインタースティシャル広告が表示される
  • 「Reward Show」ボタンを押すとリワード広告が表示される
最小限のアプリ

ソースコード: AudioController

BGMの再生と停止をします。

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();
    }
}

なお、継承元となっているSingletonMonoBehaviourは、以下の記事で紹介されているものと似たような実装です。

ソースコード: AdMobInterstitialManager

AdMobのインタースティシャル広告を管理するためのクラスです。

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();
    }
}

ソースコード: AdMobRewardManager

AdMobのリワード広告を管理するためのクラスです。

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);
    }
}

ソースコード: Main

上記で作成してきたクラスやAdMobの初期化を行います。また、ボタンクリック時の処理もここに書いています。

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();
    }
}

問題の再現

ここまでのコードをシーンに配置し、各ボタンにMain.csの対応する関数を割り当てることで、問題の再現ができると思います。

動画広告を見るまでは音楽の再生/停止が正常に動作しますが、動画広告の後は音楽の制御が全くできず、音楽が消えます。

対応策: 動画広告を再生後にオーディオを全てリセットする

対応策

Web検索して出てきた様々な方法を試して唯一効果があったのが「オーディオのリセット」です。

ここでいう「リセット」は単にAudioSourceコンポーネントで鳴っているBGMを停止して再度再生し直すということではなく、ゲームで鳴っているオーディオを根本から再構築するようなものです。

これは、以下のコードで実現できます。

AudioSettings.Reset(AudioSettings.GetConfiguration());

なお、上記の処理を行うとすでに鳴っているオーディオは全て止まってしまうため、リセット前の状態を再現するにはBGM・SEを再度再生する処理が必要になる点に注意してください。

やってみる

上記の処理をもとに、Main.csに以下のような関数を増やし、新たに「Audio Reset」ボタンを作成します。

public void OnAudioReset()
{
    Debug.Log("OnAudioReset");
    AudioSettings.Reset(AudioSettings.GetConfiguration());
}
オーディオをリセットできるようにする

実機にアプリを入れて、動画広告をみた後、オーディオの再生・停止ボタンを何度押してもオーディオは消えたままです。

しかし、オーディオのリセットをしてからオーディオの再生・停止を試すと正常に動作することがわかります。

参考にした情報

この対応策に辿り着くきっかけは、オーディオ再生時に表示されたログと、以下の2つのWebサイトでした。

まず、Xcodeに表示された以下のログから、どうやらiOSのAudioSessionというものが悪さをしていることがわかりました。

ログを訳すと「オーディオI/Oがある状態でオーディオセッションを無効化しようとした」ことがエラーの原因と読み取れたのですが、実験してみたところ動画広告表示時にAudioSourceが再生状態かどうかは今回の現象には関係なさそうでした。

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.

そして、以下の2つのサイトから得られた情報により「AudioSettins.Reset」が解決策になりそうだとわかりました。

  • オーディオの完全なリセットができれば効果があるかもしれない
  • Unityでのオーディオのリセットには、ネイティブメソッドの「UnitySetAudioSessionActive」を呼ぶ必要がある
  • Unityの「AudioSettings.Reset」は内部で「UnitySetAudioSessionActive」を呼んでいる

最後に

この記事では、Unityで作成したアプリでAdMob動画広告を視聴した場合、iOSの実機でオーディオが消失する問題の解決策とそこに至る過程を紹介しました。

根本的な原因がなんなのか、根本的に解決する方法がなんなのかは今もわからないままです。

もし、わかった方がいらっしゃれば、みどー(@mido-app)にぜひ教えてください!

みどー

みどー

ITコンサル会社→フリーランスエンジニアの経歴を経て、現在はスタートアップ企業でエンジニアをしています。30代前半ながら本業では年収1000万円&フルリモートで仕事できる環境を手に入れたので、今後は自分自身のプロダクトを作って会社に依存せず生きていくことを目標に副業開発やブログ発信をしています。副業開発・ブログ運営・IT技術情報について発信していきます。

FOLLOW

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