WPF中以MVVM方式,实现RTSP视频播放

wpf,mvvm,方式,实现,rtsp,视频,播放 · 浏览次数 : 24

小编点评

## 代码解析简介 **1. Player类** * 负责播放视频流 * 拥有 `MaxLatency` 属性,可以设置播放延迟 * 拥有 `Commands` 属性,可以设置播放动作 **2. Config类** * 负责配置播放参数 * 拥有 `Video` 属性,可以设置视频播放参数 * 拥有 `Commands` 属性,可以设置播放动作 **3. MainWindow类** * 负责创建窗口和绑定数据 * 注入 `MainViewModel` 数据 * 设置 `DataContext` * 创建 `FlyleafHost` 和 `Button` * 设置 `Player` 的 `DataContext` **4. 绑定关系** * `MainViewModel` 中设置 `MainViewModel` 的 `DataContext` * `FlyleafHost` 中设置 `Player` 的 `DataContext` * `Button` 中设置 `Player` 的 `DataContext` **5. 代码特点** * 代码简洁易懂 * 使用 `RelayCommand` 和 `DataBinding` 进行绑定 * 遵守了单一原则 * 代码可维护性好

正文

前言
视频播放在上位机开发中经常会遇到,基本上是两种常见的解决方案

1.采用厂家提供的sdk和前端控件进行展示,常见的海康/大华都提供了相关sdk及文档

2.开启相机onvif协议,捅过rtsp视频流进行播放,前端可以采用web方式,或者wpf中的视频控件进行展示。

项目需求,决定了最终采用开启相机onvif供能,wpf中播放的方式。

网络调研一阵子之后,基本都是推荐Vlc.DotNet或者libvlcsharp.wpf进行前端展示。

参考了很多代码,无论是官方文档,还是不同博客里的代码,很难做到用mvvm的方式对于逻辑解耦。

而且Vlc.DotNet已经不再更新了。

Libvlcasharp.wpf的设计有些反人类,可以参考这篇文章WPF中使用LibVLCSharp.WPF 播放rtsp - Naylor - 博客园 (cnblogs.com)

所以这部分逻辑写的很难受,需要寻找其他方案。

最近有空了,调研了几个其他开源项目,大家的思路都比较一致,相机打开onvif协议推送rtsp视频流,本地通过ffmpeg进行视频转流,然后推送到wpf前端控件上。

unosquare/ffmediaelement: FFME: The Advanced WPF MediaElement (based on FFmpeg) (github.com)

SuRGeoNix/Flyleaf: Media Player .NET Library for WinUI 3/ WPF/WinForms (based on FFmpeg/DirectX) (github.com)

网上有FFME的样例代码,我在本地搭建没有成功,应该是我的ffmpeg编译版本问题,可以参考这个项目。

DG-Wangtao/FFMEVideoPlayer: 使用FFmepg封装的WPF MideaElement,可以播放rtsp视频流。感谢 https://github.com/unosquare/ffmediaelement

最终选择了Flyleaf的方案,简单搭建了demo给大家参考。

Flyleaf官方项目地址SuRGeoNix/Flyleaf: Media Player .NET Library for WinUI 3/ WPF/WinForms (based on FFmpeg/DirectX) (github.com)

MVVM框架使用的是CommunityToolKit.MVVM

 

正文

Flyleaf的使用整体分成四步走,

1.App.xaml及App.xaml.cs中配置ffmpeg的dll文件地址;

1.1ffmpeg的dll文件,我才用的是Flyleaf官方sample中的文件,版本不是最新的。


1.2文件统一放在项目中的FFmpeg文件夹中


1.3生成操作(Build Action)配置为 无(None)


1.4复制到输出目录(Copy to Output Directory)配置为 如果较新则复制(Copy if newer)


1.5App.xaml中添加startup事件

    <Application x:Class="FlyleafDemo.App"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:local="clr-namespace:FlyleafDemo"
                 StartupUri="MainWindow.xaml"
                 Startup="Application_Startup">
        <Application.Resources>
             
        </Application.Resources>
    </Application>

1.6App.xaml.cs中配置ffmpeg的dll路径,项目编译后会复制ffmpeg文件夹及dll。

    using FlyleafLib;
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Data;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Windows;
    
    namespace FlyleafDemo
    {
        /// <summary>
        /// Interaction logic for App.xaml
        /// </summary>
        public partial class App : Application
        {
            private void Application_Startup(object sender, StartupEventArgs e)
            {
                Engine.Start(new EngineConfig()
                {
                    FFmpegPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FFmpeg"),
                    FFmpegDevices = false,    // Prevents loading avdevice/avfilter dll files. Enable it only if you plan to use dshow/gdigrab etc.
    
    #if RELEASE
                    FFmpegLogLevel      = FFmpegLogLevel.Quiet,
                    LogLevel            = LogLevel.Quiet,
    
    #else
                    FFmpegLogLevel = FFmpegLogLevel.Warning,
                    LogLevel = LogLevel.Debug,
                    LogOutput = ":debug",
                    //LogOutput         = ":console",
                    //LogOutput         = @"C:\Flyleaf\Logs\flyleaf.log",                
    #endif
    
                    //PluginsPath       = @"C:\Flyleaf\Plugins",
    
                    UIRefresh = false,    // Required for Activity, BufferedDuration, Stats in combination with Config.Player.Stats = true
                    UIRefreshInterval = 250,      // How often (in ms) to notify the UI
                    UICurTimePerSecond = true,     // Whether to notify UI for CurTime only when it's second changed or by UIRefreshInterval
                });
            }
        }
    }

2.ViewModel中配置参数等信息;

    using CommunityToolkit.Mvvm.ComponentModel;
    using CommunityToolkit.Mvvm.Input;
    using FlyleafLib.MediaPlayer;
    using FlyleafLib;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Media;
    
    namespace FlyleafDemo
    {
        public class MainViewModel:ObservableObject
        {
            private Player player;
    
            public Player Player
            {
                get => player;
                set => SetProperty(ref player, value);
            }
    
            private Config config;
    
            public Config Config
            {
                get => config;
                set => SetProperty(ref config, value);
            }
    
            private string uriString;
    
            public string UriString
            {
                get => uriString;
                set => SetProperty(ref uriString, value);
            }
    
            public IRelayCommand<string> PlayCommand { get; set; }
            public MainViewModel()
            {
                Config = new Config();
                Config.Video.BackgroundColor = Colors.Transparent;
                // 设置播放延迟为100ms,可能我理解有误,具体可以在项目issues里查看
                // Config.Player.MaxLatency = 100 * 10000;
    
                Player = new Player(Config);
                PlayCommand = new RelayCommand<string>(PlayAction);
                UriString = uri1;
            }
    
            private string currentUri = string.Empty;
            private string uri1 = "rtsp://192.168.20.2:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif";
            private string uri2 = "rtsp://192.168.20.3:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif";
            private void PlayAction(string uri)
            {
                if (!string.IsNullOrEmpty(uri))
                {
                    if (currentUri == uri1)
                    {
            //Player.Commands.Stop.Execute(null);
                        currentUri = uri2;
                        Player.Commands.Open.Execute(uri2);
                    }
                    else
                    {
            //Player.Commands.Stop.Execute(null);
                        currentUri = uri1;
                        Player.Commands.Open.Execute(uri1);
                    }
                }
            }
        }
    }

3.View中配置布局等信息;

    <Window
        x:Class="FlyleafDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:fl="clr-namespace:FlyleafLib.Controls.WPF;assembly=FlyleafLib"
        xmlns:local="clr-namespace:FlyleafDemo"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="800"
        Height="450"
        mc:Ignorable="d">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="5*" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <fl:FlyleafHost
                AttachedDragMove="Both"
                KeyBindings="Both"
                Player="{Binding Player, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
                <Viewbox>
                    <TextBlock Foreground="DarkOrange" Text="Hello Flyleaf Overlay!" />
                </Viewbox>
            </fl:FlyleafHost>
            <Button
                Grid.Row="1"
                Command="{Binding PlayCommand}"
                CommandParameter="{Binding UriString, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
        </Grid>
    </Window>

4.在xaml.cs中确定View和ViewModel的绑定关系

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace FlyleafDemo
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new MainViewModel();
            }
        }
    }

 

总结

前端控件绑定比较方便,减少了在xaml.cs中的耦合逻辑
我尝试过三路视频同时播放,效果不错,系统资源消耗也不高
很多参数都可以在Config中配置,一些交互逻辑可以在Player中执行,比较清晰
但是,单视频控件切换视频流的时候,会有一定时间延迟,我尝试过使用
Player.Commands.Stop.Execute(null);
但效果不大。
感兴趣的可以深挖源码,我这里只是抛砖引玉。
Demo源码地址,https://gitee.com/maoleigepu/flyleaf-demo.git,或者去github,maoleigepu/FlyleafDemo (github.com)效果图如下

 


————————————————
版权声明:本文为CSDN博主「maoleigepu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/maoleigepu/article/details/133268837

 

与WPF中以MVVM方式,实现RTSP视频播放相似的内容:

WPF中以MVVM方式,实现RTSP视频播放

前言视频播放在上位机开发中经常会遇到,基本上是两种常见的解决方案 1.采用厂家提供的sdk和前端控件进行展示,常见的海康/大华都提供了相关sdk及文档 2.开启相机onvif协议,捅过rtsp视频流进行播放,前端可以采用web方式,或者wpf中的视频控件进行展示。 项目需求,决定了最终采用开启相机o

使用.NET查询日出日落时间

在WPF中,通过资源文件实现主题切换是个常见的功能,有不少文章介绍了如何实现手动切换主题。那如何实现自动切换主题呢?通常有两种机制:一是跟随系统明暗主题切换,二是像手机操作系统那样根据日出日落时间自动切换。本文将以终为始,采用倒推法一步步介绍如何使用.NET免费获取日出日落时间。 获取日出日落时间

如何让WPF中的ValidationRule实现参数绑定

###背景 应用开发过程中,常常会对用户输入内容进行验证,通常是基于类型、范围、格式或者特定的要求进行验证,以确保输入符合预期。例如邮箱输入框校验输入内容是否符合邮箱格式。在WPF中,数据模型允许将`ValidationRules`与`Binding`对象关联,可以通过继承`ValidationRu

WPF 稳定的全屏化窗口方法

本文来告诉大家在 WPF 中,设置窗口全屏化的一个稳定的设置方法。在设置窗口全屏的时候,经常遇到的问题就是应用程序虽然设置最大化加无边框,但是此方式经常会有任务栏冒出来,或者说窗口没有贴屏幕的边。本文的方法是基于 Win32 的,由 lsj 提供的方法,当前已在 1000 多万台设备上稳定运行超过三...

CefSharp自定义滚动条样式

在WinForm/WPF中使用CefSharp混合开发时,通常需要自定义滚动条样式,以保证应用的整体风格统一。本文将给出一个简单的示例介绍如何自定义CefSharp中滚动条的样式。 基本思路 在前端开发中,通过CSS来控制滚动条的样式是件寻常的事情。CefSharp也提供了功能强大的API方便开发人

WPF复习知识点记录

# WPF复习知识点记录 由于近几年主要在做Web项目,客户端的项目主要是以维护为主,感觉对于基础知识的掌握没有那么牢靠,趁着这个周末重新复习下WPF的相关知识。 文章内容主要来自大佬刘铁锰老师的经典著作《深入浅出WPF》。 因为是复习,所以知识内容不会一一记录,如有需要了解更多可以看书中内容。 *

在 WPF 中集成 ASP.NET Core 和 WebView2 用于集成 SPA 应用

背景 我们有些工具在 Web 版中已经有了很好的实践,而在 WPF 中重新开发也是一种费时费力的操作,那么直接集成则是最省事省力的方法了。 修改项目文件 我们首先修改项目文件,让 WPF 项目可以包含 ASP.NET Core 的库,以及引用 WebView2 控件。

WPF中非递归(无后台代码)动态实现TreeView

WPF中提供了TreeView控件,对于TreeView控件的基本使用已经有很多文章。大都是介绍如何在后台代码递归遍历数据源,动态创建TreeView。这里我想介绍一下如何只通过XAML标记,不用一行后台代码遍历数据实现TreeView。

[WPF]浅析依赖属性(DependencyProperty)

在WPF中,引入了依赖属性这个概念,提到依赖属性时通常都会说依赖属性能节省实例对内存的开销。此外依赖属性还有两大优势。 支持多属性值,依赖属性系统可以储存多个值,配合Expression、Style、Animation等可以给我们带来很强的开发体验。 加入了属性变化通知,限制、验证等功能。方便我们使

Avalonia中的线性渐变画刷LinearGradientBrush

在WPF中使用Shape实现复杂线条动画后,尝试在Avalonia中也实现同样效果。尽管官方提供了从WPF到Avalonia的快速入门文档,但由于第一次使用Avalonia,体验过程中并不是很顺利,主要是卡在线性渐变画刷LinearGradientBrush的使用上。Avalonia中的线性渐变画刷