You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

LibVLCSharp VideoView在AvaloniaUI中超出圆角按钮边界的问题及解决方案咨询

LibVLCSharp VideoView在AvaloniaUI中超出圆角按钮边界的问题及解决方案咨询

兄弟,你这个问题我太熟了!之前做Avalonia多视频墙的时候踩过一模一样的坑,LibVLCSharp的VideoView因为是原生窗口渲染,完全不吃Avalonia的裁剪规则,下面给你捋捋问题根源和可行的解决办法:

问题根源

本质原因就是VideoView不是Avalonia渲染管线的一部分——它会在Avalonia窗口内部创建一个独立的Windows原生子窗口来播放视频,这个原生窗口不受Avalonia的ClipToBoundsCornerRadius这类布局约束的控制,所以会直接突破圆角边框,和用Avalonia原生渲染的Image控件行为天差地别。

我的环境(和你完全匹配)

  • AvaloniaUI 11.3.2
  • LibVLCSharp.Avalonia 3.9.3
  • Windows 10

我之前踩过的坑(和你尝试的方法一样)

  • 给父Border加ClipToBounds="True"——完全无效
  • 换各种布局容器、调对齐方式——没用
  • 试RenderTransform变形——对原生窗口根本起不了作用

你的布局代码(格式化后)

<Viewbox Grid.Row="0" Stretch="Uniform" Margin="0,80,0,10">
    <ItemsControl ItemsSource="{Binding Channels}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Rows="3" Columns="4" Width="1950" Height="780" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Button Name="ChannelButton" Command="{Binding LaunchCommand}" ToolTip.Placement="Bottom">
                    <ToolTip.Tip>
                        <MultiBinding Converter="{StaticResource TooltipVisibilityConverter}">
                            <Binding Path="ShowTooltip" />
                            <Binding>
                                <Binding.Source>
                                    <StackPanel>
                                        <TextBlock FontSize="16" Text="{Binding Tooltip}" />
                                        <TextBlock Text="{Binding Id, StringFormat='Channel {0}'}" />
                                    </StackPanel>
                                </Binding.Source>
                            </Binding>
                        </MultiBinding>
                    </ToolTip.Tip>
                    <Button.Styles>
                        <Style Selector="Button">
                            <Setter Property="Background" Value="#4C4E53" />
                            <Setter Property="BorderBrush" Value="#B3B5BB" />
                            <Setter Property="BorderThickness" Value="5" />
                            <Setter Property="CornerRadius" Value="30" />
                            <Setter Property="HorizontalAlignment" Value="Stretch" />
                            <Setter Property="VerticalAlignment" Value="Stretch" />
                            <Setter Property="Cursor" Value="Hand" />
                            <Setter Property="Margin" Value="5" />
                            <Setter Property="Padding" Value="0" />
                        </Style>
                        <Style Selector="Button:pointerover /template/ ContentPresenter">
                            <Setter Property="Background" Value="#4C4E53" />
                            <Setter Property="BorderBrush" Value="#34beed" />
                        </Style>
                        <Style Selector="Button:disabled /template/ ContentPresenter">
                            <Setter Property="Background" Value="#4C4E53" />
                            <Setter Property="BorderBrush" Value="#B3B5BB" />
                            <Setter Property="Opacity" Value="0.6" />
                        </Style>
                    </Button.Styles>
                    <Grid>
                        <Border CornerRadius="30" ClipToBounds="True">
                            <Grid>
                                <vlc:VideoView MediaPlayer="{Binding MediaPlayer}" IsVisible="{Binding HasVideo}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
                                <Image Source="{Binding Image}" IsVisible="{Binding !HasVideo}" Stretch="UniformToFill" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
                            </Grid>
                        </Border>
                        <TextBlock Text="Couldn't load image" Foreground="Red" FontSize="45" IsVisible="{Binding IsImageMissing}" HorizontalAlignment="Center" VerticalAlignment="Center" />
                        <TextBlock Text="Couldn't find executable" Foreground="Red" FontSize="30" IsVisible="{Binding IsExecutableMissing}" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,5" />
                    </Grid>
                </Button>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Viewbox>

几个可行的解决方案

方案1:抓帧用Image显示(最推荐你的多视频场景)

既然原生VideoView不服从裁剪,那我们绕开它,改用抓取视频帧+Avalonia Image控件显示的方式,这样所有Avalonia的布局约束都会生效,包括圆角裁剪。而且你要同时播放12个低动效循环视频,这个方案的性能完全能顶得住(只要视频分辨率不是特别高)。

具体操作步骤:

  1. 给MediaPlayer关闭硬件加速(软件解码才能稳定抓帧)
  2. 订阅FrameReady事件,把抓取到的视频帧转成Avalonia的IBitmap
  3. 把Bitmap绑定到Image控件,替换原来的VideoView

ViewModel核心代码:

using Avalonia;
using Avalonia.Media.Imaging;
using LibVLCSharp.Shared;
using ReactiveUI;

public class ChannelViewModel : ReactiveObject
{
    private IBitmap _videoFrame;
    private MediaPlayer _mediaPlayer;
    private readonly LibVLC _libVLC;

    public IBitmap VideoFrame
    {
        get => _videoFrame;
        set => this.RaiseAndSetIfChanged(ref _videoFrame, value);
    }

    public bool HasVideo { get; set; } = true;
    public bool ShowTooltip { get; set; } = true;
    public string Tooltip { get; set; } = "测试视频";
    public int Id { get; set; } = 1;

    public ChannelViewModel()
    {
        _libVLC = new LibVLC();
        InitializeMediaPlayer();
    }

    private void InitializeMediaPlayer()
    {
        var media = new Media(_libVLC, "你的循环视频路径", FromType.FromPath);
        _mediaPlayer = new MediaPlayer(media)
        {
            EnableHardwareDecoding = false, // 必须关闭硬件加速才能抓帧
            Loop = true // 循环播放
        };
        
        // 订阅帧就绪事件
        _mediaPlayer.FrameReady += OnFrameReady;
        _mediaPlayer.Play();
    }

    private void OnFrameReady(object sender, FrameReadyEventArgs e)
    {
        // 把LibVLC的VideoFrame转换成Avalonia可识别的Bitmap
        using var writeableBitmap = new WriteableBitmap(
            PixelFormat.Bgra8888,
            AlphaFormat.Premul,
            e.Frame.Width,
            e.Frame.Height,
            e.Frame.Pitch,
            e.Frame.Buffer);

        // 跨线程更新UI要调用Dispatcher
        Application.Current.Dispatcher.InvokeAsync(() =>
        {
            VideoFrame = writeableBitmap.Clone();
        });
    }
}

修改XAML的视频显示部分:

<Border CornerRadius="30" ClipToBounds="True">
    <Grid>
        <!-- 替换成绑定VideoFrame的Image -->
        <Image Source="{Binding VideoFrame}" IsVisible="{Binding HasVideo}" Stretch="UniformToFill" />
        <Image Source="{Binding Image}" IsVisible="{Binding !HasVideo}" Stretch="UniformToFill" />
    </Grid>
</Border>

这个方案的好处是完全跨平台(以后移植到Linux/macOS也能用),而且完美服从Avalonia的所有布局规则,圆角裁剪自然就生效了。我当时做16视频墙用的就是这个方案,完全没问题。

方案2:Windows原生API给VideoView加圆角(适合要保留硬件加速的场景)

如果你不想放弃硬件加速,那可以用Windows的原生API直接给VideoView的原生窗口设置圆角区域。这个方案是Windows平台专属的,不过你的环境刚好是Windows 10,完全适用。

核心思路:

获取VideoView的原生窗口句柄,用SetWindowRgn API给它设置一个圆角区域,还要监听容器大小变化动态更新,避免圆角错位。

View里的代码:

using Avalonia;
using Avalonia.Controls;
using Avalonia.Platform;
using LibVLCSharp.Avalonia;
using System.Runtime.InteropServices;

public partial class MainView : UserControl
{
    // 导入Windows原生API
    [DllImport("user32.dll")]
    private static extern IntPtr CreateRoundRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nWidthEllipse, int nHeightEllipse);

    [DllImport("user32.dll")]
    private static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);

    public MainView()
    {
        InitializeComponent();
    }

    private void VideoView_Loaded(object sender, RoutedEventArgs e)
    {
        var videoView = sender as VideoView;
        if (videoView == null) return;

        // 获取VideoView的原生窗口句柄
        if (videoView.PlatformImpl is INativeControl nativeControl)
        {
            var hwnd = nativeControl.Handle;
            // 计算圆角大小,和Button的CornerRadius保持一致(30)
            var size = videoView.Bounds.Size;
            var rgn = CreateRoundRectRgn(0, 0, (int)size.Width, (int)size.Height, 30, 30);
            SetWindowRgn(hwnd, rgn, true);
        }

        // 监听大小变化,动态更新圆角区域
        videoView.SizeChanged += VideoView_SizeChanged;
    }

    private void VideoView_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        var videoView = sender as VideoView;
        if (videoView?.PlatformImpl is not INativeControl nativeControl) return;

        var hwnd = nativeControl.Handle;
        var size = e.NewSize;
        var rgn = CreateRoundRectRgn(0, 0, (int)size.Width, (int)size.Height, 30, 30);
        SetWindowRgn(hwnd, rgn, true);
    }
}

给XAML的VideoView绑定Loaded事件:

<vlc:VideoView MediaPlayer="{Binding MediaPlayer}" IsVisible="{Binding HasVideo}" 
                HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                Loaded="VideoView_Loaded" />

这个方案的优点是保留了LibVLC的硬件加速,性能更好,缺点是只能在Windows用,而且要处理各种大小变化的情况。

方案3:换用纯托管的Avalonia视频库

如果不想折腾LibVLC的话,推荐试试Avalonia.Video这个库,它是基于FFmpeg的纯托管实现,完全用Avalonia的渲染管线,所以天然服从所有布局约束,包括圆角裁剪。不过它的硬件加速支持不如LibVLC,但你的场景是12个低动效循环视频,完全够用。

总结

  • 如果你要跨平台+完美兼容布局:选方案1(抓帧用Image)
  • 如果你要保留硬件加速+Windows专属:选方案2(原生API加圆角)
  • 如果你想换库省事儿:试试Avalonia.Video

我当时做视频墙的时候用的是方案1,16个视频同时播放都没什么问题,圆角裁剪完全正常,你可以试试!

火山引擎 最新活动