.NET MAUIで音声が再生できない原因と解決策まとめ(MediaElementとPlugin.Maui.Audio対応)

.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/RawResource/Raw や Resources/Sounds など
Build ActionMauiAssetContent, 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主な参照方法向いている用途
MauiAssetResources/RawMauiAssetFileSystem.OpenAppPackageFileAsync("file.mp3")Plugin.Maui.Audio での効果音やBGM
EmbeddedResource任意のフォルダEmbeddedResourceMediaSource.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 を置き、サービスから参照

考え方は次のとおりです。

  1. AppShell や MainPage に、非表示の MediaElement(または Plugin.Maui.Audio 用のハブ)を用意
  2. その参照を DI コンテナ(依存性注入) などで共有する
  3. サービス側からは、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 に固定

対処の優先順位は次のようになります。

  1. CommunityToolkit.Maui および CommunityToolkit.Maui.MediaElement を最新版に更新
  2. プロジェクト側で AndroidX Media3 のバージョンを明示的に上書きしている場合は、一旦それを外す
  3. どうしても解消しない場合は、.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 に寄せる。

音声再生は「動くときは何も考えなくてよい」が、「一度ハマると抜け出しにくい」領域でもあります。この記事で紹介した「課題と解決策」のパターンを押さえておけば、原因の切り分けが一気に楽になるはずです。ぜひ自分のプロジェクトに合った構成を選び、安定した音声体験を実装してみてください。

この記事を書いた人

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

コメント

コメントする

目次