Aplikacje mobilne · Multimedia

Multimedia w .NET MAUI: odtwarzanie dźwięku

Odtwarzacz muzyki, nauka wymowy, gra z efektami dźwiękowymi: wszystkie potrzebują dźwięku. W przeciwieństwie do WPF (gdzie jest MediaElement) MAUI nie ma wbudowanej kontrolki audio. Użyjesz darmowego pakietu Plugin.Maui.Audio, który działa w pełni offline.

  • około 45 minut
  • .NET MAUI · Plugin.Maui.Audio · MauiAsset
  • INF.04.6.2

Po tej lekcji: dodać plik dźwiękowy do projektu (Resources/Raw, MauiAsset), zarejestrować AudioManager i odtworzyć dźwięk w kodzie, tworząc odtwarzacz tylko raz.

Odtwarzacz z regulacją głośności

Przycisk Play nie wystarczy: trzeba otworzyć zasób, utworzyć odtwarzacz i zwolnić go po opuszczeniu strony. Zrobimy prosty panel bez pozostawiania dźwięku w tle.

Najpierw uruchomisz mały ekran. Potem połączysz go z kodem i sprawdzisz konkretne wyniki. Dodatki wykonuj dopiero, gdy wersja podstawowa działa. Quiz i oceniane zadania pozostają w biurku po zalogowaniu.

Laboratorium · ćwiczenie w przeglądarce, do którego odsyłają rozgrzewki na końcu lekcji

2 · Sprawdź w działaniu

Zepsuj każdy krok po kolei

Odtwórz dźwięk przy poprawnych ustawieniach (usłyszysz cichy ton). Potem wyłączaj kolejne wymagania: rejestrację AudioManager, MauiAsset, wielkość liter w nazwie na Androidzie i sprawdzanie _player == null. Przeczytaj komunikaty.

Najpierw przewidź, potem sprawdź: W modelu audio porównaj Play, Stop i zmianę głośności. To model sterowania; prawdziwą obsługę formatu i urządzenia sprawdzisz w uruchomionej aplikacji.

Ta interakcja wymaga włączonego JavaScriptu.

Najczęstsze błędy to brak rejestracji AudioManager, zły Build Action i wielkie litery w nazwie pliku (psują tylko Androida). Model pomaga zobaczyć mechanizm; właściwą aplikację uruchomisz z plików poniżej.

1. Przygotuj miejsce pracy

Co dodajesz, a co podmieniasz?

Stronę dodaj przez Projekt → Dodaj → Nowy element → .NET MAUI ContentPage (XAML). Powstaje para .xaml i .xaml.cs. Dla ContentView wybierz szablon ContentView; dla modeli i serwisów zwykłą klasę. Jeśli plik już istnieje, podmień wskazaną zawartość — nie twórz drugiej klasy o tej samej nazwie.

PlikRola w ćwiczeniu
MauiProgram.csLogika i obsługa danych
MainPage.xamlWygląd strony lub kontrolki
MainPage.xaml.csLogika i obsługa danych
projekt.csproj (fragment szablonu)Fragment w istniejącym pliku
App.xaml.csLogika i obsługa danych

Zanim uruchomisz: trzy połączenia

x:Class="MojaAplikacja.NazwaStrony" w XAML musi zgadzać się z namespace MojaAplikacja i nazwą klasy C#. InitializeComponent() tworzy kontrolki opisane w XAML.

x:Name="PoleImie" daje kodowi C# dostęp przez PoleImie.Text. Z kolei Clicked="Zapisz_Clicked" wskazuje metodę o tej nazwie w pliku .xaml.cs. Sam napis przycisku w Text niczego nie podpina.

Gdy dodajesz XAML z obsługą zdarzeń, od razu uzupełnij też odpowiadający C#. Uruchamiaj dopiero po skończeniu oznaczonej pary plików; brakujący handler nie jest błędem środowiska.

2. Uruchom sam wygląd — jeszcze bez logiki

W świeżym projekcie zastąp MainPage.xaml poniższym ekranem. W MainPage.xaml.cs zachowaj konstruktor z InitializeComponent(); usuń szablonowy licznik i OnCounterClicked, bo nie ma ich już w XAML. Na tym etapie App i AppShell pozostają z szablonu. To pełny, tymczasowy XAML — później świadomie go podmienisz, nie dokleisz do drugiego ContentPage.

MainPage.xaml — etap tymczasowyxaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.MainPage">
    <VerticalStackLayout Padding="24" Spacing="14">
        <Label Text="Mój odtwarzacz" FontSize="24" />
        <Button Text="Play — podłączymy za chwilę" />
        <Slider Minimum="0" Maximum="1" Value="0.5" />
    </VerticalStackLayout>
</ContentPage>
  1. Pierwsza próba Uruchom. Zobaczysz kontrolki, ale przyciski bez Clicked jeszcze nic nie robią. Sprawdź rozmiar i kolejność elementów. Zatrzymaj aplikację przed następnym etapem.

Zacznijmy od wymowy

Nagranie, które gra bez internetu

Aplikacja do nauki języka odtwarza nagranie wymowy po kliknięciu „Odsłuchaj”, a prosta gra efekt dźwiękowy po trafieniu. W obu przypadkach plik audio jest dołączony do aplikacji i działa całkowicie offline.

Temat rzadziej spotykany na egzaminie niż formularze czy listy, ale wymieniony w podstawie programowej obok grafiki i animacji.

1 · Zanim zagra pierwszy dźwięk

Lista kontrolna odtwarzania audio

Pominięcie któregokolwiek kroku kończy się ciszą albo błędem. Zaznaczaj kroki w trakcie.

4. Połącz wygląd z działaniem

Teraz zbuduj docelowy przykład. Pełne pliki poniżej zastępują swoje wcześniejsze wersje. Przeczytaj opis nad kodem i objaśnienia pod nim; potem sprawdź przepływ danych, nie tylko brak czerwonych podkreśleń.

Gdy przykład ma kilka plików zależnych od siebie, najpierw dodaj wszystkie wskazane typy, a potem uruchom całość. Ustaw breakpoint w obsłudze głównego przycisku i prześledź jedną próbę.

Budowa krok po kroku

MauiProgram.cs

Instalację Plugin.Maui.Audio wykonaj przed tym krokiem. AddSingleton udostępnia menedżera audio, a AddTransient pozwala DI utworzyć MainPage z argumentem konstruktora.

Rejestrujemy IAudioManager i MainPage. Kontener DI tworzy stronę, a nie ręczne new MainPage() bez argumentów. Pakiet Plugin.Maui.Audio dodaj przez NuGet (ten przykład kontrolnie kompilowano z wersją4.0.0).

MauiProgram.cscsharp
using Microsoft.Extensions.Logging;
using Plugin.Maui.Audio;

namespace MojaAplikacja;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

        // Bez tej linii dźwięk się nie uruchomi
        builder.Services.AddSingleton<IAudioManager>(AudioManager.Current);
        builder.Services.AddTransient<MainPage>();

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}
  1. using Plugin.Maui.Audio; Przestrzeń nazw pakietu.
  2. builder.Services.AddSingleton(AudioManager.Current) Rejestruje jedną, wspólną instancję menedżera. Bez tej linii dźwięk się nie uruchomi.

Budowa krok po kroku

MainPage.xaml

Dodaj lub otwórz MainPage.xaml i podmień jego zawartość. Najpierw odszukaj kontener, potem nazwane kontrolki oraz wskazane zdarzenia. Pozostaw zgodne x:Class i nazwę klasy w pliku .xaml.cs.

Dwa przyciski i suwak głośności (zakres 0 do 1, taki jak właściwość Volume).

MainPage.xamlxaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.MainPage">
    <VerticalStackLayout Padding="30" Spacing="20">
        <Label Text="Odtwarzanie dźwięku" FontSize="22" HorizontalOptions="Center" />
        <Button Text="Odtwórz dźwięk" Clicked="Play_Clicked" />
        <Button Text="Zatrzymaj" Clicked="Stop_Clicked" />
        <Slider x:Name="SuwakGlosnosci" Minimum="0" Maximum="1" Value="0.5"
                ValueChanged="SuwakGlosnosci_ValueChanged" />
    <Button Text="Ekran próbny — sprawdź powrót" Clicked="OtworzProbe_Clicked" />
    </VerticalStackLayout>
</ContentPage>
  1. Przeczytaj powiązanie x:Name identyfikuje kontrolkę, a Binding odczytuje właściwość obiektu ustawionego jako BindingContext. To dwa różne mechanizmy.

Budowa krok po kroku

MainPage.xaml.cs

Najpierw otwieramy plik, potem tworzymy player i uruchamiamy Play. ?. pomija wywołanie przy null. Dispose zwalnia zasób, więc po powrocie tworzymy player ponownie.

Audio i strumień zwalniamy po opuszczeniu strony. Flaga chroni przed podwójnym rozpoczęciem wczytywania, a numer wersji widoku nie pozwala uruchomić odtwarzania, jeśli uczeń zdążył opuścić stronę podczas await. Plik Resources/Raw/audio.mp3 musi istnieć; to nie URL do filmu.

MainPage.xaml.cscsharp
using Plugin.Maui.Audio;
namespace MojaAplikacja;
public partial class MainPage : ContentPage
{
    private readonly IAudioManager _audioManager;
    private IAudioPlayer? _player;
    private Stream? _strumien;
    private bool _wczytywanie;
    private int _wersjaWidoku;

    public MainPage(IAudioManager audioManager)
    {
        InitializeComponent();
        _audioManager = audioManager;
    }
    private async void Play_Clicked(object sender, EventArgs e)
    {
        if (_wczytywanie) return;
        _wczytywanie = true;
        int wersja = _wersjaWidoku;
        try
        {
            if (_player is null)
            {
                Stream otwarty = await FileSystem.OpenAppPackageFileAsync("audio.mp3");
                if (wersja != _wersjaWidoku) { otwarty.Dispose(); return; }
                _strumien = otwarty;
                _player = _audioManager.CreatePlayer(_strumien);
            }
            _player.Volume = SuwakGlosnosci.Value;
            _player.Play();
        }
        catch (Exception)
        {
            ZwolnijAudio();
            await DisplayAlertAsync("Dźwięk", "Sprawdź plik audio.mp3 i jego akcję MauiAsset.", "OK");
        }
        finally { _wczytywanie = false; }
    }
    private void Stop_Clicked(object sender, EventArgs e) => _player?.Stop();
    private void SuwakGlosnosci_ValueChanged(object sender, ValueChangedEventArgs e)
    { if (_player is not null) _player.Volume = e.NewValue; }
    private void ZwolnijAudio()
    {
        _player?.Stop();
        _player?.Dispose();
        _player = null;
        _strumien?.Dispose();
        _strumien = null;
    }
    protected override void OnDisappearing()
    {
        _wersjaWidoku++;
        ZwolnijAudio();
        base.OnDisappearing();
    }
    private async void OtworzProbe_Clicked(object sender, EventArgs e)
    {
        await Navigation.PushAsync(new ContentPage
        {
            Title = "Ekran próbny",
            Content = new Label { Text = "Wróć strzałką wstecz.", Margin = 24 }
        });
    }
}
  1. IAudioManager audioManager Zależność przekazana przez konstruktor. Kontener znajduje ją dzięki rejestracji w MauiProgram.
  2. Dispose() Zwalnia zasoby playera i strumienia; Stop samo w sobie nie zwalnia całego odtwarzacza.
  3. _wersjaWidoku Prosty znacznik: gdy strona zniknie podczas wczytywania, rezultat jest odrzucany. To nie pełny system anulowania operacji.

Budowa krok po kroku

Dołączanie plików z Resources/Raw

Najpierw sprawdź istniejący wpis MauiAsset w projekcie. Jeśli szablon ma ten glob, nie dodawaj go drugi raz. Plik audio.mp3 umieść w Resources/Raw.

Szablon MAUI już zawiera ten wpis. Dlatego każdy plik wrzucony do Resources/Raw dostaje Build Action = MauiAsset i trafia do pakietu aplikacji.

projekt.csproj (fragment szablonu)xml
<ItemGroup>
  <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

Budowa krok po kroku

Strona startowa otrzymana przez DI

App też dostaje MainPage przez konstruktor. Kontener DI tworzy zależności; new MainPage() nie pasuje do konstruktora wymagającego IAudioManager.

Zachowaj App.xaml z szablonu. AppShell nie jest w tej wersji stroną startową.

App.xaml.cscsharp
namespace MojaAplikacja;
public partial class App : Application
{
    private readonly MainPage _strona;
    public App(MainPage strona) { InitializeComponent(); _strona = strona; }
    protected override Window CreateWindow(IActivationState? activationState)
        => new Window(new NavigationPage(_strona));
}

5. Odbiór: czy naprawdę działa?

Wykonaj próby w swojej aplikacji, nie tylko w modelu na stronie. Zaznaczaj dopiero po sprawdzeniu wyniku. Funkcje urządzenia wymagają testu na właściwej platformie.

7 · Czego unikać

Trzy błędy z dźwiękiem

Brak rejestracji `AudioManager`
Tak nie
// w MauiProgram.cs brak: builder.Services.AddSingleton(AudioManager.Current);

Wyjątek albo ciche niedziałanie. To najczęstszy błąd.

Tak
builder.Services.AddSingleton(AudioManager.Current);

Pakiet wymaga rejestracji w kontenerze usług.

Zły Build Action
Tak nie
Plik w Resources/Raw bez MauiAsset

OpenAppPackageFileAsync nie znajdzie pliku w gotowej aplikacji, choć widać go w Eksploratorze rozwiązań.

Tak
Build Action: MauiAsset

Tylko zasoby MauiAsset trafiają do pakietu.

Wielkie litery w nazwie pliku
Tak nie
// w projekcie: Audio.mp3
await FileSystem.OpenAppPackageFileAsync("audio.mp3");

Błąd „plik nie znaleziony” tylko na Androidzie. Na Windows może zadziałać, co mąci diagnozę.

Tak
Nazwa małymi literami i identyczna w kodzie: audio.mp3

Nazwy zasobów na Androidzie rozróżniają wielkość liter.

Ćwiczenie z pomocą · samodzielna praca

6. Mała zmiana, którą zrobisz samodzielnie

Dodaj przycisk Pauza, który pozwoli później wznowić odtwarzanie.

Oczekiwany rezultat
Dodaj przycisk Pauza, który pozwoli później wznowić odtwarzanie.
Dotychczasowe działanie ma pozostać poprawne.
Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź

Dodaj Button i handler.

Krok 2 — pokaż podpowiedź

Wywołaj Pause na istniejącym _player tylko wtedy, gdy nie jest null.

Krok 3 — pokaż podpowiedź

Porównaj Pause i Stop: po pauzie Play ma kontynuować, a nie otwierać nowy plik.

Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.

8 · Sprawdź się

Dlaczego dźwięk nie gra?

Cztery sytuacje.

Ta interakcja wymaga włączonego JavaScriptu.

Podsumowanie

Co warto zapamiętać

  1. MAUI nie ma wbudowanej kontrolki audio, więc używasz pakietu **Plugin.Maui.Audio** (offline).
  2. Plik w **Resources/Raw**, Build Action = MauiAsset, nazwa małymi literami.
  3. Obowiązkowa rejestracja: builder.Services.AddSingleton(AudioManager.Current).
  4. Odtwarzanie: OpenAppPackageFileAsync → CreatePlayer(stream) → Play(). Odtwarzacz twórz raz.
  5. Volume ma zakres 0 do 1. _player?.Stop() chroni przed null.

Dalej na swoim koncie

Rozgrzewki, quiz i zadania są w biurku ucznia.

Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.

Zaloguj się →
Źródła i dalsza nauka