WPF中以MVVM方式,實現RTSP影片播放

貓嘞個撲發表於2023-09-25

前言
影片播放在上位機開發中經常會遇到,基本上是兩種常見的解決方案

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,效果圖如下

 


————————————————
版權宣告:本文為CSDN博主「maoleigepu」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/maoleigepu/article/details/133268837

 

相關文章