この記事は、UnityでAdmobを実装した謎の現象への対応策を備忘録として記録したものです。
Table of Contents(目次)
サマリー
急いでいる方のためにまずは要約をば。
- iOSの実機にて、AdMobの動画広告再生後にオーディオが消失する現象が起こる
- 単にオーディオを停止→再度再生して解決しようとしてもオーディオは鳴らない
- AudioSettings.Resetを実行することにより、消失したオーディオをリセットでき、その後であればオーディオを再生できる
発生した問題: AdMob動画広告視聴後に音声が消える(iOS)
UnityでAdMobを導入し、エディタではうまくテスト広告が表示されていたものの、いざiOS実機に入れてみると動画広告視聴後にゲーム音声が消えてしまうという問題に直面しました。
動画広告はAdMobに用意されている以下の2種類の広告を取り入れていました。
- インタースティシャル広告
- リワード広告
なお、問題が起こった際の私の環境は以下の通りです。なお、テストデバイスはAdMobのWebコンソールからきちんとテストデバイスとして登録してある状態で動作確認していました。
環境 | バージョン |
---|---|
Unity | 2020.1.10f1 |
AdMob | 6.1.2 |
実機テストデバイス | iPhone XR iOS 14.8 |
なお、本記事の実装は以下のUdemy講座の実装を参考に作成しています。
問題を再現するために最小限のアプリを作成
アプリの機能
問題の原因調査のために、最小限の機能を持つアプリを作成したので、そのコードをここで紹介します。
アプリの持つ機能は以下のようなものです。
- シーンはボタンが並んでいる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)にぜひ教えてください!