MediaElement in .NET MAUI 10: Video and Audio Playback for iOS and Android (2026)

A working developer guide to CommunityToolkit MediaElement in .NET MAUI 10: install, XAML setup, HLS/DASH streaming, background audio, PiP, and fixes for the errors you will actually hit.

Updated: August 31, 2026

To play video or audio in .NET MAUI 10, install the CommunityToolkit.Maui.MediaElement NuGet package (v6.x, released July 2026), call .UseMauiCommunityToolkitMediaElement() in MauiProgram.cs, and drop a <toolkit:MediaElement Source="..."> into your XAML. Under the hood it wraps AVPlayer on iOS/macOS and ExoPlayer (Media3) on Android and Windows, so a single control gives you MP4, HLS, DASH, and progressive audio streaming with lock-screen controls, background playback, and picture-in-picture support.

  • MediaElement ships in CommunityToolkit.Maui.MediaElement 6.x (July 2026). It is NOT part of the base CommunityToolkit.Maui package because of its ExoPlayer footprint.
  • On iOS/macOS it uses AVPlayer. On Android it uses androidx.media3.exoplayer (Media3 1.4). On Windows it uses MediaPlayerElement.
  • HLS (.m3u8) and DASH (.mpd) streaming work out of the box on Android and Windows. iOS supports HLS natively but not progressive DASH.
  • Background audio requires the audio UIBackgroundMode on iOS and a foreground MediaSessionService on Android 14+. MediaElement wires the plumbing but you must opt in.
  • Picture-in-picture is a one-property toggle (ShouldShowPlaybackControls plus platform manifest entries), but it requires Android 8+ and iOS 14+.
  • MediaElement does not ship DRM (FairPlay/Widevine) support, so you'll need a native handler for premium video.

What is MediaElement in .NET MAUI 10?

MediaElement is a cross-platform view in the .NET MAUI Community Toolkit that renders video and audio content from a URL, a local file, or an embedded resource. It's the spiritual successor to the Xamarin.Forms MediaElement that was deprecated back in 2020, and it replaces the tangle of platform renderers (AVPlayerViewController on iOS, VideoView + ExoPlayer on Android) that MAUI developers used to hand-roll. The control shipped as a preview in Toolkit 5.0 in 2023, went stable in 6.0 in early 2025, and the current 6.4 release (July 2026) targets .NET 10 and Media3 1.4.

Because AVPlayer, ExoPlayer, and Windows MediaPlayerElement disagree on almost every API surface, MediaElement's job is to normalize the property model (Source, Position, Duration, Volume, ShouldAutoPlay, ShouldShowPlaybackControls, ShouldLoopPlayback, and Speed) while still exposing a Handler so you can reach the native player when you need something the toolkit doesn't surface (custom track selection, ABR bitrate caps, DRM). Everything else, from HLS live streams and MP3 audio to MP4 progressive video, background playback, lock-screen artwork, and PiP, works through the shared API.

Practically, MediaElement lives inside CommunityToolkit.Maui.MediaElement as a separate NuGet package. That's intentional. The ExoPlayer AAR alone is ~7 MB, and teams that only need buttons and behaviors from the main Toolkit shouldn't pay that cost. If you've used the CommunityToolkit's bottom sheets or modal popups, this is the same team and the same code style.

How do I install and configure MediaElement?

Add the package to your MAUI project and register it in your MauiAppBuilder. The steps below assume a .NET 10 MAUI project targeting net10.0-ios, net10.0-maccatalyst, net10.0-android, and net10.0-windows10.0.19041.0.

dotnet add package CommunityToolkit.Maui.MediaElement --version 6.4.0

Then wire it up in MauiProgram.cs. The .UseMauiCommunityToolkitMediaElement() initializer registers the handler, the Android MediaSessionService, and the iOS audio session category. Skipping this step is the #1 cause of "MediaElement renders as an empty box" bug reports on GitHub. (I've hit that one myself. Twice.)

// MauiProgram.cs
using CommunityToolkit.Maui;

public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();

    builder
        .UseMauiApp<App>()
        .UseMauiCommunityToolkit()
        .UseMauiCommunityToolkitMediaElement()      // required
        .ConfigureFonts(fonts =>
        {
            fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
        });

    return builder.Build();
}

Platform manifest entries

Each platform needs a small manifest change so the OS grants network access, background audio, and PiP permission.

iOS (Platforms/iOS/Info.plist): add background audio and, if you stream from HTTP hosts, an ATS exception. Never disable ATS globally; scope it to the streaming host.

<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>media.example.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key><true/>
        </dict>
    </dict>
</dict>

Android (Platforms/Android/AndroidManifest.xml): MediaElement's foreground service declaration must be present, or Android 14+ will kill playback the moment your app backgrounds:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application ...>
    <service android:name="communityToolkit.maui.media.services"
             android:foregroundServiceType="mediaPlayback"
             android:exported="false">
        <intent-filter>
            <action android:name="androidx.media3.session.MediaSessionService" />
        </intent-filter>
    </service>
</application>

How do I play a video in .NET MAUI 10?

The simplest MediaElement declaration is three lines of XAML. Add the toolkit namespace to your ContentPage and point Source at any absolute URL. MediaElement infers the container (MP4/MKV/WEBM), the transport (HTTP progressive, HLS, DASH), and the codec automatically.

<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.PlayerPage">

    <Grid Padding="12" RowDefinitions="*,Auto,Auto">

        <toolkit:MediaElement x:Name="Media"
                              Grid.Row="0"
                              Source="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
                              ShouldShowPlaybackControls="True"
                              ShouldAutoPlay="True"
                              Aspect="AspectFit" />

        <Label Grid.Row="1"
               Text="{Binding Position, Source={x:Reference Media}, StringFormat='Position: {0}'}"
               Margin="0,8,0,0" />

        <HorizontalStackLayout Grid.Row="2" Spacing="8" Margin="0,8,0,0">
            <Button Text="Play"  Clicked="OnPlayClicked"  />
            <Button Text="Pause" Clicked="OnPauseClicked" />
            <Button Text="Stop"  Clicked="OnStopClicked"  />
        </HorizontalStackLayout>

    </Grid>

</ContentPage>
// PlayerPage.xaml.cs
public partial class PlayerPage : ContentPage
{
    public PlayerPage() => InitializeComponent();

    void OnPlayClicked(object? sender, EventArgs e)  => Media.Play();
    void OnPauseClicked(object? sender, EventArgs e) => Media.Pause();
    void OnStopClicked(object? sender, EventArgs e)  => Media.Stop();

    protected override void OnDisappearing()
    {
        Media.Stop();          // release the decoder before the page dies
        base.OnDisappearing();
    }
}

Local files and bundled assets

For content that ships with the app, place the file in Resources/Raw/ with Build Action = MauiAsset and load it through MediaSource.FromResource(...). For files the user recorded (via the MediaPicker or FilePicker), pass the returned path to MediaSource.FromFile(...).

// bundled asset (Resources/Raw/intro.mp4, MauiAsset)
Media.Source = MediaSource.FromResource("intro.mp4");

// user-picked file
var file = await FilePicker.PickAsync(new PickOptions { FileTypes = FilePickerFileType.Videos });
if (file is not null)
    Media.Source = MediaSource.FromFile(file.FullPath);

Streaming HLS and DASH in MediaElement

HLS (HTTP Live Streaming) uses .m3u8 playlists that reference short TS or fMP4 segments. It's the default for Apple platforms and works pretty much everywhere. DASH (Dynamic Adaptive Streaming over HTTP) uses .mpd manifests and is common in Android and Web pipelines. MediaElement forwards the URL to the underlying player and lets it pick the best variant based on measured bandwidth.

// Apple's public HLS test stream, works on iOS, Android, and Windows
Media.Source = "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8";

// MPEG-DASH, works on Android/Windows via ExoPlayer/MediaPlayerElement
Media.Source = "https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd";

Here's how each platform behaves in practice, so you can plan encoding once:

FeatureiOS / macOS (AVPlayer)Android (ExoPlayer / Media3)Windows (MediaPlayerElement)
MP4 progressiveYesYesYes
HLS (.m3u8)Native, first-classYesYes
DASH (.mpd)No (Apple never shipped it)YesYes
Adaptive bitrateAutomaticAutomaticAutomatic
Codec supportH.264, HEVC, AV1 (A17+)H.264, HEVC, VP9, AV1H.264, HEVC (with codec pack)
Live streamsYes, DVR window respectedYesYes
DRM (FairPlay/Widevine)Requires custom handlerRequires custom handlerRequires custom handler
Subtitles (WebVTT/CEA-608)AutoAutoAuto

If you own the encoding pipeline and only ship to mobile, produce a single HLS ladder with H.264 baseline for the lowest rung and HEVC (hvc1) for the higher rungs. HEVC roughly halves bandwidth vs H.264 at similar quality, and every Apple device since 2017 (plus every Android device since API 24) decodes it in hardware. Check the Apple HLS Authoring Specification for the current ladder recommendation. The Community Toolkit repository also links to sample encoders that produce Media3-friendly outputs.

How to play audio in the background in .NET MAUI

Both iOS and Android require an explicit background mode. MediaElement handles the plumbing, but you have to declare intent. iOS uses the audio value in UIBackgroundModes plus an active AVAudioSession with the Playback category. Android 14+ requires a foreground service with type mediaPlayback, which MediaElement registers as MediaSessionService from androidx.media3.

With the manifest entries from the setup section, background playback "just works". Pressing the home button or locking the screen will keep audio going and show controls on the lock screen, in Control Center on iOS, and in the media notification on Android. You can go further by attaching now-playing metadata so the lock screen shows the track title, artist, and artwork.

// audio-only source with metadata
Media.Source = "https://ice1.somafm.com/groovesalad-128-mp3";
Media.MetadataTitle    = "Groove Salad";
Media.MetadataArtist   = "SomaFM";
Media.MetadataArtworkUrl = "https://somafm.com/logos/512/groovesalad512.png";
Media.ShouldShowPlaybackControls = true;   // required for lock-screen controls on iOS

Media.Play();

On Android 13+, remember to request POST_NOTIFICATIONS before starting playback, or the notification silently fails to appear. If you need audio that continues while another feature is running (voice recording, workout tracking), pair MediaElement with a proper foreground service so both stay alive under Doze.

Picture-in-picture and full-screen playback

Picture-in-picture (PiP) lets a small video window persist over other apps. As of Toolkit 6.4, MediaElement exposes it through the built-in playback controls on iOS 14+ and Android 8+ (API 26+). No new C# code required. Just declare intent on each platform.

On iOS, PiP requires the same audio background mode you already added for background audio. On Android, add android:supportsPictureInPicture="true" and android:resizeableActivity="true" to the <activity> in AndroidManifest.xml, and declare that your activity handles the relevant configuration changes so the OS doesn't restart it when entering PiP.

<activity android:name="crc64XXXXXXXX.MainActivity"
          android:supportsPictureInPicture="true"
          android:resizeableActivity="true"
          android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|uiMode"
          android:launchMode="singleTop" />

Full-screen playback is even simpler. MediaElement listens for the double-tap and rotation gestures on its built-in controls, and there's a full-screen button in the default control overlay. If you build a custom overlay, call Media.FullScreen = true to enter and Media.FullScreen = false to exit. Fullscreen respects the parent page's Shell.NavBarIsVisible and Shell.TabBarIsVisible, so hide both for an immersive experience.

Events, commands, and playback control

MediaElement raises a small, well-defined set of events that are trivial to bind from MVVM. In an MVVM app, prefer ObservableProperty for the source URL and RelayCommand for play/pause. The MVVM Community Toolkit reduces this to a few attributes.

// PlayerViewModel.cs
public partial class PlayerViewModel : ObservableObject
{
    [ObservableProperty]
    private MediaSource? source;

    [ObservableProperty]
    private MediaElementState currentState;

    [RelayCommand]
    void Load(string url) => Source = MediaSource.FromUri(url);

    // subscribe to Media.StateChanged from code-behind and set CurrentState
}

The events you'll use most often:

  • MediaOpened: fires when the source is loaded and Duration is known. Enable seek controls here, not before.
  • StateChanged: covers every transition between Opening, Buffering, Playing, Paused, and Stopped. Use this to drive spinner UI.
  • PositionChanged: throttled to ~4Hz. Don't rebind ProgressBar directly to Position; bind to this event's payload instead so the UI thread isn't hammered.
  • MediaEnded: fires at end of stream. Combine with ShouldLoopPlayback for looping video backgrounds.
  • MediaFailed: receives an ErrorMessage. Log the message; on Android it's the ExoPlayer PlaybackException string, which is precise (network vs. codec vs. DRM).

Common MediaElement errors and fixes

Even with correct setup, cross-platform playback trips over a handful of predictable issues. Honestly, I've bumped into every one of these on real projects, so treat this as a checklist.

"MediaElement renders as an empty box"

You forgot .UseMauiCommunityToolkitMediaElement() in MauiProgram.cs, or you added the base CommunityToolkit.Maui package but not the separate CommunityToolkit.Maui.MediaElement package. Verify with dotnet list package | grep MediaElement.

Video plays on Windows but not iOS from an HTTP URL

iOS App Transport Security blocks cleartext HTTP by default. Either switch the origin to HTTPS (correct) or add an NSExceptionDomains entry scoped to the specific host (last resort).

Background audio stops when the screen locks on Android 14

Missing FOREGROUND_SERVICE_MEDIA_PLAYBACK permission or missing <service> registration in the manifest. Both are required as of Android 14 (API 34). Grep your AndroidManifest.xml for both strings.

"UnsupportedOperationException: Codec not found"

Android device lacks a hardware decoder for HEVC or AV1. Fall back to H.264 for compatibility, or check MediaCodecList at runtime and pick a URL variant that matches. This is common on entry-level Android Go devices sold in emerging markets.

Live HLS drifts behind or stalls

ExoPlayer's default live-window target is 3 segments behind the head. If you author 6-second segments, that's ~18 s of latency. Configure a shorter target via the native player handle, or reduce your segment duration to 2 s. Check the ExoPlayer HLS documentation for the current recommended tuning.

App crashes on second Play after navigating away

MediaElement's underlying player was disposed, but a queued PositionChanged event fired on the dead handler. Always call Media.Stop() in OnDisappearing. See the earlier warning about MediaCodec leaks. This class of bug is one of the top hits in our guide to finding and fixing memory leaks in .NET MAUI.

DRM-protected content refuses to play

MediaElement doesn't ship FairPlay or Widevine glue. For premium streaming (Netflix-class content), you must implement a native handler that supplies the license URL and content protection metadata. The tracking issue on the CommunityToolkit repo has the current status and recipes contributed by teams that have shipped DRM in production.

Frequently Asked Questions

What replaced MediaElement from Xamarin.Forms in .NET MAUI?

The .NET MAUI Community Toolkit's MediaElement (in the CommunityToolkit.Maui.MediaElement NuGet package) is the direct replacement. It's a separate package from the base Community Toolkit because it pulls in the Media3/ExoPlayer AAR on Android.

Can I use MediaElement to stream from YouTube?

No. YouTube URLs are HTML player pages, not direct video URLs, and scraping them violates YouTube's Terms of Service. Use the official YouTube IFrame Player API inside a WebView, or embed via the YouTube Data API using a signed player.

Does MediaElement support DRM-protected video?

Not out of the box. MediaElement does not include FairPlay (iOS) or Widevine (Android) license acquisition. For DRM you need a custom handler that talks to a license server; the CommunityToolkit repo has an open issue tracking the recipes contributed by teams shipping it in production.

How do I add subtitles or closed captions?

Embed WebVTT tracks in your HLS or DASH manifest and MediaElement will surface them automatically through the platform player's built-in caption picker. For side-loaded SRT files you need to drop to the native handler and add the subtitle URL as a separate track.

Is MediaElement production-ready in 2026?

Yes. It went stable in Community Toolkit 6.0 in early 2025, has shipped in dozens of production apps, and gets updates roughly every six weeks. It's the recommended path for standard streaming. Only reach for a fully custom AVPlayer/ExoPlayer implementation if you need DRM, precise ABR control, or specialty features like 360°/VR video.

Editorial Team
About the Author Editorial Team

Our team of expert writers and editors.