USER
--- 文件: PluginParameterViewModel.cs ---
using System.ComponentModel;
using SharedInterfaces;
namespace WpfMediaPlayer.ViewModels
{
public class PluginParameterViewModel : INotifyPropertyChanged
{
private double _value;
public string Name { get; set; }
public string DisplayName { get; set; }
public double Value
{
get => _value;
set
{
if (_value != value)
{
_value = value;
OnPropertyChanged(nameof(Value));
// 更新插件的参数
if (Plugin != null)
{
Plugin.Parameters[Name] = value;
}
}
}
}
public double Minimum { get; set; }
public double Maximum { get; set; }
public IPluginWithUI Plugin { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
--- 文件: PluginViewModel.cs ---
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using SharedInterfaces;
using System.Windows.Controls;
namespace WpfMediaPlayer.ViewModels
{
public class PluginViewModel : INotifyPropertyChanged
{
private bool _isEnabled;
public string PluginName => Plugin.PluginName;
public bool IsEnabled
{
get => _isEnabled;
set
{
if (_isEnabled != value)
{
_isEnabled = value;
Plugin.IsEnabled = value;
OnPropertyChanged(nameof(IsEnabled));
}
}
}
public ObservableCollection<PluginParameterViewModel> Parameters { get; set; } = new
ObservableCollection<PluginParameterViewModel>();
public IPluginBase Plugin { get; set; }
public UserControl Control
{
get
{
if (Plugin is IPluginWithUI pluginWithUI)
{
return pluginWithUI.GetControl();
}
return null;
}
}
public PluginViewModel(IPluginBase plugin)
{
Plugin = plugin;
IsEnabled = plugin.IsEnabled;
if (plugin is IPluginWithUI pluginWithUI)
{
foreach (var param in pluginWithUI.Parameters)
{
Parameters.Add(new PluginParameterViewModel
{
Name = param.Key,
DisplayName = param.Key, // 可以根据需要更改显示名称
Value = Convert.ToDouble(param.Value),
Minimum = 0, // 根据插件定义设置
Maximum = 2, // 根据插件定义设置
Plugin = pluginWithUI
});
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
--- 文件: MainWindow.xaml ---
<Window x:Class="WpfMediaPlayer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMediaPlayer"
Title="WPF媒体播放器" Height="600" Width="800"
AllowDrop="True"
Drop="Window_Drop"
DragOver="Window_DragOver">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 主内容区,包含视频和处理后的视频 -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 左侧视频显示区域 -->
<MediaElement Grid.Column="0" x:Name="mediaElement"
LoadedBehavior="Manual"
UnloadedBehavior="Stop"
Stretch="Uniform"
MediaOpened="MediaElement_MediaOpened"
MediaEnded="MediaElement_MediaEnded" />
<!-- 右侧处理后的视频显示区域,使用 Border 设置背景 -->
<Border Grid.Column="1" Background="LightGray">
<Image x:Name="imageDisplay" Stretch="Uniform" />
</Border>
</Grid>
<!-- 控制面板 -->
<Border Grid.Row="1" Background="Black" Padding="10">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- 播放控制按钮 -->
<Button x:Name="playButton" Content="播放" Width="60" Click="PlayButton_Click"/>
<Button x:Name="pauseButton" Content="暂停" Width="60" Margin="5,0" Click="PauseButton_Click"/>
<Button x:Name="stopButton" Content="停止" Width="60" Margin="5,0" Click="StopButton_Click"/>
<Button x:Name="rewindButton" Content="<< 10秒" Width="90" Margin="5,0"
Click="RewindButton_Click"/>
<Button x:Name="forwardButton" Content="10秒 >>" Width="90" Margin="5,0"
Click="ForwardButton_Click"/>
<!-- 使用 ToggleButton 作为插件列表按钮 -->
<ToggleButton x:Name="pluginListButton" Content="插件列表" Width="80" Margin="5,0"/>
<!-- 视频进度条 -->
<Slider x:Name="progressSlider"
Minimum="0"
Value="0"
IsEnabled="False"
ValueChanged="ProgressSlider_ValueChanged"
PreviewMouseLeftButtonDown="ProgressSlider_PreviewMouseLeftButtonDown"
PreviewMouseLeftButtonUp="ProgressSlider_PreviewMouseLeftButtonUp"
Width="300"
Margin="10,0"/>
<!-- 音量控制 -->
<TextBlock Text="音量" Foreground="White" VerticalAlignment="Center" Margin="10,0,5,0"/>
<Slider x:Name="volumeSlider" Minimum="0" Maximum="1" Value="0.5" Width="100"
ValueChanged="VolumeSlider_ValueChanged"/>
<!-- 时间显示 -->
<TextBlock x:Name="timeDisplay" Foreground="White" Margin="10,0" VerticalAlignment="Center"/>
<!-- 插件加载按钮 -->
<Button x:Name="plugButton" Content="加载插件" Width="80" Margin="5,0" Click="PlugButton_Click"/>
</StackPanel>
</StackPanel>
</Border>
<!-- 插件列表弹出框 -->
<Popup x:Name="pluginListPopup"
Placement="Bottom"
PlacementTarget="{Binding ElementName=pluginListButton}"
StaysOpen="False"
AllowsTransparency="True"
PopupAnimation="Slide"
Width="300"
IsOpen="{Binding IsChecked, ElementName=pluginListButton, Mode=TwoWay}"
Focusable="False">
<Border Background="White" BorderBrush="Gray" BorderThickness="1" CornerRadius="5" Padding="10">
<StackPanel Width="280" Margin="0">
<TextBlock Text="已加载插件:" FontWeight="Bold" Margin="5"/>
<!-- 修改 ItemsSource 绑定到 PluginViewModels -->
<ItemsControl ItemsSource="{Binding PluginViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding PluginName}" IsExpanded="False" Margin="5">
<StackPanel>
<CheckBox Content="启用" IsChecked="{Binding IsEnabled, Mode=TwoWay}"
Margin="0,0,0,5"/>
<!-- 插件参数 UserControl -->
<ContentPresenter Content="{Binding Control}" />
</StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- 插件加载提示 -->
<TextBlock Text="拖入 DLL 文件以加载插件"
Foreground="Gray"
FontStyle="Italic"
HorizontalAlignment="Center"
Margin="5,10,5,0"/>
</StackPanel>
</Border>
</Popup>
</Grid>
</Window>
--- 文件: MainWindow.xaml.cs ---
// WpfMediaPlayer/MainWindow.xaml.cs
using Microsoft.Win32;
using SharedInterfaces;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using WpfMediaPlayer.ViewModels;
namespace WpfMediaPlayer
{
public partial class MainWindow : Window
{
private readonly DispatcherTimer timer; // 用于更新进度条和时间显示
private bool isDraggingSlider = false; // 判断用户是否正在拖动进度条
private readonly PluginManager pluginManager; // 插件管理器
public MainWindow()
{
InitializeComponent();
// 初始化插件管理器
pluginManager = new PluginManager();
// 设置 Popup 的 DataContext 为插件管理器
pluginListPopup.DataContext = pluginManager;
// 初始化计时器
timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(33) // 大约每秒30帧
};
timer.Tick += Timer_Tick;
// 初始化音量
volumeSlider.Value = 0.5;
mediaElement.Volume = volumeSlider.Value;
// 自动加载 Plugins 文件夹中的所有插件
LoadAllPlugins();
// 添加全局点击事件
this.PreviewMouseDown += MainWindow_PreviewMouseDown;
}
#region 控件事件处理
// 播放按钮事件
private void PlayButton_Click(object sender, RoutedEventArgs e)
{
if (mediaElement.Source != null)
{
mediaElement.Play();
timer.Start();
}
else
{
OpenFileDialog openFileDialog = new OpenFileDialog
{
Filter = "媒体文件 (*.mpg;*.mp4;*.mp3;*.avi;*.mkv;*.flv)|*.mp4;*.mp3;*.avi;*.mkv;*.flv|所有文件
(*.*)|*.*"
};
if (openFileDialog.ShowDialog() == true)
{
LoadAndPlayMedia(openFileDialog.FileName);
}
}
}
// 暂停按钮事件
private void PauseButton_Click(object sender, RoutedEventArgs e)
{
mediaElement.Pause();
timer.Stop(); // 暂停计时器
}
// 停止按钮事件
private void StopButton_Click(object sender, RoutedEventArgs e)
{
mediaElement.Stop();
timer.Stop(); // 停止计时器
progressSlider.Value = 0;
if (mediaElement.NaturalDuration.HasTimeSpan)
{
timeDisplay.Text = $"00:00:00 / {mediaElement.NaturalDuration.TimeSpan:hh\\:mm\\:ss}";
}
else
{
timeDisplay.Text = "00:00:00 / 00:00:00";
}
// 清空插件显示内容
foreach (var plugin in pluginManager.IPlugins)
{
try
{
// 如果插件需要处理null帧(如重置状态),调用其ProcessFrame
plugin.ProcessFrame(null);
}
catch (Exception ex)
{
Console.WriteLine($"插件 {plugin.PluginName} 处理异常:{ex.Message}");
}
}
}
// 快退10秒按钮事件
private void RewindButton_Click(object sender, RoutedEventArgs e)
{
if (mediaElement.Position.TotalSeconds > 10)
{
mediaElement.Position -= TimeSpan.FromSeconds(10);
}
else
{
mediaElement.Position = TimeSpan.Zero;
}
}
// 快进10秒按钮事件
private void ForwardButton_Click(object sender, RoutedEventArgs e)
{
if (mediaElement.NaturalDuration.HasTimeSpan)
{
if (mediaElement.Position.TotalSeconds + 10 < mediaElement.NaturalDuration.TimeSpan.TotalSeconds)
{
mediaElement.Position += TimeSpan.FromSeconds(10);
}
else
{
mediaElement.Position = mediaElement.NaturalDuration.TimeSpan;
}
}
}
// 音量滑块事件
private void VolumeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (mediaElement != null)
{
mediaElement.Volume = volumeSlider.Value;
}
}
// 进度条滑块开始拖动事件
private void ProgressSlider_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
isDraggingSlider = true;
timer.Stop();
}
// 进度条滑块拖动结束事件
private void ProgressSlider_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
isDraggingSlider = false;
UpdateMediaPosition();
timer.Start();
}
// 插件加载按钮点击事件,加载 IPluginBase 或 IPluginWithUI 类型的 DLL 并设置其处理逻辑
private void PlugButton_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog
{
Filter = "DLL文件 (*.dll)|*.dll"
};
if (openFileDialog.ShowDialog() == true)
{
try
{
// 加载插件
Assembly assembly = Assembly.LoadFile(openFileDialog.FileName);
// 获取实现了IPluginWithUI接口的类型
foreach (Type type in assembly.GetTypes())
{
if (typeof(IPluginWithUI).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
{
IPluginWithUI pluginWithUI = (IPluginWithUI)Activator.CreateInstance(type);
pluginWithUI.Initialize();
pluginManager.AddIPluginWithUI(pluginWithUI);
MessageBox.Show($"插件 {pluginWithUI.PluginName} 加载成功,并设置为处理帧。");
}
else if (typeof(IPluginBase).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
{
// 获取仅实现了IPluginBase接口的插件
IPluginBase pluginBase = (IPluginBase)Activator.CreateInstance(type);
pluginBase.Initialize();
pluginManager.AddIPlugin(pluginBase);
MessageBox.Show($"插件 {pluginBase.PluginName} 加载成功,并设置为处理帧。");
}
}
}
catch (Exception ex)
{
MessageBox.Show($"加载插件失败: {ex.Message}");
}
}
}
// 进度条滑块值变化事件
private void ProgressSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (mediaElement != null && mediaElement.NaturalDuration.HasTimeSpan && !isDraggingSlider)
{
mediaElement.Position = TimeSpan.FromSeconds(progressSlider.Value);
UpdateTimeDisplay();
}
}
#endregion
#region 媒体事件处理
// 当媒体文件加载完成后
private void MediaElement_MediaOpened(object sender, RoutedEventArgs e)
{
if (mediaElement.NaturalDuration.HasTimeSpan)
{
progressSlider.IsEnabled = true;
progressSlider.Minimum = 0;
progressSlider.Maximum = mediaElement.NaturalDuration.TimeSpan.TotalSeconds;
progressSlider.Value = mediaElement.Position.TotalSeconds;
// 设置时间显示
timeDisplay.Text = $"{mediaElement.Position:hh\\:mm\\:ss} / {mediaElement.NaturalDuration.TimeSpan:hh\
\:mm\\:ss}";
Console.WriteLine("媒体文件已打开。");
}
}
// 当媒体播放结束
private void MediaElement_MediaEnded(object sender, RoutedEventArgs e)
{
mediaElement.Stop();
timer.Stop();
progressSlider.Value = 0;
if (mediaElement.NaturalDuration.HasTimeSpan)
{
timeDisplay.Text = "00:00:00 / " + mediaElement.NaturalDuration.TimeSpan.ToString(@"hh\:mm\:ss");
}
else
{
timeDisplay.Text = "00:00:00 / 00:00:00";
}
// 清空插件显示内容
foreach (var plugin in pluginManager.IPlugins)
{
try
{
// 如果插件需要处理null帧(如重置状态),调用其ProcessFrame
plugin.ProcessFrame(null);
}
catch (Exception ex)
{
Console.WriteLine($"插件 {plugin.PluginName} 处理异常:{ex.Message}");
}
}
}
#endregion
#region 计时器和进度更新
// 计时器更新视频帧及进度条
private void Timer_Tick(object sender, EventArgs e)
{
if (mediaElement.NaturalDuration.HasTimeSpan && !isDraggingSlider)
{
progressSlider.Maximum = mediaElement.NaturalDuration.TimeSpan.TotalSeconds;
progressSlider.Value = mediaElement.Position.TotalSeconds;
// 更新时间显示
UpdateTimeDisplay();
// 捕获并处理当前帧
CaptureAndProcessFrame();
}
}
// 更新时间显示
private void UpdateTimeDisplay()
{
if (mediaElement.NaturalDuration.HasTimeSpan)
{
timeDisplay.Text = $"{mediaElement.Position:hh\\:mm\\:ss} / {mediaElement.NaturalDuration.TimeSpan:hh\
\:mm\\:ss}";
}
else
{
timeDisplay.Text = $"{mediaElement.Position:hh\\:mm\\:ss} / 00:00:00";
}
}
// 捕获当前视频帧并处理
private void CaptureAndProcessFrame()
{
BitmapSource frame = CaptureCurrentFrame();
if (frame != null)
{
Console.WriteLine("捕获到一帧视频。");
// 在 UI 线程上处理帧
Dispatcher.BeginInvoke(new Action(() =>
{
BitmapSource processedFrame = frame;
// 依次应用所有启用的 IPluginBase 插件
foreach (var plugin in pluginManager.IPlugins.Where(p => p.IsEnabled))
{
try
{
processedFrame = plugin.ProcessFrame(processedFrame);
Console.WriteLine($"插件 {plugin.PluginName} 处理完成。");
}
catch (Exception ex)
{
Console.WriteLine($"插件 {plugin.PluginName} 处理异常:{ex.Message}");
}
}
// 更新图像显示
imageDisplay.Source = processedFrame;
Console.WriteLine("更新 imageDisplay.Source 成功。");
}), DispatcherPriority.Background);
}
else
{
Console.WriteLine("帧捕获返回null。");
}
}
// 捕获当前视频帧的方法
private BitmapSource CaptureCurrentFrame()
{
double width = mediaElement.ActualWidth;
double height = mediaElement.ActualHeight;
if (width == 0 || height == 0)
return null;
try
{
RenderTargetBitmap rtb = new RenderTargetBitmap(
(int)width, (int)height,
96, 96,
PixelFormats.Pbgra32); // 确保正确的像素格式
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
dc.DrawRectangle(new VisualBrush(mediaElement), null, new Rect(new Point(), new Size(width,
height)));
}
rtb.Render(dv);
return rtb;
}
catch
{
return null;
}
}
#endregion
#region 插件管理
// 自动加载 Plugins 文件夹中的所有插件
private void LoadAllPlugins()
{
string pluginsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
if (Directory.Exists(pluginsPath))
{
pluginManager.LoadPlugins(pluginsPath);
}
else
{
// 如果 Plugins 文件夹不存在,创建一个
Directory.CreateDirectory(pluginsPath);
Console.WriteLine("Plugins 文件夹已创建。");
}
}
#endregion
#region 拖放处理
// 统一处理拖放事件
private void Window_DragOver(object sender, DragEventArgs e)
{
HandleDragOver(e, allowDll: false);
}
private void Window_Drop(object sender, DragEventArgs e)
{
HandleDrop(e, allowDll: false);
}
private void PluginListPopup_DragOver(object sender, DragEventArgs e)
{
HandleDragOver(e, allowDll: true);
}
private void PluginListPopup_Drop(object sender, DragEventArgs e)
{
HandleDrop(e, allowDll: true);
}
// 统一处理拖放事件的逻辑
private void HandleDragOver(DragEventArgs e, bool allowDll = false)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
bool hasValidFile = false;
foreach (var file in files)
{
string extension = Path.GetExtension(file).ToLower();
if (allowDll && extension == ".dll")
{
hasValidFile = true;
break;
}
else if (!allowDll && IsMediaFile(extension))
{
hasValidFile = true;
break;
}
}
e.Effects = hasValidFile ? DragDropEffects.Copy : DragDropEffects.None;
}
else
{
e.Effects = DragDropEffects.None;
}
e.Handled = true;
}
private void HandleDrop(DragEventArgs e, bool allowDll = false)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
foreach (string filePath in files)
{
string extension = Path.GetExtension(filePath).ToLower();
if (allowDll && extension == ".dll")
{
// 加载插件 DLL
try
{
Assembly assembly = Assembly.LoadFile(filePath);
foreach (Type type in assembly.GetTypes())
{
if (typeof(IPluginWithUI).IsAssignableFrom(type) && !type.IsInterface && !
type.IsAbstract)
{
IPluginWithUI pluginWithUI = (IPluginWithUI)Activator.CreateInstance(type);
pluginWithUI.Initialize();
pluginManager.AddIPluginWithUI(pluginWithUI);
Console.WriteLine($"插件 {pluginWithUI.PluginName} 加载成功。");
}
else if (typeof(IPluginBase).IsAssignableFrom(type) && !type.IsInterface && !
type.IsAbstract)
{
// 获取仅实现了IPluginBase接口的插件
IPluginBase pluginBase = (IPluginBase)Activator.CreateInstance(type);
pluginBase.Initialize();
pluginManager.AddIPlugin(pluginBase);
Console.WriteLine($"插件 {pluginBase.PluginName} 加载成功,无UI控件。");
}
}
MessageBox.Show($"插件加载成功: {filePath}");
}
catch (Exception ex)
{
MessageBox.Show($"加载插件失败: {ex.Message}");
Console.WriteLine($"加载插件失败: {ex.Message}");
}
}
else if (!allowDll && IsMediaFile(extension))
{
// 加载并播放媒体文件
LoadAndPlayMedia(filePath);
}
}
}
}
// 判断是否为支持的媒体文件
private bool IsMediaFile(string extension)
{
string[] supportedExtensions = { ".mp4", ".mp3", ".avi", ".mkv", ".flv" };
return supportedExtensions.Contains(extension);
}
#endregion
#region 外部点击关闭 Popup
private void MainWindow_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (pluginListPopup.IsOpen)
{
// 获取点击的元素
DependencyObject clickedElement = e.OriginalSource as DependencyObject;
// 检查点击是否在 Popup 内部
if (!IsDescendant(pluginListPopup.Child, clickedElement))
{
pluginListPopup.IsOpen = false;
// 如果使用 ToggleButton,取消选中状态
pluginListButton.IsChecked = false;
}
}
}
// 递归检查是否为子孙元素
private bool IsDescendant(DependencyObject parent, DependencyObject child)
{
if (child == null) return false;
if (parent == child) return true;
DependencyObject current = child;
while (current != null)
{
if (current == parent)
return true;
current = VisualTreeHelper.GetParent(current);
}
return false;
}
#endregion
#region 媒体播放
// 加载并播放媒体文件
private void LoadAndPlayMedia(string filePath)
{
try
{
mediaElement.Source = new Uri(filePath);
mediaElement.Play();
timer.Start(); // 启动计时器以更新进度条
Console.WriteLine($"播放媒体文件: {filePath}");
}
catch (Exception ex)
{
MessageBox.Show($"无法播放文件: {ex.Message}", "播放错误", MessageBoxButton.OK, MessageBoxImage.Error);
Console.WriteLine($"无法播放文件: {ex.Message}");
}
}
// 更新视频播放位置
private void UpdateMediaPosition()
{
if (mediaElement != null && mediaElement.NaturalDuration.HasTimeSpan)
{
mediaElement.Position = TimeSpan.FromSeconds(progressSlider.Value);
}
}
#endregion
#region Expander Toggle
private void ExpanderToggle_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
if (button != null)
{
Expander expander = button.Parent as Expander;
if (expander != null)
{
expander.IsExpanded = !expander.IsExpanded;
}
}
}
#endregion
}
}
--- 文件: PluginManager.cs ---
using SharedInterfaces;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using WpfMediaPlayer.ViewModels;
namespace WpfMediaPlayer
{
public class PluginManager
{
public ObservableCollection<IPluginBase> IPlugins { get; private set; } = new ObservableCollection<IPluginBase>
();
public ObservableCollection<IPluginWithUI> IPluginsWithUI { get; private set; } = new
ObservableCollection<IPluginWithUI>();
public ObservableCollection<PluginViewModel> PluginViewModels { get; private set; } = new
ObservableCollection<PluginViewModel>();
/// <summary>
/// 加载指定路径中的所有插件DLL
/// </summary>
/// <param name="pluginsPath">插件文件夹路径</param>
public void LoadPlugins(string pluginsPath)
{
if (!Directory.Exists(pluginsPath))
return;
var dllFiles = Directory.GetFiles(pluginsPath, "*.dll");
foreach (var dll in dllFiles)
{
try
{
Assembly assembly = Assembly.LoadFrom(dll);
var types = assembly.GetTypes();
foreach (var type in types)
{
if (typeof(IPluginWithUI).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
{
// 插件实现了IPluginWithUI
IPluginWithUI pluginWithUI = (IPluginWithUI)Activator.CreateInstance(type);
pluginWithUI.Initialize();
IPlugins.Add(pluginWithUI);
IPluginsWithUI.Add(pluginWithUI);
PluginViewModels.Add(new PluginViewModel(pluginWithUI));
Console.WriteLine($"Loaded IPluginWithUI: {pluginWithUI.PluginName}");
}
else if (typeof(IPluginBase).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
{
// 插件仅实现了IPluginBase
IPluginBase pluginBase = (IPluginBase)Activator.CreateInstance(type);
pluginBase.Initialize();
IPlugins.Add(pluginBase);
PluginViewModels.Add(new PluginViewModel(pluginBase));
Console.WriteLine($"Loaded IPluginBase: {pluginBase.PluginName}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load plugin from {dll}: {ex.Message}");
}
}
}
/// <summary>
/// 卸载所有插件
/// </summary>
public void UnloadPlugins()
{
// 卸载IPluginWithUI插件
foreach (var pluginWithUI in IPluginsWithUI)
{
pluginWithUI.GetControl()?.Dispatcher.Invoke(() => pluginWithUI.GetControl().ClearValue
(UserControl.ContentProperty));
}
IPluginsWithUI.Clear();
// 卸载IPluginBase插件
IPlugins.Clear();
PluginViewModels.Clear();
}
/// <summary>
/// 添加一个IPluginBase插件
/// </summary>
/// <param name="plugin">插件实例</param>
public void AddIPlugin(IPluginBase plugin)
{
if (plugin != null)
{
plugin.Initialize();
IPlugins.Add(plugin);
PluginViewModels.Add(new PluginViewModel(plugin));
Console.WriteLine($"Loaded IPluginBase: {plugin.PluginName}");
}
}
/// <summary>
/// 添加一个IPluginWithUI插件
/// </summary>
/// <param name="plugin">插件实例</param>
public void AddIPluginWithUI(IPluginWithUI plugin)
{
if (plugin != null)
{
plugin.Initialize();
IPlugins.Add(plugin);
IPluginsWithUI.Add(plugin);
PluginViewModels.Add(new PluginViewModel(plugin));
Console.WriteLine($"Loaded IPluginWithUI: {plugin.PluginName}");
}
}
}
}
--- 文件: IPluginbase.cs ---
// SharedInterfaces/IPluginBase.cs
using System.Windows.Media.Imaging;
namespace SharedInterfaces
{
public interface IPluginBase
{
/// <summary>
/// 插件名称
/// </summary>
string PluginName { get; }
/// <summary>
/// 插件是否启用
/// </summary>
bool IsEnabled { get; set; }
/// <summary>
/// 初始化插件
/// </summary>
void Initialize();
/// <summary>
/// 处理视频帧数据,并返回处理后的帧
/// </summary>
/// <param name="frame">当前视频帧</param>
/// <returns>处理后的 BitmapSource 视频帧</returns>
BitmapSource ProcessFrame(BitmapSource frame);
}
}
--- 文件: IPluginWithUI.cs ---
// SharedInterfaces/IPluginWithUI.cs
using System.Collections.Generic;
using System.Windows.Controls;
namespace SharedInterfaces
{
public interface IPluginWithUI : IPluginBase
{
/// <summary>
/// 获取插件的 UserControl 界面
/// </summary>
/// <returns>UserControl 用于显示</returns>
UserControl GetControl();
/// <summary>
/// 获取插件的参数字典
/// </summary>
IDictionary<string, object> Parameters { get; }
}
}
--- 文件: PluginParameter.cs ---
// SharedInterfaces/PluginParameter.cs
using System.ComponentModel;
namespace SharedInterfaces
{
public class PluginParameter : INotifyPropertyChanged
{
private string name;
private double value;
private double min;
private double max;
public string Name
{
get => name;
set { name = value; OnPropertyChanged(nameof(Name)); }
}
public double Value
{
get => value;
set { this.value = value; OnPropertyChanged(nameof(Value)); }
}
public double Min
{
get => min;
set { min = value; OnPropertyChanged(nameof(Min)); }
}
public double Max
{
get => max;
set { max = value; OnPropertyChanged(nameof(Max)); }
}
public PluginParameter(string name, double value, double min, double max)
{
Name = name;
Value = value;
Min = min;
Max = max;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
--- 文件: IComponent.cs ---
// SharedInterfaces/IComponent.cs
using System.Windows.Controls;
namespace SharedInterfaces
{
public interface IComponent
{
/// <summary>
/// 组件的名称
/// </summary>
string ComponentName { get; }
/// <summary>
/// 初始化组件
/// </summary>
void Initialize();
/// <summary>
/// 获取组件的 UserControl 界面
/// </summary>
/// <returns>UserControl用于显示</returns>
UserControl GetControl();
/// <summary>
/// 释放组件资源
/// </summary>
void Unload();
}
}<Window x:Class="WpfMediaPlayer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMediaPlayer"
Title="WPF媒体播放器" Height="600" Width="800"
AllowDrop="True"
Drop="Window_Drop"
DragOver="Window_DragOver">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 主内容区,包含原始视频和处理后的视频 -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 左侧视频显示区域 -->
<MediaElement Grid.Column="0" x:Name="mediaElement"
LoadedBehavior="Manual"
UnloadedBehavior="Stop"
Stretch="Uniform"
MediaOpened="MediaElement_MediaOpened"
MediaEnded="MediaElement_MediaEnded" />
<!-- 右侧处理后的视频显示区域,使用 Border 设置背景 -->
<Border Grid.Column="1" Background="LightGray">
<Image x:Name="imageDisplay" Stretch="Uniform" />
</Border>
</Grid>
<!-- 控制面板 -->
<Border Grid.Row="1" Background="Black" Padding="10">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- 播放控制按钮 -->
<Button x:Name="playButton" Content="播放" Width="60" Click="PlayButton_Click"/>
<Button x:Name="pauseButton" Content="暂停" Width="60" Margin="5,0" Click="PauseButton_Click"/>
<Button x:Name="stopButton" Content="停止" Width="60" Margin="5,0" Click="StopButton_Click"/>
<Button x:Name="rewindButton" Content="<< 10秒" Width="90" Margin="5,0" Click="RewindButton_Click"/>
<Button x:Name="forwardButton" Content="10秒 >>" Width="90" Margin="5,0" Click="ForwardButton_Click"/>
<!-- 使用 ToggleButton 作为插件列表按钮 -->
<ToggleButton x:Name="pluginListButton" Content="插件列表" Width="80" Margin="5,0"/>
<!-- 视频进度条 -->
<Slider x:Name="progressSlider"
Minimum="0"
Value="0"
IsEnabled="False"
ValueChanged="ProgressSlider_ValueChanged"
PreviewMouseLeftButtonDown="ProgressSlider_PreviewMouseLeftButtonDown"
PreviewMouseLeftButtonUp="ProgressSlider_PreviewMouseLeftButtonUp"
Width="300"
Margin="10,0"/>
<!-- 音量控制 -->
<TextBlock Text="音量" Foreground="White" VerticalAlignment="Center" Margin="10,0,5,0"/>
<Slider x:Name="volumeSlider" Minimum="0" Maximum="1" Value="0.5" Width="100" ValueChanged="VolumeSlider_ValueChanged"/>
<!-- 时间显示 -->
<TextBlock x:Name="timeDisplay" Foreground="White" Margin="10,0" VerticalAlignment="Center"/>
<!-- 插件加载按钮 -->
<Button x:Name="plugButton" Content="加载插件" Width="80" Margin="5,0" Click="PlugButton_Click"/>
</StackPanel>
</StackPanel>
</Border>
<!-- 插件列表弹出框 -->
<Popup x:Name="pluginListPopup"
Placement="Bottom"
PlacementTarget="{Binding ElementName=pluginListButton}"
StaysOpen="False"
AllowsTransparency="True"
PopupAnimation="Slide"
Width="300"
IsOpen="{Binding IsChecked, ElementName=pluginListButton, Mode=TwoWay}"
Focusable="False">
<Border Background="White" BorderBrush="Gray" BorderThickness="1" CornerRadius="5" Padding="10">
<StackPanel Width="280" Margin="0">
<TextBlock Text="已加载插件:" FontWeight="Bold" Margin="5"/>
<!-- 修改 ItemsSource 绑定到 PluginViewModels -->
<ItemsControl ItemsSource="{Binding PluginViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding PluginName}" IsExpanded="False" Margin="5">
<StackPanel>
<CheckBox Content="启用" IsChecked="{Binding IsEnabled, Mode=TwoWay}" Margin="0,0,0,5"/>
<!-- 插件参数 UserControl -->
<ContentPresenter Content="{Binding Control}" />
</StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- 插件加载提示 -->
<TextBlock Text="拖入 DLL 文件以加载插件"
Foreground="Gray"
FontStyle="Italic"
HorizontalAlignment="Center"
Margin="5,10,5,0"/>
</StackPanel>
</Border>
</Popup>
</Grid>
</Window> 阅读上面代码 写一个 数据绑定的 插件DLL CS 通过CAPTURE RENDER ENABLEBITMAP 把视频单帧的1/4部分 移动40像素 注意 CATPURE 与输出尺寸与视频一样ASSISTANT
To create a data-bound plugin DLL for your WPF Media Player application that captures each video frame, moves a quarter of the frame by 40 pixels, and maintains the original frame dimensions, follow the steps below. This plugin will implement the `IPluginWithUI` interface, allowing it to have a user interface for adjusting parameters if needed.
### Overview
1. **Plugin Implementation (`MoveQuarterPlugin.cs`)**
- Implements `IPluginWithUI`.
- Contains a parameter for the number of pixels to move.
- Processes each frame by moving a specified quarter of the frame.
2. **Plugin UI (`MoveQuarterPluginControl.xaml` and `MoveQuarterPluginControl.xaml.cs`)**
- Provides a user interface to adjust the movement pixels dynamically.
- Binds to the plugin's parameters using data binding.
### 1. Plugin Implementation
Create a new Class Library project for your plugin and add references to the `SharedInterfaces` project. Then, implement the plugin as follows:
#### `MoveQuarterPlugin.cs`
```csharp
// MoveQuarterPlugin.cs
using SharedInterfaces;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace MoveQuarterPlugin
{
public class MoveQuarterPlugin : IPluginWithUI
{
// Plugin properties
public string PluginName => "Move Quarter Plugin";
private bool isEnabled;
public bool IsEnabled
{
get => isEnabled;
set
{
isEnabled = value;
OnPropertyChanged(nameof(IsEnabled));
}
}
// Parameters dictionary
public IDictionary<string, object> Parameters { get; private set; }
// Event for property changes
public event PropertyChangedEventHandler PropertyChanged;
// Constructor
public MoveQuarterPlugin()
{
Parameters = new Dictionary<string, object>
{
{ "MovePixels", 40.0 } // Default value
};
}
// Initialize method
public void Initialize()
{
// Additional initialization if needed
}
// Get the UserControl for UI
public UserControl GetControl()
{
return new MoveQuarterPluginControl(this);
}
// ProcessFrame method
public BitmapSource ProcessFrame(BitmapSource frame)
{
if (!IsEnabled || frame == null)
return frame;
try
{
// Convert BitmapSource to WriteableBitmap for manipulation
WriteableBitmap writableBitmap = new WriteableBitmap(frame);
int width = writableBitmap.PixelWidth;
int height = writableBitmap.PixelHeight;
int stride = writableBitmap.BackBufferStride;
int bytesPerPixel = (writableBitmap.Format.BitsPerPixel + 7) / 8;
// Get parameters
double movePixelsDouble = Parameters.ContainsKey("MovePixels") ?
Convert.ToDouble(Parameters["MovePixels"]) : 40.0;
int movePixels = (int)Math.Round(movePixelsDouble);
// Define the quarter region (top-left quadrant)
int quarterWidth = width / 2;
int quarterHeight = height / 2;
// Create a copy of the quarter pixels
byte[] quarterPixels = new byte[quarterWidth * quarterHeight * bytesPerPixel];
writableBitmap.CopyPixels(new Int32Rect(0, 0, quarterWidth, quarterHeight),
quarterPixels, stride, 0);
// Clear the original quarter area (optional)
// You can skip this if you want to overlay
// for (int y = 0; y < quarterHeight; y++)
// {
// for (int x = 0; x < quarterWidth; x++)
// {
// int index = y * stride + x * bytesPerPixel;
// quarterPixels[index] = 0; // B
// quarterPixels[index + 1] = 0; // G
// quarterPixels[index + 2] = 0; // R
// if (bytesPerPixel == 4)
// quarterPixels[index + 3] = 0; // A
// }
// }
// Write the quarter pixels back with offset
int newX = 40; // Move right by 40 pixels
int newY = 40; // Move down by 40 pixels
// Ensure the new position is within bounds
newX = Math.Min(newX, width - quarterWidth);
newY = Math.Min(newY, height - quarterHeight);
// Write the pixels to the new location
writableBitmap.WritePixels(new Int32Rect(newX, newY, quarterWidth, quarterHeight),
quarterPixels, stride, 0);
return writableBitmap;
}
catch (Exception ex)
{
Console.WriteLine($"[{PluginName}] Error processing frame: {ex.Message}");
return frame;
}
}
// Helper method to raise PropertyChanged events
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
```
**Explanation:**
- **Properties:**
- `PluginName`: The name of the plugin.
- `IsEnabled`: Determines whether the plugin is active.
- `Parameters`: A dictionary holding plugin parameters, such as `MovePixels`.
- **Methods:**
- `Initialize()`: For any initialization logic.
- `GetControl()`: Returns the UserControl for the plugin's UI.
- `ProcessFrame(BitmapSource frame)`: Processes each video frame by moving the top-left quarter of the frame by 40 pixels both horizontally and vertically.
- **Frame Processing Logic:**
- Converts the `BitmapSource` to a `WriteableBitmap` for pixel manipulation.
- Determines the quarter of the frame to move.
- Copies the pixels of the quarter and writes them back to a new position with an offset of 40 pixels.
- Handles boundary conditions to ensure it doesn't write outside the frame.
### 2. Plugin UI
Create a UserControl to allow users to adjust the number of pixels (`MovePixels`) the quarter of the frame is moved. This UI will bind to the plugin's parameters using data binding.
#### `MoveQuarterPluginControl.xaml`
```xml
<!-- MoveQuarterPluginControl.xaml -->
<UserControl x:Class="MoveQuarterPlugin.MoveQuarterPluginControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:MoveQuarterPlugin"
Height="100" Width="250">
<Grid Margin="10">
<StackPanel>
<TextBlock Text="Move Pixels:" FontWeight="Bold" Margin="0,0,0,5"/>
<Slider Minimum="0" Maximum="100"
Value="{Binding Path=Parameters[MovePixels], Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
TickFrequency="10"
IsSnapToTickEnabled="True" />
<TextBlock Text="{Binding Path=Parameters[MovePixels], StringFormat=Move Pixels: {0}}"
HorizontalAlignment="Center" Margin="0,5,0,0"/>
</StackPanel>
</Grid>
</UserControl>
```
#### `MoveQuarterPluginControl.xaml.cs`
```csharp
// MoveQuarterPluginControl.xaml.cs
using System.Windows.Controls;
namespace MoveQuarterPlugin
{
/// <summary>
/// Interaction logic for MoveQuarterPluginControl.xaml
/// </summary>
public partial class MoveQuarterPluginControl : UserControl
{
public MoveQuarterPluginControl(MoveQuarterPlugin plugin)
{
InitializeComponent();
this.DataContext = plugin;
}
}
}
```
**Explanation:**
- **UI Elements:**
- **Slider:** Allows the user to adjust the `MovePixels` parameter between 0 and 100 pixels.
- **TextBlock:** Displays the current value of `MovePixels`.
- **Data Binding:**
- The Slider's `Value` is bound to the `MovePixels` parameter in the plugin's `Parameters` dictionary.
- `Mode=TwoWay` ensures that changes in the UI update the plugin's parameter and vice versa.
### 3. Compiling the Plugin
1. **Project Setup:**
- Create a new Class Library project in your solution (e.g., `MoveQuarterPlugin`).
- Add a reference to the `SharedInterfaces` project to access `IPluginWithUI` and related interfaces.
2. **Add the Files:**
- Add `MoveQuarterPlugin.cs`, `MoveQuarterPluginControl.xaml`, and `MoveQuarterPluginControl.xaml.cs` to the project.
3. **Build the DLL:**
- Build the project to generate the `MoveQuarterPlugin.dll`.
4. **Deploy the Plugin:**
- Place the compiled `MoveQuarterPlugin.dll` into the `Plugins` folder of your WPF Media Player application.
### 4. Using the Plugin in the Application
After placing the `MoveQuarterPlugin.dll` in the `Plugins` directory and running your WPF Media Player application:
1. **Loading Plugins:**
- The application will automatically load all plugins from the `Plugins` folder on startup.
- Alternatively, you can use the "加载插件" (Load Plugin) button to load plugins manually.
2. **Enabling the Plugin:**
- Open the "插件列表" (Plugin List) by clicking the corresponding toggle button.
- Find the "Move Quarter Plugin" in the list and check the "启用" (Enable) checkbox to activate it.
3. **Adjusting Parameters:**
- Expand the plugin's expander in the plugin list to access its UI.
- Use the slider to adjust the number of pixels (`MovePixels`) by which the quarter of the frame is moved.
4. **Playing Media:**
- Play any supported video file. The plugin will process each frame, moving the specified quarter by the defined number of pixels.
### 5. Additional Considerations
- **Performance:**
- Frame processing can be CPU-intensive. Ensure that your manipulation logic is optimized for performance to prevent playback lag.
- **Error Handling:**
- The plugin includes basic error handling. Depending on your needs, you might want to enhance it to handle more edge cases or provide user feedback.
- **Extensibility:**
- You can extend this plugin to allow moving different quarters (e.g., top-right, bottom-left, bottom-right) or adding more parameters for dynamic manipulation.
### Conclusion
This plugin demonstrates how to implement a data-bound plugin in your WPF Media Player application, allowing dynamic manipulation of video frames with user-adjustable parameters. By following the structure and guidelines provided, you can create more complex plugins to extend the functionality of your media player.