.Net8 Avalonia跨平台UI框架——<vlc:VideoView>控件播放海康监控、摄像机视频(Windows / Linux)

一、UI效果


二、新建用户控件:VideoViewControl.axaml

需引用:VideoLAN.LibVLC.Windows包

Linux平台需安装:VLC 和 LibVLC (sudo apt-get update、sudo apt-get install vlc libvlccore-dev libvlc-dev)

.axaml 代码

注:vlc:VideoView 上无法增加鼠标和指针事件,需使用Popup浮动透明层

csharp 复制代码
<UserControl 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"
			 xmlns:vlc="clr-namespace:LibVLCSharp.Avalonia;assembly=LibVLCSharp.Avalonia"
			 xmlns:vm="using:TrainArrivalAnalysis.Avalonia.ViewModels"
			 xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
			 xmlns:controls="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls"
             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
             x:Class="TrainArrivalAnalysis.Avalonia.Controls.VideoViewControl"
			 >
	<Design.DataContext>
		<vm:VideoWindowViewModel/>
	</Design.DataContext>
		<Grid>

		<vlc:VideoView x:Name="playerView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"  IsHitTestVisible="False" SizeChanged="playerViewSizeChanged" >
		</vlc:VideoView>

		<Popup x:Name="videoViewPopup" Placement="Center" PlacementTarget="{Binding ElementName=playerView}" IsOpen="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">

			<Border Background="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >

				<Grid Background="Transparent" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">

					<TextBlock x:Name="playBackTip" VerticalAlignment="Top" HorizontalAlignment="Right" Background="#F0F8FF" Foreground="Red" Margin="0,50,10,0" Padding="15" FontSize="16" FontWeight="Bold" IsVisible="False" />

					<TextBlock x:Name="connectTip" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Transparent" Foreground="White" FontSize="40" Text="网络中断" IsVisible="False" />

				</Grid>

			</Border>

		</Popup>

	</Grid>

</UserControl>
.axaml.cs 代码
csharp 复制代码
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Extensions.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Avalonia.VisualTree;
using LibVLCSharp.Avalonia;
using LibVLCSharp.Shared;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Net.NetworkInformation;
using System.Threading;
using System.Threading.Tasks;
using TrainArrivalAnalysis.Utility;

namespace TrainArrivalAnalysis.Avalonia.Controls;

public partial class VideoViewControl : UserControl, INotifyPropertyChanged, IDisposable
{
    private bool _disposed = false;
    private LibVLC _libVLC;
    private MediaPlayer _mediaPlayer;
    private string _url;
    public string Url
    {
        get => _url;
        set
        {
            if (_url != value)
            {
                StopPlayback(); // 停止当前播放
                _url = value;
                OnPropertyChanged(nameof(Url));
                if (!string.IsNullOrEmpty(_url))
                {
                    SetMediaPlayerAsync();
                }
            }
        }
    }
    private int _type = 0;
    public int Type
    {
        get => _type;
        set
        {
            _type = value;
            OnPropertyChanged(nameof(Type));
        }
    }
    private bool _isPlayBackTip = false;
    public bool IsPlayBackTip
    {
        get => _isPlayBackTip;
        set
        {
            _isPlayBackTip = value;
            OnPropertyChanged(nameof(IsPlayBackTip));

            Dispatcher.UIThread.InvokeAsync(() =>
            {
                playBackTip.IsVisible = _isPlayBackTip;
            });
        }
    }

    private string _playBackTipContent;
    public string PlayBackTipContent
    {
        get => _playBackTipContent;
        set
        {
            if (_playBackTipContent != value)
            {
                _playBackTipContent = value;
                OnPropertyChanged(nameof(PlayBackTipContent));

                Dispatcher.UIThread.InvokeAsync(() =>
                {
                    playBackTip.Text = _playBackTipContent;
                });
            }
        }
    }

    private bool _isConnectTip = false;
    public bool IsConnectTip
    {
        get => _isConnectTip;
        set
        {
            _isConnectTip = value;
            OnPropertyChanged(nameof(IsConnectTip));

            Dispatcher.UIThread.InvokeAsync(() =>
            {
                connectTip.IsVisible = _isConnectTip;
            });
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private int _maxRetryAttempts = 5;
    private int _retryDelayMilliseconds = 5000;
    private int _currentRetryAttempt = 0;

    public VideoViewControl()
    {
        InitializeComponent();

        var libVlcOptions = new[] { "--no-video-title-show", "--no-osd" };
        _libVLC = new LibVLC(libVlcOptions);

        this.Loaded += async (sender, e) =>
        {
            var videoView = this.FindControl<VideoView>("playerView");
            if (videoView != null)
            {
                videoView.Loaded += async (s, ev) =>
                {
                    if (!string.IsNullOrEmpty(Url))
                    {
                        await SetMediaPlayerAsync();
                    }
                };
            }
        };

        this.Unloaded += (sender, e) =>
        {
            StopPlayback();

            Dispose(false);
        };
    }
    public async Task SetMediaPlayerAsync()
    {
        try
        {
            if (!string.IsNullOrEmpty(Url))
            {
                _mediaPlayer = new MediaPlayer(_libVLC);

                await Dispatcher.UIThread.InvokeAsync(() =>
                {
                    VideoView _videoView = this.FindControl<VideoView>("playerView");
                    if (_videoView != null)
                    {
                        _videoView.MediaPlayer = _mediaPlayer;
                    }
                });
                var mediaOptions = new[] { ":network-caching=300", "avcodec-hw=any" };

                var _media = new Media(_libVLC, Url, FromType.FromLocation, mediaOptions);

                _mediaPlayer.Media = _media;

                _mediaPlayer.EncounteredError += OnEncounteredError;

                await Task.Run(() =>
                {
                    _mediaPlayer.Play();
                });
            }

            _currentRetryAttempt = 0;

            _ = CheckPlayStatus().ConfigureAwait(false);
        }
        catch (Exception ex)
        {
        }
    }
    private async Task CheckPlayStatus()
    {
        try
        {
            await Task.Delay(5000).ConfigureAwait(false);

            while (true)
            {
                if (_mediaPlayer != null && _mediaPlayer.IsPlaying)
                {
                    IsConnectTip = false;
                    if (Type == 1)
                    {
                        IsPlayBackTip = true;
                    }
                    _currentRetryAttempt = 0;
                }
                else if (_mediaPlayer.State == VLCState.Stopped || _mediaPlayer.State == VLCState.Error || (_mediaPlayer.State == VLCState.Ended && !NetworkHelper.IsNetworkConnected()))
                {
                    IsPlayBackTip = false;

                    IsConnectTip = true;

                    StopPlayback();
                }
                else if (_mediaPlayer.State == VLCState.Ended)
                {
                    IsConnectTip = false;
                    if (Type == 1)
                    {
                        IsPlayBackTip = true;
                    }
                }

                await Task.Delay(10000).ConfigureAwait(false);
            }
        }
        catch (OperationCanceledException)
        {

        }
        catch (Exception ex)
        {

        }
    }
    private async void OnEncounteredError(object sender, EventArgs e)
    {
        IsPlayBackTip = false;
        IsConnectTip = true;
    }
    private async void playerViewSizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (videoViewPopup.IsOpen)
        {
            videoViewPopup.Width = playerView.ActualWidth();
            videoViewPopup.Height = playerView.ActualHeight();
        }
    }
    // 停止播放
    private void StopPlayback()
    {
        _mediaPlayer?.Stop();
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                if (_mediaPlayer != null)
                {
                    _mediaPlayer.Stop();
                    _mediaPlayer.Dispose();
                    _mediaPlayer = null;
                }
                if (_libVLC != null)
                {
                    _libVLC.Dispose();
                    _libVLC = null;
                }
            }
            _disposed = true;
        }
    }
}

三、主页面MainWindow.axaml使用

.axaml代码
csharp 复制代码
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		xmlns:animation="clr-namespace:Avalonia.Animation;assembly=Avalonia.Animation"
        xmlns:vm="using:TrainArrivalAnalysis.Avalonia.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
		xmlns:controls="clr-namespace:TrainArrivalAnalysis.Avalonia.Controls"
		xmlns:converters="clr-namespace:TrainArrivalAnalysis.Avalonia.Converters"
		xmlns:md="clr-namespace:TrainArrivalAnalysis.Avalonia.Models.Dto"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="TrainArrivalAnalysis.Avalonia.Views.MainWindow"
        x:DataType="md:ArrivalRecordDto"
        Icon="/Assets/avalonia-logo.ico"
		Title="半自动闭塞区段列车整列到达自动智能分析系统"
		WindowState="FullScreen" SystemDecorations="None"  ZIndex="1">
	<Window.Resources>
		<converters:GridLengthToDoubleConverter x:Key="GridLengthToDoubleConverter"/>
	</Window.Resources>
	<Window.Styles>
		<Style Selector="Button.blueBtn">
			<Setter Property="Foreground" Value="White"/>
			<Setter Property="Background" Value="#4C7DF7"/>
			<Setter Property="Height" Value="32"/>
			<Setter Property="FontSize" Value="16"/>
			<Setter Property="BorderBrush" Value="DarkGray"/>
			<Setter Property="Template">
				<Setter.Value>
					<ControlTemplate TargetType="Button">
						<Border x:Name="containerBorder" BorderBrush="{TemplateBinding BorderBrush}" Background="#4C7DF7" CornerRadius="13">
							<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" ></ContentPresenter>
						</Border>
					</ControlTemplate>
				</Setter.Value>
			</Setter>
		</Style>
	</Window.Styles>

	<Grid  x:Name="RootGrid" RowDefinitions="Auto,*"  SizeChanged="RootGridSizeChanged">
		<Grid x:Name="TopGird"  Grid.Row="0" Height="2" Background="Black" PointerMoved="TopGridPointerMovedHandler"> </Grid>
		<Grid Grid.Row="1"  Background="Black">
			<Grid.ColumnDefinitions>
				<ColumnDefinition Width="*" />
				<ColumnDefinition Width="*" />
			</Grid.ColumnDefinitions>

			<Grid.RowDefinitions>
				<RowDefinition Height="*" />
				<RowDefinition Height="*" />
			</Grid.RowDefinitions>
			
			<controls:VideoViewControl  Grid.Row="0" Grid.Column="0" x:Name="videoView"  DoubleTapped="VideoDoubleTappedHandler" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

			<controls:VideoViewControl  Grid.Row="0" Grid.Column="1" x:Name="videoView1" DoubleTapped="VideoDoubleTappedHandler" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>


			<Image x:Name="imageArriva" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"   Stretch="Fill" DoubleTapped="ImageDoubleTappedHandler"></Image>

			<Image x:Name="imageLeave" Grid.Row="1" Grid.Column="1"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch"   Stretch="Fill" DoubleTapped="ImageDoubleTappedHandler" ></Image>

			<Image x:Name="imageArriva_1" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"   Stretch="Fill" DoubleTapped="ImageDoubleTappedHandler"></Image>

			<Image x:Name="imageLeave_1" Grid.Row="0"  Grid.Column="1"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch"   Stretch="Fill" DoubleTapped="ImageDoubleTappedHandler" ></Image>

		</Grid>

		<Popup x:Name="NavMenuPopup" Placement="Top" PlacementTarget="{Binding ElementName=TopGird}" IsOpen="False" IsVisible="False">

			<Border x:Name="PopupBorder"  Background="#F0F8FF"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch">

				<Grid x:Name="NavMenuGird" Background="Transparent" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*,Auto" MinHeight="30" PointerExited="NavMenuPointerExitedHandler">

					<StackPanel Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left">
						<Button Content="标记为来车信号" Margin="5" Classes="blueBtn" Width="125" x:Name="arrivalSignalButton" Click="ArrivalSignal_Click" DockPanel.Dock="Left" />
						<TextBox x:Name="searchStartTime" Watermark="取图开始日期" Text="" Height="32" />
						<TextBox x:Name="searchEndTime" Watermark="取图结束日期" Text="" Height="32" />
						<Button Content="标记为无车信号" Margin="5" Classes="blueBtn" Width="125" x:Name="leaveSignalButton" Click="LeaveSignal_Click" DockPanel.Dock="Left" />
					</StackPanel>

					<Rectangle Grid.Column="1" Fill="Transparent"/>

					<StackPanel Orientation="Horizontal" Grid.Column="2" HorizontalAlignment="Right">
						<Button Content="最近来车记录" Margin="5" Classes="blueBtn" Width="125" x:Name="showLastArrivalRecordButton" Click="ShowLastArrivalRecordClick" />
						<Button Content="来车记录查询" x:Name="showArrivalRecordButton" Margin="5" Classes="blueBtn" Width="125" Click="ShowArrivalRecordClick" />
						<Button Content="结束查询" x:Name="closeArrivalRecordButton" Margin="5" Classes="blueBtn" Width="125" Click="CloseArrivalRecordClick"/>
						<Button Content="测试下载" x:Name="downLoadButton" Margin="5" Classes="blueBtn" Width="125" Click="DownLoadClick" IsVisible="False" />
						<Button Content="关闭系统" Margin="50,5,5,5" Classes="blueBtn" Width="125" Click="CloseAppClick" />
					</StackPanel>

				</Grid>

			</Border>
		</Popup>

		<Popup x:Name="ArrivalRecordPopup" Placement="Center" PlacementTarget="{Binding ElementName=RootGrid}" IsOpen="False" >
			<Border x:Name="ArrivalRecordPopupBorder"  Background="White"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch">

				<controls:ArrivalRecordViewControl x:Name="arrivalRecordView"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
			</Border>
		</Popup>
	</Grid>
</Window>
.axaml.cs代码
csharp 复制代码
 public partial class MainWindow : Window
 {
 public MainWindow()
{
    InitializeComponent();
  this.Loaded += async (sender, e) =>
  {
      string url = $"rtsp://用户名:密码@IP/LiveMedia/ch1/Media1";
      SetVideoViewUrl("videoView", url, "");

 	  url = $"rtsp://用户名:密码@IP/LiveMedia/ch1/Media1";
  	  SetVideoViewUrl("videoView1", url, "");
  };
 }
 public async void SetVideoViewUrl(string videoViewControlName, string url, string playBackTipContent, int type = 0, bool isPlayBackTip = false)
{
    _ = Task.Run(async () =>
    {
        await Dispatcher.UIThread.InvokeAsync(() =>
        {
            var _videoView = this.FindControl<VideoViewControl>(videoViewControlName);
            if (_videoView != null)
            {
                _videoView.Url = url;
                _videoView.Type = type;
                _videoView.PlayBackTipContent = playBackTipContent;
                _videoView.IsPlayBackTip = isPlayBackTip;
            }
        });
    });
}
}
相关推荐
EdisonZhou3 天前
.NET程序员AI开发基座:Microsoft.Extensions.AI
aigc·.net core
hez20105 天前
用 C# 插值字符串处理器写一个 sscanf
c#·.net·.net core
布布(CeSun)12 天前
.NET适配HarmonyOS进展
.net·鸿蒙系统·harmonyos next·avalonia
时光追逐者15 天前
一款由 .NET 官方团队开源的电子商务系统 - eShop
c#·.net·商城系统·.net core·微软技术
hez201021 天前
Brainfly: 用 C# 类型系统构建 Brainfuck 编译器
c#·.net·aot·.net core·clr·compiler
Ronin-Lotus1 个月前
上位机知识篇---CMake
c语言·c++·笔记·学习·跨平台·编译·cmake
代码拾光1 个月前
.NET Core 中如何构建一个弹性的 HTTP 请求机制?
.net core
天边树若荠1 个月前
(一)afsim第三方库编译
跨平台·麒麟·afsim
代码拾光1 个月前
在 .NET Core中如何使用 Redis 创建分布式锁
.net core