0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Unity×LevelPlay バナー広告でBGM/SEが消える問題の原因と対策

0
Posted at

はじめに

UnityモバイルゲームでLevelPlay(ironSource)のバナー広告を表示すると、BGM/SEが突然無音になる問題に遭った。原因と対策をまとめる。

LevelPlay固有の問題ではなく、AdMob・AppLovin MAX等、WebViewベースのバナー広告を使う広告SDKなら同様に起こり得る。

環境

  • Unity 6 (6000.3.1f1)
  • LevelPlay SDK
  • iOS / Android

症状

  • バナー広告の初回表示・自動リフレッシュ のタイミングでBGM/SEが無音になる
  • AudioSource.isPlaying は true のまま(Unity公式 Won't Fix: UUM-60061)
  • ユーザーが音量設定を触ると復帰する(=音声データ自体は生きている)

原因

バナー広告はWKWebView(iOS)で描画される。WKWebViewは別プロセスで動作し、広告のロード/リフレッシュ時にAVAudioSessionを奪取する。

UnityのFMODベースのオーディオパイプラインはセッションを失うと音声出力が停止するが、isPlayingは内部状態を見ているだけなのでtrueを返し続ける。

さらにバナー広告はSDK内部で自動リフレッシュするため、OnAdLoadedコールバックだけでは全ての中断を検知できない。

試したが不十分だったアプローチ

アプローチ 結果
OnAdLoadedコールバックで復帰 SDK内部の広告取得を検知できず、放置すると無音のまま
AudioSettings.Reset() 単体 セッション共存設定なしでは復帰失敗し、リカバリーループに陥る
Stop() → Play() のみ(Reset不使用) FMODパイプラインが停止しているため無効

解決策

3つの要素の組み合わせで解決した。

  1. timeSamples ポーリングで中断を検出 — isPlayingは信用できないため、AudioSource.timeSamplesが進んでいるかを毎フレーム監視する
  2. iOS ネイティブプラグインで MixWithOthers を設定 — AVAudioSessionCategoryPlayback + MixWithOthers でWebViewとのセッション共存を確立する
  3. AudioSettings.Reset() でオーディオパイプラインを再構築 — セッション共存を確立した上でReset()を呼び、確実に復帰させる

実装

iOS ネイティブプラグイン(AudioSessionMixer.mm)

#import <AVFoundation/AVFoundation.h>

extern "C" void AudioSession_SetMixWithOthers_()
{
    AVAudioSession *session = [AVAudioSession sharedInstance];
    NSError *error = nil;
    [session setCategory:AVAudioSessionCategoryPlayback
             withOptions:AVAudioSessionCategoryOptionMixWithOthers
                   error:&error];
    if (error) { return; }
    [session setActive:YES error:&error];
}

C# 側(SoundManager 抜粋)

// --- フィールド ---
private int lastTimeSamples;
private int stallFrameCount;
private const int StallFrameThreshold = 3;
private float lastRecoveryTime;
private const float RecoveryCooldown = 2f;

#if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void AudioSession_SetMixWithOthers_();
#endif

// --- Update() から毎フレーム呼び出し ---
private void DetectAndRecoverAudioStall()
{
    if (bgmSource == null || bgmSource.clip == null) return;

    // クールダウン中はスキップ(復帰ループ防止)
    if (Time.realtimeSinceStartup - lastRecoveryTime < RecoveryCooldown)
    {
        stallFrameCount = 0;
        lastTimeSamples = bgmSource.timeSamples;
        return;
    }

    var current = bgmSource.timeSamples;

    // isPlaying=true なのに timeSamples が進んでいない → セッション奪取
    if (bgmSource.isPlaying && current == lastTimeSamples && current > 0)
    {
        stallFrameCount++;
        if (stallFrameCount >= StallFrameThreshold)
        {
            RecoverAudio();
        }
    }
    else
    {
        stallFrameCount = 0;
    }
    lastTimeSamples = current;
}

private void RecoverAudio()
{
    lastRecoveryTime = Time.realtimeSinceStartup;
    stallFrameCount = 0;

#if UNITY_IOS && !UNITY_EDITOR
    AudioSession_SetMixWithOthers_();  // セッション共存を確立
#endif

    var clip = bgmSource.clip;
    var time = bgmSource.timeSamples;
    var pitch = bgmSource.pitch;

    AudioSettings.Reset(AudioSettings.GetConfiguration());

#if UNITY_IOS && !UNITY_EDITOR
    AudioSession_SetMixWithOthers_();  // Reset()がカテゴリを上書きするため再設定
#endif

    bgmSource.clip = clip;
    bgmSource.timeSamples = time;
    bgmSource.pitch = pitch;
    bgmSource.Play();
}

ポイント

要素 理由
timeSamples 監視 isPlaying が信用できないため、実際にサンプルが進んでいるかで判定
3フレーム閾値 1-2フレームの誤検知を防止
2秒クールダウン Reset() → 再検出 → Reset() の無限ループを防止
MixWithOthers WebViewとセッションを共存させ、Reset()の成功率を上げる
Reset()後の再MixWithOthers Reset()が内部でカテゴリを再設定する可能性があるため

残る制約

  • 約100msの無音(検出3フレーム + Reset処理)はAudioSettings.Reset()を使う限り不可避。Unityエンジンレベルの制約。
  • 起動時からPlayback + MixWithOthersを設定すれば中断自体を防げるが、マナーモードで音が鳴る副作用があるため不採用。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?