C# Avalonia没有内置的MediaElement,我们用LibVLC实现一个就行了。Avalonia官方实现的,要收费。配置文章里更新了。https://www.cnblogs.com/dalgleish/p/18967204

MediaElement类,实现了播放器大多数功能。

    public class MediaElement : UserControl, IDisposable
    {
        private static LibVLC libVLC;
        private readonly VideoView videoView;
        private readonly MediaPlayer mediaPlayer;
        private uint videoWidth;
        private uint videoHeight;
        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<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<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()
        {
            Core.Initialize();
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                libVLC = new LibVLC("--directx-use-sysmem", "--network-caching=2000");
            else
                libVLC = new LibVLC("--network-caching=2000");
            /*
#if DEBUG
            libVLC.Log += (sender, e) =>
            {
                Console.WriteLine($"[LibVLC 日志][{e.Level}] {e.Module}: {e.Message}");
            };
#endif
            */
        }
        public MediaElement()
        {
            mediaPlayer = new MediaPlayer(libVLC);
            videoView = new VideoView()
            {
                MediaPlayer = mediaPlayer
            };

            this.Content = videoView;
            this.GetObservable(SourceProperty).Subscribe(s => OnSourceChanged(s));
            this.GetObservable(RateProperty).Subscribe(r => mediaPlayer.SetRate(r));
            this.GetObservable(SpuProperty).Subscribe(s => mediaPlayer.SetSpu(s));
            this.GetObservable(BoundsProperty).Subscribe(_ => ScheduleApplyStretch());
            OnMediaPlayerChanged(mediaPlayer);
        }
        public void Seek(TimeSpan timeSpan)
        {
            if (IsSeekable)
            {
                Time = (long)(timeSpan.TotalMilliseconds * 1000); // 转换为微秒
            }
        }

        // 按百分比跳转
        public void SeekToPercentage(double percentage)
        {
            if (IsSeekable &&  mediaPlayer?.Length > 0)
            {
                Time = (long)(mediaPlayer.Length * percentage);
            }
        }
        public bool IsPlaying => mediaPlayer.IsPlaying;

        public void Play() => mediaPlayer.Play();

        public bool CanPlay() => !mediaPlayer.IsPlaying;

        public void Stop() => mediaPlayer.Stop();

        public bool CanStop() => mediaPlayer.IsPlaying;

        public void Pause() => mediaPlayer.Pause();

        public bool CanPause() => mediaPlayer.IsPlaying;

        public void TogglePlayPause()
        {
            if (mediaPlayer.IsPlaying)
            {
                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;
        public long Length => mediaPlayer.Length;

        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);
            if (AutoPlay)
            {
                mediaPlayer.Play();
            }
        }
        private void OnSourceChanged(Uri uri)
        {
            if (uri == null) 
                return;

            try
            {
                Media? media;
                if (uri.Scheme == "avares")
                {
                    var stream = new StreamMediaInput(AssetLoader.Open(uri));
                    media = new Media(libVLC, stream);
                }
                else
                    media = new Media(libVLC, uri);

                mediaPlayer.Media = media;

                media.ParsedChanged += (s, e) =>
                {
                    foreach (var track in media.Tracks)
                    {
                        if (track.TrackType == TrackType.Video)
                        {
                            VideoTrack videoTrack = track.Data.Video;
                            videoWidth = videoTrack.Width;
                            videoHeight = videoTrack.Height;
                            ScheduleApplyStretch();
                            break;
                        }
                    }
                };
                
            }
            catch (Exception ex)
            {
                Console.WriteLine("[MediaElement] 警告:媒体播放失败:" + ex.Message, ex);
            }
        }
        private void OnMediaPlayerChanged(MediaPlayer newPlayer)
        {
            if (newPlayer == null)
            {
                Console.WriteLine("[MediaElement] 警告:newPlayer不能为空.");
                return;
            }
            newPlayer.Paused += (s, e) => Paused?.Invoke(s, e);
            newPlayer.Stopped += (s, e) => Stopped?.Invoke(s, e);
            newPlayer.Playing += (s, e) => Playing?.Invoke(s, e);
            newPlayer.VolumeChanged += (s, e) => VolumeChanged?.Invoke(s, e);
            newPlayer.TimeChanged += (s, e) => TimeChanged?.Invoke(s, e);
            newPlayer.LengthChanged += (s, e) => LengthChanged?.Invoke(s, e);
            newPlayer.Muted += (s, e) => Muted?.Invoke(s, e);
            newPlayer.Unmuted += (s, e) => Unmuted?.Invoke(s, e);
            newPlayer.Buffering += (s, e) =>
            {
                Dispatcher.UIThread.Post(() =>
                {
                    BufferingProgress = e.Cache / 100.0;
                    Buffering?.Invoke(s, e); 
                });
            };
            newPlayer.EndReached += (s, e) =>
            {
                //停止并回到 0
                Dispatcher.UIThread.Post(() =>
                {
                    newPlayer.Stop();
                    newPlayer.Time = 0;
                    MediaEnded?.Invoke(this, EventArgs.Empty);
                });
            };

            this[!!MediaElement.VolumeProperty] = new Binding() { Source = newPlayer, Path = nameof(Volume), Mode = BindingMode.TwoWay };
            this[!!MediaElement.IsMutedProperty] = new Binding() { Source = newPlayer, Path = nameof(Mute), Mode = BindingMode.TwoWay };
            this[!!MediaElement.TimeProperty] = new Binding() { Source = newPlayer, Path = nameof(Time), Mode = BindingMode.TwoWay };
            this[!!MediaElement.AspectRatioProperty] = new Binding() { Source = newPlayer, Path = nameof(AspectRatio), Mode = BindingMode.TwoWay };
        }
        
        private void ScheduleApplyStretch()
        {
            Dispatcher.UIThread.Post(() =>
            {

                if (videoWidth <= 0 || videoHeight <= 0)
                {
                    videoView.Width = double.NaN;
                    videoView.Height = double.NaN;
                    videoView.HorizontalAlignment = HorizontalAlignment.Stretch;
                    videoView.VerticalAlignment = VerticalAlignment.Stretch;
                    return;
                }

                var availableWidth = this.Bounds.Width;
                var availableHeight = this.Bounds.Height;
                if (availableWidth <= 0 || availableHeight <= 0)
                    return;

                double aspectVideo = (double)videoWidth / videoHeight;
                double 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)
                        {
                            AspectRatio = $"{availableWidth}:{(int)availableWidth / aspectVideo}";
                            videoView.Width = availableWidth;
                            videoView.Height = availableWidth / aspectVideo;
                        }
                        else
                        {
                            AspectRatio = $"{(int)availableHeight * aspectVideo}:{availableHeight}";
                            videoView.Height = availableHeight;
                            videoView.Width = availableHeight * aspectVideo;
                        }
                        videoView.HorizontalAlignment = HorizontalAlignment.Center;
                        videoView.VerticalAlignment = VerticalAlignment.Center;
                        break;
                }
            });
        }

        public void Dispose()
        {
            mediaPlayer?.Media?.Dispose();
            mediaPlayer?.Dispose();
        }
    }

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();
    }
}

运行效果

image

 

posted on 2025-07-30 11:03  dalgleish  阅读(48)  评论(0)    收藏  举报