C# Avalonia没有内置的MediaElement,我们用LibVLC实现一个就行了。Avalonia官方实现的,要收费。配置文章里更新了。https://www.cnblogs.com/dalgleish/p/18967204
MediaElement类,已完善功能。之后会有专门一章来测试这个播放器代码,现在可以简单使用播放功能。
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Threading;
using LibVLCSharp.Avalonia;
using LibVLCSharp.Shared;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Shares.Avalonia.CustomControls
{
public class MediaElement : UserControl, IDisposable
{
private static LibVLC? libVLC;
private VideoView? videoView;
private MediaPlayer? mediaPlayer;
private Media? currentMedia;
private EventHandler<MediaParsedChangedEventArgs>? parsedChangedHandler;
private Stream? currentStream;
private StreamMediaInput? currentMediaInput;
private bool isDisposed;
private bool isStopping;
private bool updatingTimeFromPlayer;
private bool updatingMuteFromPlayer;
private DispatcherTimer? seekDebounceTimer;
private long pendingSeekTime;
private bool hasPendingSeek;
private bool isScrubbing;
private bool stretchPending;
private CancellationTokenSource? sourceCts;
private bool isVideoDetached;
private EventHandler<EventArgs>? pausedHandler;
private EventHandler<EventArgs>? stoppedHandler;
private EventHandler<EventArgs>? playingHandler;
private EventHandler<MediaPlayerVolumeChangedEventArgs>? volumeChangedHandler;
private EventHandler<MediaPlayerTimeChangedEventArgs>? timeChangedHandler;
private EventHandler<MediaPlayerLengthChangedEventArgs>? lengthChangedHandler;
private EventHandler<EventArgs>? mutedHandler;
private EventHandler<EventArgs>? unmutedHandler;
private EventHandler<MediaPlayerBufferingEventArgs>? bufferingHandler;
private EventHandler<EventArgs>? endReachedHandler;
public event EventHandler? Paused;
public event EventHandler? Stopped;
public event EventHandler? Playing;
public event EventHandler? VolumeChanged;
public event EventHandler? TimeChanged;
public event EventHandler? LengthChanged;
public event EventHandler? Muted;
public event EventHandler? Unmuted;
public event EventHandler? Buffering;
public event EventHandler? MediaEnded;
public static readonly StyledProperty<Uri> SourceProperty =
AvaloniaProperty.Register<MediaElement, Uri>(nameof(Source));
public Uri Source
{
get => GetValue(SourceProperty);
set => SetValue(SourceProperty, value);
}
public static readonly StyledProperty<uint> VideoWidthProperty =
AvaloniaProperty.Register<MediaElement, uint>(nameof(VideoWidth));
public uint VideoWidth
{
get => GetValue(VideoWidthProperty);
private set => SetValue(VideoWidthProperty, value);
}
public static readonly StyledProperty<uint> VideoHeightProperty =
AvaloniaProperty.Register<MediaElement, uint>(nameof(VideoHeight));
public uint VideoHeight
{
get => GetValue(VideoHeightProperty);
private set => SetValue(VideoHeightProperty, value);
}
public static readonly StyledProperty<bool> AutoPlayProperty =
AvaloniaProperty.Register<MediaElement, bool>(nameof(AutoPlay), false);
public bool AutoPlay
{
get => GetValue(AutoPlayProperty);
set => SetValue(AutoPlayProperty, value);
}
public static readonly StyledProperty<int> VolumeProperty =
AvaloniaProperty.Register<MediaElement, int>(nameof(Volume), 100);
public int Volume
{
get => GetValue(VolumeProperty);
set => SetValue(VolumeProperty, value);
}
public static readonly StyledProperty<bool> IsMutedProperty =
AvaloniaProperty.Register<MediaElement, bool>(nameof(IsMuted), false);
public bool IsMuted
{
get => GetValue(IsMutedProperty);
set => SetValue(IsMutedProperty, value);
}
public static readonly StyledProperty<long> TimeProperty =
AvaloniaProperty.Register<MediaElement, long>(nameof(Time), 0L);
public long Time
{
get => GetValue(TimeProperty);
set => SetValue(TimeProperty, value);
}
public static readonly StyledProperty<long> LengthProperty =
AvaloniaProperty.Register<MediaElement, long>(nameof(Length), 0L);
public long Length
{
get => GetValue(LengthProperty);
private set => SetValue(LengthProperty, value);
}
public static readonly StyledProperty<float> RateProperty =
AvaloniaProperty.Register<MediaElement, float>(nameof(Rate), 1.0f);
public float Rate
{
get => GetValue(RateProperty);
set => SetValue(RateProperty, value);
}
public static readonly StyledProperty<int> SpuProperty =
AvaloniaProperty.Register<MediaElement, int>(nameof(Spu), -1);
public int Spu
{
get => GetValue(SpuProperty);
set => SetValue(SpuProperty, value);
}
public static readonly StyledProperty<Stretch> StretchProperty =
AvaloniaProperty.Register<MediaElement, Stretch>(nameof(Stretch), Stretch.Fill);
public Stretch Stretch
{
get => GetValue(StretchProperty);
set => SetValue(StretchProperty, value);
}
public static readonly StyledProperty<string> AspectRatioProperty =
AvaloniaProperty.Register<MediaElement, string>(nameof(AspectRatio));
public string AspectRatio
{
get => GetValue(AspectRatioProperty);
set => SetValue(AspectRatioProperty, value);
}
public static readonly StyledProperty<double> BufferingProgressProperty =
AvaloniaProperty.Register<MediaElement, double>(nameof(BufferingProgress), 0.0);
public double BufferingProgress
{
get => GetValue(BufferingProgressProperty);
private set => SetValue(BufferingProgressProperty, value);
}
static MediaElement()
{
if (Design.IsDesignMode)
return;
try
{
Core.Initialize(null);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
libVLC = new LibVLC("--directx-use-sysmem", "--network-caching=2000", "--no-plugins-cache");
else
libVLC = new LibVLC("--network-caching=2000", "--no-plugins-cache");
}
catch (Exception ex)
{
Console.WriteLine("[MediaElement] LibVLC init failed: " + ex);
}
}
public MediaElement()
{
if (libVLC is null)
{
Content = null;
return;
}
seekDebounceTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(120)
};
seekDebounceTimer.Tick += (s, e) => FlushPendingSeek();
mediaPlayer = new MediaPlayer(libVLC);
videoView = new VideoView { MediaPlayer = mediaPlayer };
Content = videoView;
HookPropertyObservers();
HookPlayerEvents(mediaPlayer);
ApplyAllToPlayer();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DetachVideoViewEarly();
base.OnDetachedFromVisualTree(e);
}
private void HookPropertyObservers()
{
this.GetObservable(SourceProperty).Subscribe(uri => _ = OnSourceChangedAsync(uri));
this.GetObservable(RateProperty).Subscribe(r =>
{
if (isDisposed)
return;
Try(() => mediaPlayer?.SetRate(r));
});
this.GetObservable(SpuProperty).Subscribe(s =>
{
if (isDisposed)
return;
Try(() => mediaPlayer?.SetSpu(s));
});
this.GetObservable(VolumeProperty).Subscribe(v =>
{
if (isDisposed || mediaPlayer is null)
return;
var clamped = Math.Clamp(v, 0, 100);
Try(() =>
{
if (mediaPlayer.Volume != clamped)
mediaPlayer.Volume = clamped;
});
});
this.GetObservable(IsMutedProperty).Subscribe(m =>
{
if (isDisposed || mediaPlayer is null || updatingMuteFromPlayer)
return;
Try(() =>
{
if (mediaPlayer.Mute != m)
mediaPlayer.Mute = m;
});
});
this.GetObservable(TimeProperty).Subscribe(t =>
{
if (isDisposed || mediaPlayer is null || updatingTimeFromPlayer)
return;
pendingSeekTime = Math.Max(0, t);
hasPendingSeek = true;
isScrubbing = true;
seekDebounceTimer?.Stop();
seekDebounceTimer?.Start();
});
this.GetObservable(BoundsProperty).Subscribe(_ => ScheduleApplyStretch());
}
private void HookPlayerEvents(MediaPlayer player)
{
pausedHandler = (s, e) => UI(() => Paused?.Invoke(this, EventArgs.Empty));
stoppedHandler = (s, e) => UI(() =>
{
ApplyAudioToPlayer();
isStopping = false;
Stopped?.Invoke(this, EventArgs.Empty);
});
playingHandler = (s, e) => UI(() =>
{
isStopping = false;
ApplyAudioToPlayer();
Playing?.Invoke(this, EventArgs.Empty);
});
volumeChangedHandler = (s, e) => UI(() =>
{
if (isStopping)
return;
VolumeChanged?.Invoke(this, EventArgs.Empty);
});
mutedHandler = (s, e) => UI(() =>
{
updatingMuteFromPlayer = true;
SetCurrentValue(IsMutedProperty, true);
updatingMuteFromPlayer = false;
Muted?.Invoke(this, EventArgs.Empty);
});
unmutedHandler = (s, e) => UI(() =>
{
updatingMuteFromPlayer = true;
SetCurrentValue(IsMutedProperty, false);
updatingMuteFromPlayer = false;
Unmuted?.Invoke(this, EventArgs.Empty);
});
timeChangedHandler = (s, e) => UI(() =>
{
if (mediaPlayer is null)
return;
if (isScrubbing)
{
TimeChanged?.Invoke(this, EventArgs.Empty);
return;
}
updatingTimeFromPlayer = true;
SetCurrentValue(TimeProperty, mediaPlayer.Time);
updatingTimeFromPlayer = false;
TimeChanged?.Invoke(this, EventArgs.Empty);
});
lengthChangedHandler = (s, e) => UI(() =>
{
if (mediaPlayer is null)
return;
Length = mediaPlayer.Length;
LengthChanged?.Invoke(this, EventArgs.Empty);
});
bufferingHandler = (s, e) => UI(() =>
{
BufferingProgress = e.Cache / 100.0;
Buffering?.Invoke(this, EventArgs.Empty);
});
endReachedHandler = (s, e) => UI(() =>
{
if (mediaPlayer is null)
return;
Try(() =>
{
isStopping = true;
mediaPlayer.Stop();
mediaPlayer.Time = 0;
updatingTimeFromPlayer = true;
SetCurrentValue(TimeProperty, 0L);
updatingTimeFromPlayer = false;
ApplyAudioToPlayer();
});
MediaEnded?.Invoke(this, EventArgs.Empty);
});
player.Paused += pausedHandler;
player.Stopped += stoppedHandler;
player.Playing += playingHandler;
player.VolumeChanged += volumeChangedHandler;
player.Muted += mutedHandler;
player.Unmuted += unmutedHandler;
player.TimeChanged += timeChangedHandler;
player.LengthChanged += lengthChangedHandler;
player.Buffering += bufferingHandler;
player.EndReached += endReachedHandler;
}
private void UnhookPlayerEvents(MediaPlayer player)
{
if (pausedHandler is not null) player.Paused -= pausedHandler;
if (stoppedHandler is not null) player.Stopped -= stoppedHandler;
if (playingHandler is not null) player.Playing -= playingHandler;
if (volumeChangedHandler is not null) player.VolumeChanged -= volumeChangedHandler;
if (mutedHandler is not null) player.Muted -= mutedHandler;
if (unmutedHandler is not null) player.Unmuted -= unmutedHandler;
if (timeChangedHandler is not null) player.TimeChanged -= timeChangedHandler;
if (lengthChangedHandler is not null) player.LengthChanged -= lengthChangedHandler;
if (bufferingHandler is not null) player.Buffering -= bufferingHandler;
if (endReachedHandler is not null) player.EndReached -= endReachedHandler;
}
private void FlushPendingSeek()
{
seekDebounceTimer?.Stop();
if (isDisposed || mediaPlayer is null)
{
hasPendingSeek = false;
isScrubbing = false;
return;
}
if (!hasPendingSeek)
{
isScrubbing = false;
return;
}
hasPendingSeek = false;
Try(() => mediaPlayer.Time = pendingSeekTime);
Dispatcher.UIThread.Post(() =>
{
if (!isDisposed)
isScrubbing = false;
}, DispatcherPriority.Background);
}
public void Seek(TimeSpan timeSpan)
{
if (mediaPlayer is null || isDisposed)
return;
var ms = (long)Math.Max(0, timeSpan.TotalMilliseconds);
pendingSeekTime = ms;
hasPendingSeek = true;
isScrubbing = true;
seekDebounceTimer?.Stop();
seekDebounceTimer?.Start();
updatingTimeFromPlayer = true;
SetCurrentValue(TimeProperty, ms);
updatingTimeFromPlayer = false;
}
public void SeekToPercentage(double percentage)
{
if (mediaPlayer is null || isDisposed)
return;
var len = mediaPlayer.Length;
if (percentage > 100 || (len <= 0 && percentage > 1))
{
var ms = (long)Math.Max(0, percentage);
pendingSeekTime = ms;
hasPendingSeek = true;
isScrubbing = true;
seekDebounceTimer?.Stop();
seekDebounceTimer?.Start();
updatingTimeFromPlayer = true;
SetCurrentValue(TimeProperty, ms);
updatingTimeFromPlayer = false;
return;
}
var p = percentage;
if (p > 1)
p /= 100.0;
p = Math.Clamp(p, 0, 1);
Try(() => mediaPlayer.Position = (float)p);
if (len > 0)
{
var ms = (long)(len * p);
updatingTimeFromPlayer = true;
SetCurrentValue(TimeProperty, ms);
updatingTimeFromPlayer = false;
}
}
public bool IsPlaying => mediaPlayer?.IsPlaying ?? false;
public void Play() => mediaPlayer?.Play();
public Task PlayAsync() => Dispatcher.UIThread.InvokeAsync(() => mediaPlayer?.Play()).GetTask();
public bool CanPlay() => !(mediaPlayer?.IsPlaying ?? false);
public void Stop()
{
if (mediaPlayer is null || isDisposed)
return;
isStopping = true;
Try(() => mediaPlayer.Stop());
UI(ApplyAudioToPlayer);
}
public bool CanStop() => mediaPlayer?.IsPlaying ?? false;
public void Pause() => mediaPlayer?.Pause();
public bool CanPause() => mediaPlayer?.IsPlaying ?? false;
public void TogglePlayPause()
{
if (mediaPlayer?.IsPlaying == true)
mediaPlayer.Pause();
else
mediaPlayer?.Play();
}
public void Mute() => IsMuted = true;
public bool CanMute() => !IsMuted;
public void Unmute() => IsMuted = false;
public bool CanUnmute() => IsMuted;
public void ToggleMute() => IsMuted = !IsMuted;
public void ToggleCloseCaption()
{
if (Spu != -1)
Spu = -1;
else
Spu = 0;
}
public bool IsSeekable => mediaPlayer?.IsSeekable ?? false;
public void Download()
{
if (Source?.Scheme != "avares")
Process.Start(new ProcessStartInfo { FileName = mediaPlayer?.Media?.Mrl, UseShellExecute = true });
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
isVideoDetached = false;
ApplyAllToPlayer();
if (AutoPlay)
mediaPlayer?.Play();
}
private async Task OnSourceChangedAsync(Uri uri)
{
if (isDisposed || libVLC is null || mediaPlayer is null)
return;
if (uri is null)
return;
var old = Interlocked.Exchange(ref sourceCts, new CancellationTokenSource());
Try(() =>
{
old?.Cancel();
old?.Dispose();
});
var token = sourceCts!.Token;
try
{
UI(() =>
{
Length = 0;
updatingTimeFromPlayer = true;
SetCurrentValue(TimeProperty, 0L);
updatingTimeFromPlayer = false;
pendingSeekTime = 0;
hasPendingSeek = false;
isScrubbing = false;
seekDebounceTimer?.Stop();
});
CleanupCurrentMedia();
token.ThrowIfCancellationRequested();
Media media;
if (uri.Scheme == "avares")
{
// 把资源变成“稳定可 seek + 有长度”的 MemoryStream
using var src = AssetLoader.Open(uri);
var ms = new MemoryStream();
src.CopyTo(ms);
ms.Position = 0;
currentStream = ms;
currentMediaInput = new StreamMediaInput(currentStream);
media = new Media(libVLC, currentMediaInput);
}
else
{
media = new Media(libVLC, uri);
}
// 明确告诉 VLC 不要做各种缓存(否则 stream input 很容易落盘到 temp)
ConfigureMediaOptions(media);
if (token.IsCancellationRequested || isDisposed || mediaPlayer is null)
{
Try(() => media.Dispose());
return;
}
mediaPlayer.Media = media;
currentMedia = media;
parsedChangedHandler = (s, e) =>
{
UI(() =>
{
if (isDisposed)
return;
foreach (var track in media.Tracks)
{
if (track.TrackType == TrackType.Video)
{
var videoTrack = track.Data.Video;
VideoWidth = videoTrack.Width;
VideoHeight = videoTrack.Height;
ScheduleApplyStretch();
break;
}
}
});
};
media.ParsedChanged += parsedChangedHandler;
// Parse 可选:为了拿 track 信息;取消会提前退出
try
{
await Task.Run(() =>
{
if (token.IsCancellationRequested)
return;
try { media.Parse(MediaParseOptions.ParseNetwork); }
catch
{
try { media.Parse(); } catch { }
}
}, token);
}
catch (OperationCanceledException)
{
return;
}
catch
{
}
if (token.IsCancellationRequested || isDisposed || mediaPlayer is null)
return;
ApplyAllToPlayer();
if (AutoPlay && VisualRoot is not null && !isDisposed)
mediaPlayer.Play();
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
Console.WriteLine("[MediaElement] 警告:媒体播放失败:" + ex.Message, ex);
}
}
// “禁止大量临时文件”的核心
private static void ConfigureMediaOptions(Media media)
{
// 目标:不要为了 demux/seek 去做落盘缓存
media.AddOption(":file-caching=0");
media.AddOption(":network-caching=2000");
media.AddOption(":live-caching=0");
media.AddOption(":disc-caching=0");
media.AddOption(":sout-mux-caching=0");
media.AddOption(":no-sub-autodetect-file");
media.AddOption(":no-video-title-show");
}
private void CleanupCurrentMedia()
{
if (currentMedia is not null && parsedChangedHandler is not null)
Try(() => currentMedia.ParsedChanged -= parsedChangedHandler);
parsedChangedHandler = null;
Try(() => currentMedia?.Dispose());
currentMedia = null;
Try(() => mediaPlayer?.Media?.Dispose());
if (mediaPlayer is not null)
mediaPlayer.Media = null;
Try(() => currentMediaInput?.Dispose());
currentMediaInput = null;
Try(() => currentStream?.Dispose());
currentStream = null;
}
private void ApplyAudioToPlayer()
{
if (mediaPlayer is null || isDisposed)
return;
Try(() =>
{
mediaPlayer.Volume = Math.Clamp(Volume, 0, 100);
mediaPlayer.Mute = IsMuted;
});
}
private void ApplyAllToPlayer()
{
if (mediaPlayer is null || isDisposed)
return;
Try(() =>
{
ApplyAudioToPlayer();
mediaPlayer.SetRate(Rate);
mediaPlayer.SetSpu(Spu);
if (!string.IsNullOrWhiteSpace(AspectRatio))
mediaPlayer.AspectRatio = AspectRatio;
if (Time > 0)
mediaPlayer.Time = Math.Max(0, Time);
});
}
private void ScheduleApplyStretch()
{
if (stretchPending)
return;
stretchPending = true;
Dispatcher.UIThread.Post(() =>
{
stretchPending = false;
if (isDisposed || videoView is null || mediaPlayer is null)
return;
if (VideoWidth <= 0 || VideoHeight <= 0)
{
videoView.Width = double.NaN;
videoView.Height = double.NaN;
videoView.HorizontalAlignment = HorizontalAlignment.Stretch;
videoView.VerticalAlignment = VerticalAlignment.Stretch;
return;
}
var availableWidth = Bounds.Width;
var availableHeight = Bounds.Height;
if (availableWidth <= 0 || availableHeight <= 0)
return;
var aspectVideo = (double)VideoWidth / VideoHeight;
var aspectContainer = availableWidth / availableHeight;
switch (Stretch)
{
case Stretch.None:
AspectRatio = $"{VideoWidth}:{VideoHeight}";
videoView.Width = VideoWidth;
videoView.Height = VideoHeight;
videoView.HorizontalAlignment = HorizontalAlignment.Left;
videoView.VerticalAlignment = VerticalAlignment.Top;
break;
case Stretch.Fill:
AspectRatio = $"{availableWidth}:{availableHeight}";
videoView.Width = availableWidth;
videoView.Height = availableHeight;
videoView.HorizontalAlignment = HorizontalAlignment.Stretch;
videoView.VerticalAlignment = VerticalAlignment.Stretch;
break;
case Stretch.Uniform:
AspectRatio = $"{VideoWidth}:{VideoHeight}";
if (aspectContainer > aspectVideo)
{
videoView.Height = availableHeight;
videoView.Width = availableHeight * aspectVideo;
}
else
{
videoView.Width = availableWidth;
videoView.Height = availableWidth / aspectVideo;
}
videoView.HorizontalAlignment = HorizontalAlignment.Center;
videoView.VerticalAlignment = VerticalAlignment.Center;
break;
case Stretch.UniformToFill:
if (aspectContainer > aspectVideo)
{
var height = availableWidth / aspectVideo;
AspectRatio = $"{availableWidth}:{height}";
videoView.Width = availableWidth;
videoView.Height = height;
}
else
{
var width = availableHeight * aspectVideo;
AspectRatio = $"{width}:{availableHeight}";
videoView.Height = availableHeight;
videoView.Width = width;
}
videoView.HorizontalAlignment = HorizontalAlignment.Center;
videoView.VerticalAlignment = VerticalAlignment.Center;
break;
}
Try(() =>
{
if (!string.IsNullOrWhiteSpace(AspectRatio))
mediaPlayer.AspectRatio = AspectRatio;
});
});
}
private void DetachVideoViewEarly()
{
if (isVideoDetached)
return;
isVideoDetached = true;
if (videoView is null)
return;
if (Dispatcher.UIThread.CheckAccess())
{
videoView.MediaPlayer = null;
return;
}
Dispatcher.UIThread.Post(() =>
{
if (isDisposed || videoView is null)
return;
videoView.MediaPlayer = null;
}, DispatcherPriority.Send);
}
private void UI(Action action)
{
if (isDisposed)
return;
if (Dispatcher.UIThread.CheckAccess())
{
action();
return;
}
Dispatcher.UIThread.Post(() =>
{
if (!isDisposed)
action();
});
}
private static void Try(Action action)
{
try { action(); } catch { }
}
public void Dispose()
{
if (isDisposed)
return;
isDisposed = true;
Try(() =>
{
sourceCts?.Cancel();
sourceCts?.Dispose();
});
sourceCts = null;
Try(() => seekDebounceTimer?.Stop());
DetachVideoViewEarly();
if (mediaPlayer is not null)
Try(() => UnhookPlayerEvents(mediaPlayer));
CleanupCurrentMedia();
Try(() => mediaPlayer?.Stop());
Try(() => mediaPlayer?.Dispose());
mediaPlayer = null;
videoView = null;
}
}
}
AssemblyResources.axaml代码
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="300" Width="300" x:Class="AvaloniaUI.AssemblyResources" Title="AssemblyResources"> <!--https://github.com/videolan/libvlcsharp/tree/3.x--> <Grid RowDefinitions="*,auto"> <MediaElement Name="Sound" Source="avares://AvaloniaUI/Resources/Sounds/金刚经.mp4"/> <StackPanel Grid.Row="1"> <Button Name="Play" Click="cmdPlay_Click" Content="Play"> <Button.Background> <ImageBrush Source="avares://AvaloniaUI/Resources/Images/Blue hills.jpg" Stretch="Fill"/> </Button.Background> </Button> </StackPanel> </Grid> </Window>
AssemblyResources.axaml.cs代码
using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Platform; using Shares.Avalonia; using System; namespace AvaloniaUI; public partial class AssemblyResources : Window { public AssemblyResources() { InitializeComponent(); } private void cmdPlay_Click(object? sender, RoutedEventArgs e) { var uri = new Uri("avares://AvaloniaUI/Resources/Images/Winter.jpg"); using var stream = AssetLoader.Open(uri); Play.Background = new ImageBrush() { Source = new Bitmap(stream), Stretch = Stretch.Fill }; Sound.Play(); } }
运行效果

浙公网安备 33010602011771号