.NET MAUIアプリで音声を再生しているのに、Androidだけページ遷移やホーム復帰、画面ロックをきっかけに再生が止まって困ることがあります。本記事では原因を整理しつつ、MediaElementをアプリ共通のプレイヤーとして使い回して「止めずに再生」と「一時停止位置から再開」を両立する実装を解説します。
Androidで「ページ遷移すると音が止まる」現象を整理する
まず状況を言語化しておくと、トラブルシューティングが一気に楽になります。今回のケースは次のようなものです。
- .NET MAUIで音声(
audioUrl)を再生したい - 当初は
WebViewで再生していた - iOSは同じコードでも再生が継続するのに、Androidはページ遷移/ホームに戻る/画面ロックなどで停止する
- Android側ログに
AAudioStream_requestStopやAAudioStream_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の設定を検討

コメント