.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;
            }
        });
    });
}
}
相关推荐
代码拾光19 小时前
如何在 ASP.NET Core 中实现速率限制?
.net core
代码拾光2 天前
.NET Core 委托原理解析
.net core
代码拾光4 天前
中间件 vs 过滤器
.net core
代码拾光5 天前
了解 ASP.NET Core 中的中间件
.net core
petunsecn5 天前
EFCore HasDefaultValueSql
c#·.net core
代码拾光6 天前
.NetCore依赖注入(DI)之生命周期
.net core
Damon小智6 天前
C#进阶-在Ubuntu上部署ASP.NET Core Web API应用
linux·nginx·c#·asp.net·.net·.net core
代码拾光7 天前
.NET Core:架构、特性和优势详解
.net core
Agile.Zhou10 天前
在 Development 环境下依赖注入的行为可能有所不同
.net core