.NET MAUIでAndroidのページ遷移でも音声を止めない方法|MediaElementでレジューム再生

.NET MAUIアプリで音声を再生しているのに、Androidだけページ遷移やホーム復帰、画面ロックをきっかけに再生が止まって困ることがあります。本記事では原因を整理しつつ、MediaElementをアプリ共通のプレイヤーとして使い回して「止めずに再生」と「一時停止位置から再開」を両立する実装を解説します。

目次

Androidで「ページ遷移すると音が止まる」現象を整理する

まず状況を言語化しておくと、トラブルシューティングが一気に楽になります。今回のケースは次のようなものです。

  • .NET MAUIで音声(audioUrl)を再生したい
  • 当初はWebViewで再生していた
  • iOSは同じコードでも再生が継続するのに、Androidはページ遷移/ホームに戻る/画面ロックなどで停止する
  • Android側ログにAAudioStream_requestStopAAudioStream_closeが出ており、音声ストリームがクローズされている

WebViewは「画面に表示するためのビュー」であり、ページ遷移や画面状態の変化に合わせて破棄・再生成されやすいコンポーネントです。Androidは特に、ビューが外れた瞬間にメディア再生が止まる(止められる)挙動になりやすく、WebViewに再生を任せるとUIライフサイクルの都合で音が止まる問題が出やすくなります。

方式長所短所/ハマりどころ向いているケース
WebViewで再生既存のWebプレイヤーやJS資産をそのまま使えるページ遷移やWebView破棄で停止しやすい。Androidで顕著「そのページにいる間だけ」再生できればよい
MediaElementで再生アプリ側で再生制御しやすい。UIと分離した設計にしやすい実装場所(どのページに置くか)を誤ると同じ問題が起きるページ遷移しても再生を続けたい/再開位置を保ちたい

結論:MediaElementを「共通プレイヤー」として1つだけ持つ

対策の核はシンプルです。MediaElementをアプリ全体で1つだけ生成し、ページを跨いでも生き続ける場所に常駐させる。そうすれば、ページが切り替わっても同じプレイヤーが鳴り続けます。

実装方法はいくつかありますが、ここでは分かりやすい「staticなMediaElementを1つだけ持つ」パターンを紹介します(後半で、static利用時の注意点も触れます)。

実装手順:static MediaElementを初期化してレイアウトに常駐させる

ポイントは2つです。

  • MediaElementは一度だけ生成する(ページが表示されるたびに作り直さない)
  • Sourceは「初期化時」または「音源を切り替える時」だけ設定する(Play/Pauseのたびに触らない)

例として、最初に開くページ(例:DailySaintPage)にMediaElementを作り、ほぼ見えないサイズでGridに追加します。

public partial class DailySaintPage : ContentPage
{
    // アプリ共通のプレイヤー
    public static MediaElement MymediaElement;

    // 再生状態を簡易的に管理(UI変更などに使う)
    public static bool IsPlaying = false;

    // 例:再生したい音源URL
    private string mAudioURL = "https://example.com/audio/sample.mp3";

    protected override void OnAppearing()
    {
        base.OnAppearing();

        // ★重要:OnAppearingは何度も呼ばれるため、二重生成を防ぐ
        if (MymediaElement == null)
        {
            MymediaElement = new MediaElement
            {
                WidthRequest = 1,
                HeightRequest = 1, // ほぼ見えないプレイヤーにする
                ShouldAutoPlay = false
            };

            // ★ポイント:Sourceの設定は初期化時(または音源変更時)だけ
            MymediaElement.Source = mAudioURL;

            // 画面上のGridなどに追加(表示しないが、Visual Treeに常駐させる)
            myGrid.Add(MymediaElement);
        }
    }
}

このようにstaticで保持しておくと、他のページからもDailySaintPage.MymediaElementとしてアクセスでき、ページ遷移しても同じインスタンスが使われます。結果として、Androidでも「ページが変わったから止まる」という問題を回避しやすくなります。

再生/一時停止ボタンはstatic MediaElementを直接操作する

次に、再生ボタンと一時停止ボタンの実装例です。XAML側はImageにタップジェスチャを付け、タップでPlayAudioを呼びます。

<Grid Grid.Column="1" x:Name="myGrid">
  <Image
    HorizontalOptions="CenterAndExpand"
    VerticalOptions="CenterAndExpand"
    Source="ic_audio_play_icon_xx.png">
    <Image.GestureRecognizers>
      <TapGestureRecognizer
        Tapped="PlayAudio"
        NumberOfTapsRequired="1" />
    </Image.GestureRecognizers>
  </Image>
</Grid>

コードビハインド側では、再生中ならPause、停止中ならPlay、といったトグルにします。

public void PlayAudio(object sender, EventArgs args)
{
    var image = (Microsoft.Maui.Controls.Image)sender;

    try
    {
        // URLが未設定なら何もしない
        if (string.IsNullOrWhiteSpace(mAudioURL) || DailySaintPage.MymediaElement == null)
            return;

        if (DailySaintPage.IsPlaying)
        {
            // 再生中 → 一時停止
            DailySaintPage.MymediaElement.Pause();
            DailySaintPage.IsPlaying = false;
            image.Source = "ic_audio_play_icon_xx.png";
        }
        else
        {
            // 一時停止中 → 続きから再生
            DailySaintPage.MymediaElement.Play();
            DailySaintPage.IsPlaying = true;
            image.Source = "ic_audio_pause_icon_xx.png";
        }
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine("Exception:>" + ex);
    }
}

ここでの狙いは、ポップアップやページ固有のWebViewに再生を任せず、常駐している共通プレイヤーを直接操作することです。これだけで「ページ遷移しても鳴り続ける」挙動に寄せられます。

レジューム再生ができない最大の原因:Play/PauseのたびにSourceを再設定している

MediaElementに切り替えた後に起きがちな二次トラブルが、次の現象です。

  • PauseしてからPlayすると、曲の頭から再生される
  • 「止めた位置から再開」したいのに戻ってしまう

この原因として非常に多いのが、PlayやPauseのメソッド内で毎回Sourceを設定し直しているパターンです。Sourceを再設定すると、MediaElementは「新しいメディア」として扱い、再生位置が0にリセットされやすくなります。

やり方起こりやすい挙動
NG:Play/PauseのたびにSourceを再設定Source = url; Play();再開時に先頭へ戻る(レジュームできない)
OK:Sourceは初期化時/音源変更時だけPause(); Play();Pauseした位置からそのまま再開しやすい

つまり、やりたいことが「一時停止」なら、やるべき操作はSourceの差し替えではなくPauseです。逆に「最初から再生し直したい」ならStopやSource再設定が必要になる、という棲み分けが重要です。

音源を切り替えるときだけSourceを更新する

では、別のページで新しい音源を再生したい場合はどうするか。結論は、音源を変える必要がある時にだけSourceを差し替える、です。例を示します。

private void ChangeTrackAndPlay(string newUrl)
{
    if (DailySaintPage.MymediaElement == null) return;

    // いま鳴っているものを止める(曲を切り替える意図)
    DailySaintPage.MymediaElement.Stop();
    DailySaintPage.IsPlaying = false;

    // ★ここでだけSourceを更新
    DailySaintPage.MymediaElement.Source = newUrl;

    // 新しい曲を再生
    DailySaintPage.MymediaElement.Play();
    DailySaintPage.IsPlaying = true;
}

逆に、単に「一時停止→再開」をしたいだけなら、Stop()Source更新も不要で、Pause()Play()だけで十分です。

やりたい操作推奨メソッド補足
途中で止めて、同じ位置から再開Pause()Play()Sourceに触らないのが鉄則
最初から再生し直すStop()Play()実装によっては先頭に戻る
曲を変更して再生Stop()Source差し替え → Play()「曲を変える時だけ」Source更新

ページ遷移でも止めないための設計ポイント

staticにしても、実装場所やライフサイクルを間違えると「結局止まる」ことがあります。特にAndroidで安定させるために、次の点を意識してください。

  • 生成は一度だけ:OnAppearingで毎回newしない(if (MymediaElement == null)でガード)
  • 破棄しない:OnDisappearingでStopしたり、レイアウトからRemoveしない
  • 見えない=存在しないにしない:IsVisible=falseで止まる場合があるため、最小サイズで配置しておく方が無難
  • UIイベントは最小限:イベント多重購読(+=を繰り返す)で二重再生や状態不整合が起きやすい

staticは手軽ですが、規模が大きいアプリほど「状態管理が散らばる」「テストしづらい」「参照が残って解放されない」などの問題を生みやすいです。中〜大規模なら、アプリ全体で1つのプレイヤーを管理するシングルトンサービス(DI)として切り出し、UIはサービスを叩くだけ、という構成も検討すると安定します。

よくある原因と対策(止まる/再開できない/音が出ない)

「止まらなくなったはずなのに、端末によって挙動が違う」「たまに先頭に戻る」など、運用で遭遇しやすいポイントを表にまとめます。

症状よくある原因対策
Pause→Playで先頭からになるPlay/Pause内でSourceを再設定しているSourceは初期化時・音源変更時だけ。Pause/Playでは触らない
ページ遷移で止まるページごとにMediaElementを生成している/画面から外しているアプリ共通の1インスタンスを常駐させる(staticやサービス化)
音が出ない(Android)URLがhttpでブロック、またはネットワーク到達不可https化、または後述のusesCleartextTraffic設定を検討
曲を変えたら前の曲が残る/状態が混ざるStopせずにSourceだけ差し替え、状態フラグも未更新曲変更時はStop→Source更新→Play、フラグも揃えて更新する

HTTP(http://)音源を再生する場合のAndroid設定

音源URLがhttp://(暗号化されていない)場合、Androidはセキュリティ方針により通信をブロックすることがあります。その場合は、AndroidManifest.xml<application>に次の属性を追加します。

<application
  android:usesCleartextTraffic="true"
  ... >
</application>

ただし、可能であればhttpsを使うのが推奨です。音源配信側のTLS対応は、ユーザー体験(再生失敗の減少)にも直結します。

「完全なバックグラウンド再生」をしたい場合に知っておくべきこと

ここまでの構成は主に、アプリ内でページを遷移しても音が止まらないようにすることが目的です。一方、Androidで次のような要件がある場合は、別の実装が必要になります。

  • ホームに戻っても鳴らし続けたい
  • 端末ロック中も鳴らし続けたい
  • 通知領域に再生/停止などのコントロールを出したい

これらは一般に「バックグラウンド再生」と呼ばれ、AndroidではフォアグラウンドサービスMediaSession、通知(Notification)などと連携した実装が必要になることが多いです。MAUIのUIコンポーネントだけで完結させるのは難しいため、専用のオーディオライブラリ/プラグインの導入や、プラットフォーム別実装を検討すると良いでしょう。

まとめ

  • AndroidでWebView再生がページ遷移で止まるのは、WebViewがUIライフサイクルで破棄されやすいことが大きな要因
  • MediaElementをアプリ共通のプレイヤーとして1つだけ持ち回し、ページを跨いでも同じインスタンスを使うと止まりにくい
  • レジューム再生の鍵は、Play/PauseのたびにSourceを再設定しないこと(Source更新は初期化/音源変更時のみ)
  • http音源はAndroidでブロックされることがあるため、https化かusesCleartextTrafficの設定を検討

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次