一、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;
}
});
});
}
}