一、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浮动透明层
<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 代码
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代码
<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代码
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;}});});
}
}