Build a .NET MAUI Video Player in C¶
.NET MAUI lets you ship one C# codebase to iOS, Android, macOS, and Windows. This guide wires up the VisioForge VideoView control with MediaPlayerCoreX to play local files and network streams — a focused starter that mirrors the Simple Player MAUI demo.
Choosing a framework? Avalonia (desktop-first, includes Linux) → Avalonia Player Guide. WinForms / WPF (Windows desktop) → Build a Video Player in C#. Android-only (no MAUI) → Android Player Guide.
AI coding agents: use the VisioForge MCP server
Building this with Claude Code, Cursor, or another AI coding agent? Connect to the public VisioForge MCP server at https://mcp.visioforge.com/mcp for structured API lookups, runnable code samples, and deployment guides — more accurate than grepping llms.txt. No authentication required.
Claude Code: claude mcp add --transport http visioforge-sdk https://mcp.visioforge.com/mcp
Prerequisites¶
- .NET 8 SDK (or newer) with MAUI workload:
dotnet workload install maui - Platform toolchains for the targets you ship to (Xcode for iOS/macCatalyst, Android SDK for Android)
- See the MAUI installation guide for full platform setup details
NuGet Packages¶
<PackageReference Include="VisioForge.DotNet.MediaPlayer" Version="2026.2.19" />
<PackageReference Include="VisioForge.DotNet.Core.UI.MAUI" Version="2026.2.19" />
<!-- Platform redists — include only the targets you build for -->
<PackageReference Include="VisioForge.CrossPlatform.Core.Windows.x64" Version="2025.11.0"
Condition="$(TargetFramework.Contains('windows'))" />
<PackageReference Include="VisioForge.CrossPlatform.Libav.Windows.x64" Version="2025.11.0"
Condition="$(TargetFramework.Contains('windows'))" />
<PackageReference Include="VisioForge.CrossPlatform.Core.macCatalyst" Version="2025.9.1"
Condition="$(TargetFramework.Contains('maccatalyst'))" />
<PackageReference Include="VisioForge.CrossPlatform.Core.Android" Version="15.10.33"
Condition="$(TargetFramework.Contains('android'))" />
<PackageReference Include="VisioForge.CrossPlatform.Core.iOS" Version="2025.0.16"
Condition="$(TargetFramework.Contains('ios'))" />
MauiProgram.cs¶
Register the VisioForge handlers so MAUI can resolve the VideoView control:
using VisioForge.Core.UI.MAUI;
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseSkiaSharp()
.ConfigureMauiHandlers(handlers => handlers.AddVisioForgeHandlers());
return builder.Build();
}
XAML Layout¶
Drop a VideoView into your page alongside a seek slider and playback buttons:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:my="clr-namespace:VisioForge.Core.UI.MAUI;assembly=VisioForge.Core.UI.MAUI"
x:Class="MauiPlayerDemo.MainPage">
<Grid RowDefinitions="*,Auto" RowSpacing="0">
<my:VideoView Grid.Row="0" x:Name="videoView"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
Background="Black" />
<VerticalStackLayout Grid.Row="1" Spacing="4" Padding="12,8">
<Slider x:Name="slSeeking" ValueChanged="slSeeking_ValueChanged" />
<Grid ColumnDefinitions="*,*,*" ColumnSpacing="8">
<Button Grid.Column="0" x:Name="btOpen"
Text="OPEN" Clicked="btOpen_Clicked" />
<Button Grid.Column="1" x:Name="btPlayPause"
Text="PLAY" Clicked="btPlayPause_Clicked" />
<Button Grid.Column="2" x:Name="btStop"
Text="STOP" Clicked="btStop_Clicked" />
</Grid>
<Slider x:Name="slVolume" Minimum="0" Maximum="100" Value="50"
ValueChanged="slVolume_ValueChanged" />
</VerticalStackLayout>
</Grid>
</ContentPage>
Code-Behind: Player Setup¶
VideoView.GetVideoView() returns an IVideoView bridge that MediaPlayerCoreX consumes identically on every MAUI target (WinUI, macCatalyst, AndroidX, UIKit):
using VisioForge.Core;
using VisioForge.Core.MediaPlayerX;
using VisioForge.Core.Types;
using VisioForge.Core.Types.Events;
using VisioForge.Core.Types.X.AudioRenderers;
using VisioForge.Core.Types.X.Sources;
using VisioForge.Core.UI.MAUI;
public partial class MainPage : ContentPage
{
private MediaPlayerCoreX _player;
private System.Timers.Timer _positionTimer = new(500);
private string _filename;
private volatile bool _isTimerUpdate;
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
_positionTimer.Elapsed += PositionTimer_Elapsed;
}
private async void MainPage_Loaded(object sender, EventArgs e)
{
IVideoView vv = videoView.GetVideoView();
_player = new MediaPlayerCoreX(vv);
_player.OnError += (_, args) => Debug.WriteLine(args.Message);
_player.OnStop += Player_OnStop;
// iOS does not enumerate audio output devices — skip the picker there.
#if !__IOS__ || __MACCATALYST__
var outputs = await _player.Audio_OutputDevicesAsync();
if (outputs.Length > 0)
_player.Audio_OutputDevice = new AudioRendererSettings(outputs[0]);
#endif
Window.Destroying += async (_, _) =>
{
if (_player != null)
{
await _player.StopAsync();
await _player.DisposeAsync();
_player = null;
}
VisioForgeX.DestroySDK();
};
}
}
Open a File with FilePicker¶
private async void btOpen_Clicked(object sender, EventArgs e)
{
var result = await FilePicker.Default.PickAsync();
if (result == null) return;
_filename = result.FullPath;
var source = await UniversalSourceSettings.CreateAsync(new Uri(_filename));
await _player.OpenAsync(source);
await _player.PlayAsync();
_positionTimer.Start();
btPlayPause.Text = "PAUSE";
}
You can pass any supported URI (HTTP, HLS, RTSP) to UniversalSourceSettings.CreateAsync — the same code path plays network streams on mobile.
Play / Pause / Stop¶
MediaPlayerCoreX.State exposes the current PlaybackState so one button can cover the full lifecycle:
private async void btPlayPause_Clicked(object sender, EventArgs e)
{
if (_player == null || string.IsNullOrEmpty(_filename)) return;
switch (_player.State)
{
case PlaybackState.Play:
await _player.PauseAsync();
btPlayPause.Text = "PLAY";
break;
case PlaybackState.Pause:
await _player.ResumeAsync();
btPlayPause.Text = "PAUSE";
break;
case PlaybackState.Free:
var source = await UniversalSourceSettings.CreateAsync(new Uri(_filename));
await _player.OpenAsync(source);
await _player.PlayAsync();
_positionTimer.Start();
btPlayPause.Text = "PAUSE";
break;
}
}
private async void btStop_Clicked(object sender, EventArgs e)
{
_positionTimer.Stop();
if (_player != null) await _player.StopAsync();
btPlayPause.Text = "PLAY";
}
Seeking and Volume¶
The seek slider and volume slider both route through the timer flag so timer-driven position updates do not fight user drags:
private async void PositionTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (_player == null) return;
var position = await _player.Position_GetAsync();
var duration = await _player.DurationAsync();
await MainThread.InvokeOnMainThreadAsync(() =>
{
_isTimerUpdate = true;
slSeeking.Maximum = duration.TotalMilliseconds;
slSeeking.Value = Math.Min(position.TotalMilliseconds, slSeeking.Maximum);
_isTimerUpdate = false;
});
}
private async void slSeeking_ValueChanged(object sender, ValueChangedEventArgs e)
{
if (!_isTimerUpdate && _player != null)
await _player.Position_SetAsync(TimeSpan.FromMilliseconds(e.NewValue));
}
private void slVolume_ValueChanged(object sender, ValueChangedEventArgs e)
{
if (_player != null)
_player.Audio_OutputDevice_Volume = e.NewValue / 100.0;
}
private void Player_OnStop(object sender, StopEventArgs e)
=> MainThread.BeginInvokeOnMainThread(() =>
{
btPlayPause.Text = "PLAY";
slSeeking.Value = 0;
});
Platform Notes¶
- iOS — add the App Transport Security exceptions for HTTP (non-HTTPS) streams and the microphone/photo-library usage descriptions you need.
_player.Audio_OutputDevicesAsync()is not available on iOS — the renderer picks the active output automatically. - Android — declare
INTERNETpermission for network streams; for local-file playback on API 33+ you needREAD_MEDIA_VIDEOor a user-grantedFilePickerURI. - macCatalyst — use
VisioForge.CrossPlatform.Core.macCatalyst; hardware decoding runs throughVideoToolbox. - Windows (WinUI) — use the
Windows.x64+Libav.Windows.x64redists; hardware decoding goes throughd3d11va/nvdec/qsvdepending on the GPU.
Full platform-specific wiring (entitlements, manifests, provisioning) lives in the MAUI installation guide.
Supported Formats¶
| Category | Formats |
|---|---|
| Containers | MP4, MOV, MKV, WebM, AVI, TS, FLV |
| Video codecs | H.264, H.265/HEVC, VP8, VP9, AV1, MPEG-2 |
| Audio codecs | AAC, MP3, Opus, Vorbis, FLAC, AC-3 |
| Streaming | HTTP, HLS, RTSP, MPEG-DASH |
Sample Applications¶
- MAUI SimplePlayer (Media Player SDK X) — the full working source behind this tutorial
- MAUI SimplePlayer (Media Blocks SDK) — the same app built on the pipeline API, if you need custom processing
See Also¶
- Avalonia Cross-Platform Player — desktop-first cross-platform (includes Linux)
- Build a Video Player in C# — WinForms/WPF on Windows
- Android Player Guide — Android-specific setup and deployment
- Media Blocks Pipeline Player — block-based alternative with explicit video/audio renderers
- MAUI Installation Guide — platform entitlements, workloads, and redist packaging