.NET MAUI で「音が鳴らない」「FileNotFoundException が出る」「MediaElement を載せたら Android だけクラッシュする」といったトラブルはとても起こりがちです。この記事では、Plugin.Maui.Audio と CommunityToolkit.Maui.MediaElement を中心に、よくある課題をパターン別に整理しながら、具体的な解決策と設計の考え方をまとめます。Android / iOS / .NET 9 それぞれの注意点も併せて確認していきましょう。
.NET MAUI で音声が再生できないときの基本パターン
課題:Plugin.Maui.Audio で FileNotFoundException が出る
もっとも多いのが、次のようなコードを書いたときに FileNotFoundException が発生し、音が再生されないパターンです。
using Plugin.Maui.Audio;
var player = AudioManager.Current.CreatePlayer(
await FileSystem.OpenAppPackageFileAsync("myaudio.mp3")
);
player.Play();
この時点で疑うべきポイントは「API ではなく、ファイルの配置とビルド設定」です。MAUI のリソース周りは慣れるまで間違えやすいので、まずは落ち着いて次の3点を確認します。
- 配置フォルダが
Resources/Raw(複数形)になっているか - 音声ファイルの Build Action が
MauiAssetになっているか OpenAppPackageFileAsyncに渡している名前が正しいか(パスなし・拡張子含む・大文字小文字一致)
解決策:Resources/Raw + MauiAsset + ファイル名だけ を徹底する
Plugin.Maui.Audio でアプリパッケージ内の音声を鳴らす場合、正しい組み合わせは次のとおりです。
| 項目 | 正しい設定 | よくある間違い |
|---|---|---|
| 配置フォルダ | Resources/Raw | Resource/Raw や Resources/Sounds など |
| Build Action | MauiAsset | Content, EmbeddedResource, None など |
| 参照名 | "myaudio.mp3"(ファイル名のみ) | "Resources/Raw/myaudio.mp3" のようにパス付き |
具体的には、次のような構成にします。
- プロジェクト直下に
Resourcesフォルダ - その中に
Rawフォルダを作成 Resources/Raw/myaudio.mp3の Build Action を MauiAsset に変更
そのうえで、コード側は次のようにパスなしで呼び出します。
using Plugin.Maui.Audio;
async Task PlayAsync()
{
using var stream = await FileSystem.OpenAppPackageFileAsync("myaudio.mp3");
using var player = AudioManager.Current.CreatePlayer(stream);
player.Play();
}
これだけで解決することも多いですが、まだダメな場合は次のチェックも行います。
- ファイル名の 大文字小文字 が完全一致しているか(
MyAudio.mp3とmyaudio.mp3は別物) - 拡張子を変えたあとに古いファイルが残っていないか
- プロジェクトのクリーンと再ビルド、必要なら
bin/objフォルダを削除したか
特に Android はファイル名の大小文字にシビアで、1文字違うだけで FileNotFoundException になります。迷ったら、ビルド出力ログやデバッガで例外メッセージを再確認しましょう。
MediaElement での再生と「UI に出さずに鳴らしたい」問題
課題:MediaElement を使いたいが UI に出したくない
動画再生やストリーミングを含めて扱いたい場合、公式の CommunityToolkit.Maui.MediaElement を使う選択肢があります。しかし、MediaElement は ビュー(UI要素) なので、視覚ツリーに存在させておかないと動作しないという制約があります。
「ボタンも何も表示せずに、コードだけで裏で鳴らしたい」という要件では、次のような疑問が生まれます。
- 画面に置かないといけないの?
- 非表示でもいい?
- 別ページやサービスからどうやって鳴らす?
解決策:ページやシェルに非表示 MediaElement を配置してコードから操作
UI に見せたくない場合でも、MediaElement 自体はどこかの画面(ページやシェル)に載せておきます。その上で、IsVisible="False" にして非表示にし、コードから再生制御を行うのがシンプルです。
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="MyApp.MainPage">
<Grid>
<toolkit:MediaElement x:Name="mediaplayer"
Source="embed://MyApp.Resources.Sounds.myaudio.mp3"
IsVisible="False"
ShouldAutoPlay="False" />
</Grid>
</ContentPage>
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
void OnPlayClicked(object sender, EventArgs e)
{
mediaplayer.Play();
}
}
ここでは Source に embed:// を使っていますが、これは「埋め込みリソース(EmbeddedResource)」を指す URI です。次の点に注意してください。
- MauiAsset ではなく EmbeddedResource を使う(Build Action を
EmbeddedResource) - 完全修飾名は
既定の名前空間.フォルダ.ファイル名の形になる - うまく解決できない場合は、
embed://MyApp.Resources.Sounds.myaudio.mp3のように完全なリソース名を渡す
リソースの種類と参照方法の対応表
MauiAsset / EmbeddedResource が混ざると混乱しやすいので、一度整理しておきましょう。
| 方式 | 配置場所 | Build Action | 主な参照方法 | 向いている用途 |
|---|---|---|---|---|
| MauiAsset | Resources/Raw | MauiAsset | FileSystem.OpenAppPackageFileAsync("file.mp3") | Plugin.Maui.Audio での効果音やBGM |
| EmbeddedResource | 任意のフォルダ | EmbeddedResource | MediaSource.FromResource("MyApp.Resources.Sounds.file.mp3")または Source="embed://MyApp.Resources.Sounds.file.mp3" | MediaElement での再生(音声・動画) |
どちらの方式も同時に使うことはできますが、「このプロジェクトでは音声はすべて MauiAsset」「この画面だけ EmbeddedResource」 のように、パターンを決めておくとトラブルが減ります。
別ページやサービスクラス、バックグラウンドから音を鳴らしたい
課題:ページに置いた MediaElement を他のクラスから操作できない
MediaElement はビューなので、通常はそのページのコードビハインドや ViewModel から操作します。しかし、次のような要件では、ページをまたいだ再生制御が必要になります。
- アプリ全体で共通の BGM を流したい
- 期限前のタイマーがサービスクラスで動いており、そのタイミングで効果音を鳴らしたい
- バックグラウンドでも音を鳴らしたい
単純に「サービスクラスに MediaElement を new する」というのは UI スレッドやライフサイクルの観点で危険です。そこで、アプリのルートに MediaElement を1つだけ配置し、それをサービスから操作するという構成が現実的です。
解決策:AppShell / MainPage に常駐 MediaElement を置き、サービスから参照
考え方は次のとおりです。
AppShellやMainPageに、非表示のMediaElement(または Plugin.Maui.Audio 用のハブ)を用意- その参照を DI コンテナ(依存性注入) などで共有する
- サービス側からは、UI スレッド上でその MediaElement を操作する
例として、音声再生を仲介する「AudioHub」サービスを定義します。
public interface IAudioHub
{
void Attach(MediaElement element);
void PlayEmbedded(string resourceId);
}
public class AudioHub : IAudioHub
{
MediaElement? _element;
public void Attach(MediaElement element)
=> _element = element;
public void PlayEmbedded(string resourceId)
{
if (_element is null)
return;
MainThread.BeginInvokeOnMainThread(() =>
{
_element.Source = MediaSource.FromResource(resourceId);
_element.Play();
});
}
}
AppShell 側で MediaElement を定義し、AudioHub に渡します。
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="MyApp.AppShell">
<Grid>
<toolkit:MediaElement x:Name="GlobalPlayer"
IsVisible="False"
ShouldAutoPlay="False" />
<!-- ページのコンテンツは別途配置 -->
</Grid>
</Shell>
public partial class AppShell : Shell
{
public AppShell(IAudioHub audioHub)
{
InitializeComponent();
audioHub.Attach(GlobalPlayer);
}
}
これで、任意のサービスや ViewModel から次のように呼び出せます。
public class TimerService
{
readonly IAudioHub _audioHub;
public TimerService(IAudioHub audioHub)
{
_audioHub = audioHub;
}
public void Notify()
{
// EmbeddedResource の完全名
const string ResourceId = "MyApp.Resources.Sounds.onStart.mp3";
_audioHub.PlayEmbedded(ResourceId);
}
}
このパターンのポイントは、MediaElement インスタンスが常に UI スレッド上・視覚ツリー上に存在していることです。サービスは単に「再生したい音源の ID を渡すだけ」にしておくと、ライフサイクルの管理がシンプルになります。
バックグラウンドで鳴らしたい場合の現実的なアプローチ
「アプリが完全にバックグラウンドにあっても音が鳴ってほしい」という要件になると、OS レベルの制約と戦う必要があります。
- iOS:
Info.plistで Background Modes(Audio) を有効にし、AVAudioSessionを適切に設定する - Android:フォアグラウンドサービスと通知を組み合わせた実装が基本
ただし、シンプルな「期限前に効果音を鳴らしたい」程度であれば、ローカル通知(スケジュール通知)にカスタムサウンドを紐付ける構成の方が堅牢です。OS の通知機能が音の再生まで面倒を見てくれるため、省電力モードやアプリの停止の影響を受けにくくなります。
iOS で WAV が不安定 / 再生できない問題
課題:iOS だけ WAV が鳴らない、端末によって挙動が違う
Android では WAV が普通に再生できるのに、iOS ではまったく鳴らなかったり、一部の端末だけ鳴らないといった報告もよくあります。ネット上では「iOS は WAV をサポートしていない」という記述を見かけることもありますが、実際にはもう少し複雑です。
解決策:通知音や効果音は MP3 / AAC に統一する
iOS 自体はローカルファイルとしての Linear PCM WAV を再生できますが、次のようなケースで不安定になることがあります。
- ストリーミング再生(URL が WAV のとき)
- ライブラリや MediaElement の実装が特定フォーマットに最適化されている場合
- サンプリングレートやチャンネル数が特殊なファイル
そのため、互換性とファイルサイズのバランスを考えると、MP3 か AAC(.m4aなど) に統一するのが無難です。特に効果音や短い通知音は、次のようなパラメータに揃えておくとトラブルが少なくなります。
- フォーマット:MP3 または AAC
- サンプリングレート:44.1kHz
- チャンネル:モノラル(1ch)
既存の WAV をそのまま使う場合も、サンプリングレートやチャンネル数が極端でないかを一度確認すると安心です。
.NET 9 + Android で MediaElement が AbstractMethodError でクラッシュする
課題:.NET 9 / MAUI で MediaElement を使うと Android だけ落ちる
.NET 9 + .NET MAUI の環境で、CommunityToolkit.Maui.MediaElement を利用したときに、Android で次のような AbstractMethodError が発生するという報告があります。
androidx.media3.common.Player$Listener.onSurfaceSizeChanged(int, int)
java.lang.AbstractMethodError: abstract method "void ..."
このエラーは、MediaElement が内部で利用している AndroidX Media3 ライブラリとのインターフェースが食い違っているときに発生する典型的なパターンです。
解決策:Toolkit / 依存関係のバージョンを揃える or .NET 8 に固定
対処の優先順位は次のようになります。
- CommunityToolkit.Maui および CommunityToolkit.Maui.MediaElement を最新版に更新
- プロジェクト側で AndroidX Media3 のバージョンを明示的に上書きしている場合は、一旦それを外す
- どうしても解消しない場合は、.NET 8 で固定するか、音声だけなら Plugin.Maui.Audio に切り替える
特に、MediaElement で動画再生まで必要なければ、音声再生は Plugin.Maui.Audio に寄せてしまうと依存関係がシンプルになり、トラブルも減ります。
トラブルシューティングのチェックリスト
ここまでの内容を「課題と解決策」という軸で一覧表にまとめると、次のようになります。
| 課題 | 主な原因 | 実用的な解決策 |
|---|---|---|
| FileNotFoundException で音が鳴らない | フォルダ名の誤り / Build Action ミス / ファイル名不一致 | Resources/Raw + MauiAsset + パスなしファイル名に統一し、クリーンビルド |
| MediaElement を UI に出さずに鳴らしたい | MediaElement が視覚ツリーに存在していない | ページや AppShell に非表示の MediaElement を配置し、コードから再生制御する |
| 別ページやサービスから音を鳴らしたい | MediaElement への参照が分散 / UI スレッド外操作 | ルートに常駐 MediaElement を 1 個だけ置き、DI などで参照を共有する |
| iOS で WAV が鳴らない / 不安定 | フォーマット・サンプリングレート・ストリーミング制限 | 通知音・効果音は MP3 / AAC、44.1kHz モノラルなどに変換してから利用 |
| .NET 9 + Android で MediaElement がクラッシュ | AndroidX Media3 と Toolkit のバージョン不整合 | Toolkit を最新版に更新し、依存を揃える。最悪は .NET 8 固定 or Plugin.Maui.Audio に移行 |
目的別:Plugin.Maui.Audio と MediaElement の使い分け
「そもそもどちらを選べばいいのか?」という観点で、用途別のおすすめ構成を整理します。
| 用途 | おすすめ | 理由・ポイント |
|---|---|---|
| 短い効果音・通知音をアプリ前面で鳴らす | Plugin.Maui.Audio (MauiAsset + Resources/Raw) | 軽量でシンプル。UI 不要。 効果音用の関数を 1 つ用意しておけばどこからでも呼べる。 |
| 動画再生やストリーミングも扱いたい | CommunityToolkit.Maui.MediaElement | 再生 UI やシークバーなどもまとめて扱える。非表示の常駐プレイヤーとしても利用可能。 |
| 期限前に自動で通知音(バックグラウンド含む) | ローカル通知 + カスタムサウンド | OS 標準機能を使うことで、省電力モードやアプリ終了の影響を受けにくい。 |
| .NET 9 で MediaElement が不安定 | Toolkit 更新 / 依存見直し それでもダメなら .NET 8 固定 or Plugin.Maui.Audio に寄せる | 動画が不要なら MediaElement 自体を外してしまうと依存関係がシンプルになる。 |
最低限動くサンプルコード
Plugin.Maui.Audio:最短の効果音再生
効果音だけ鳴らしたい場合の、もっともシンプルなコードです。
// ファイル: Resources/Raw/myaudio.mp3 (Build Action: MauiAsset)
using Plugin.Maui.Audio;
public class SoundService
{
readonly IAudioManager _audioManager;
public SoundService(IAudioManager audioManager)
{
_audioManager = audioManager;
}
public async Task PlayBeepAsync()
{
using var stream = await FileSystem.OpenAppPackageFileAsync("myaudio.mp3");
using var player = _audioManager.CreatePlayer(stream);
player.Play();
}
}
DI を使わない場合は AudioManager.Current をそのまま利用しても構いませんが、テストしやすさや拡張性を考えると、IAudioManager をコンストラクタで受け取る形にしておくと後々楽になります。
MediaElement + EmbeddedResource:グローバルプレイヤー構成
埋め込みリソースを使用する MediaElement のパターンです。
<!-- AppShell.xaml -->
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="MyApp.AppShell">
<Grid>
<toolkit:MediaElement x:Name="GlobalPlayer"
IsVisible="False"
ShouldAutoPlay="False" />
</Grid>
</Shell>
// AppShell.xaml.cs
public partial class AppShell : Shell
{
public AppShell(IAudioHub audioHub)
{
InitializeComponent();
// MediaElement を AudioHub に登録
audioHub.Attach(GlobalPlayer);
}
}
// どこからでも利用できる再生 API
public class NotificationService
{
readonly IAudioHub _audioHub;
public NotificationService(IAudioHub audioHub)
{
_audioHub = audioHub;
}
public void PlayStartSound()
{
const string ResourceId = "MyApp.Resources.Sounds.onStart.mp3";
_audioHub.PlayEmbedded(ResourceId);
}
}
MediaElement を利用する場合は、問題が起きたときのために MediaOpened と MediaFailed イベントをハンドルしてログを出しておくと、原因特定がかなり楽になります。
GlobalPlayer.MediaOpened += (_, __) =>
{
Debug.WriteLine("Media opened.");
};
GlobalPlayer.MediaFailed += (_, e) =>
{
Debug.WriteLine($"Media failed: {e?.ErrorMessage}");
};
まとめ:音声再生でハマらないための指針
最後に、.NET MAUI で音声再生を扱うときに意識しておくと良いポイントを、改めて箇条書きでまとめます。
- まずは「どこに」「どの Build Action で」置くかを決める
— 効果音ならResources/Raw+MauiAsset、MediaElement ならEmbeddedResourceなど。 - MediaElement は UI 要素であることを忘れない
— UI に表示しなくても良いが、視覚ツリーのどこかに非表示で載せておく必要がある。 - 共通のサウンドは「ハブ」を用意して一本化
— AppShell / MainPage にプレイヤーを置き、サービスや ViewModel からはハブ経由で制御すると設計が安定する。 - 確実なバックグラウンド通知音はローカル通知を優先
— アプリ内の再生にこだわるより、OS が面倒を見てくれる仕組みを使う方が結果的にユーザー体験が安定する。 - iOS の WAV は用途によっては相性が悪い
— 迷ったら MP3 / AAC に統一し、44.1kHz・モノラルの効果音にする。 - .NET 9 で MediaElement 周りが不安定なら、無理に最新を追わない選択肢もある
— Toolkit の更新と依存を揃えても直らない場合は .NET 8 に固定し、音声だけなら Plugin.Maui.Audio に寄せる。
音声再生は「動くときは何も考えなくてよい」が、「一度ハマると抜け出しにくい」領域でもあります。この記事で紹介した「課題と解決策」のパターンを押さえておけば、原因の切り分けが一気に楽になるはずです。ぜひ自分のプロジェクトに合った構成を選び、安定した音声体験を実装してみてください。

コメント