一、项目准备
-
创建WPF项目
- 在Visual Studio中新建WPF App (.NET Framework)项目
- 命名为"MediaPlayerApp"
-
添加必要的NuGet包
Install-Package Microsoft.WindowsAPICodePack-Shell Install-Package TagLibSharp
二、界面设计
1. 主窗口XAML (MainWindow.xaml)
<Window x:Class="MediaPlayerApp.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MediaPlayerApp"
mc:Ignorable="d"
Title="媒体播放器" Height="600" Width="800">
<Grid>
<!-- 播放器控制区 -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" Height="80" Background="#FF2D2D30" Margin="0,0,0,10">
<Button x:Name="btnPlayPause" Content="▶" Width="50" Height="50" Margin="10" Click="btnPlayPause_Click" FontSize="20"/>
<Button x:Name="btnStop" Content="◼" Width="50" Height="50" Margin="10" Click="btnStop_Click" FontSize="20"/>
<Slider x:Name="timelineSlider" Width="400" Margin="10" ValueChanged="timelineSlider_ValueChanged"/>
<TextBlock x:Name="txtTime" Margin="10" VerticalAlignment="Center" Foreground="White" FontSize="14"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="10">
<TextBlock Text="音量:" Foreground="White" FontSize="14"/>
<Slider x:Name="volumeSlider" Width="80" Margin="5" ValueChanged="volumeSlider_ValueChanged" Value="50"/>
</StackPanel>
<ComboBox x:Name="mediaTypeCombo" Width="100" Margin="10" SelectionChanged="mediaTypeCombo_SelectionChanged"/>
<TextBox x:Name="txtMediaPath" Width="200" Margin="10"/>
<Button x:Name="btnOpen" Content="打开" Width="60" Margin="10" Click="btnOpen_Click"/>
</StackPanel>
<!-- 媒体显示区 -->
<MediaElement x:Name="mediaElement" LoadedBehavior="Manual" UnloadedBehavior="Stop" MediaOpened="mediaElement_MediaOpened" MediaEnded="mediaElement_MediaEnded"/>
<!-- 视频控制区(仅在播放视频时显示) -->
<StackPanel x:Name="videoControls" Orientation="Vertical" VerticalAlignment="Top" Height="40" Background="#CC000000" Visibility="Collapsed" Margin="10">
<ToggleButton x:Name="fullscreenToggle" Content="全屏" Width="80" Height="30" Margin="5" Click="fullscreenToggle_Click"/>
</StackPanel>
</Grid>
</Window>
三、代码实现
1. 主窗口代码 (MainWindow.xaml.cs)
using Microsoft.WindowsAPICodePack.Dialogs;
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
namespace MediaPlayerApp
{
public partial class MainWindow : Window
{
private DispatcherTimer timelineSliderTimer;
private bool isDraggingSlider = false;
private bool isFullscreen = false;
public MainWindow()
{
InitializeComponent();
InitializeTimer();
LoadMediaTypes();
}
private void InitializeTimer()
{
timelineSliderTimer = new DispatcherTimer();
timelineSliderTimer.Interval = TimeSpan.FromMilliseconds(100);
timelineSliderTimer.Tick += TimelineSliderTimer_Tick;
}
private void LoadMediaTypes()
{
mediaTypeCombo.Items.Add("音频");
mediaTypeCombo.Items.Add("视频");
mediaTypeCombo.SelectedIndex = 0;
}
private void btnOpen_Click(object sender, RoutedEventArgs e)
{
var dialog = new CommonOpenFileDialog
{
Title = "选择媒体文件",
IsFolderPicker = false,
Multiselect = false
};
if (mediaTypeCombo.SelectedIndex == 0) // 音频
{
dialog.Filters.Add(new CommonFileDialogFilter("音频文件", "*.mp3;*.wav;*.aac;*.flac"));
}
else // 视频
{
dialog.Filters.Add(new CommonFileDialogFilter("视频文件", "*.mp4;*.avi;*.mkv;*.wmv"));
}
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
txtMediaPath.Text = dialog.FileName;
PlayMedia(dialog.FileName);
}
}
private void PlayMedia(string filePath)
{
if (!File.Exists(filePath))
{
MessageBox.Show("文件不存在!", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
mediaElement.Source = new Uri(filePath);
mediaElement.Play();
// 更新时间显示
UpdateTimeDisplay();
// 显示/隐藏视频控制区
if (mediaElement.NaturalVideoHeight > 0)
{
videoControls.Visibility = Visibility.Visible;
}
else
{
videoControls.Visibility = Visibility.Collapsed;
}
}
private void btnPlayPause_Click(object sender, RoutedEventArgs e)
{
if (mediaElement.CanPause)
{
if (mediaElement.CurrentState == System.Windows.Media.MediaElementState.Paused ||
mediaElement.CurrentState == System.Windows.Media.MediaElementState.Stopped)
{
mediaElement.Play();
btnPlayPause.Content = "❚❚";
}
else
{
mediaElement.Pause();
btnPlayPause.Content = "▶";
}
}
}
private void btnStop_Click(object sender, RoutedEventArgs e)
{
mediaElement.Stop();
btnPlayPause.Content = "▶";
timelineSlider.Value = 0;
txtTime.Text = "00:00:00 / 00:00:00";
}
private void timelineSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (!isDraggingSlider)
{
isDraggingSlider = true;
mediaElement.Position = TimeSpan.FromSeconds(timelineSlider.Value);
isDraggingSlider = false;
}
}
private void timelineSliderTimer_Tick(object sender, EventArgs e)
{
if (!isDraggingSlider)
{
timelineSlider.Value = mediaElement.Position.TotalSeconds;
}
}
private void TimelineSliderTimer_Tick(object sender, EventArgs e)
{
if (!isDraggingSlider && mediaElement.NaturalDuration.HasTimeSpan)
{
timelineSlider.Maximum = mediaElement.NaturalDuration.TimeSpan.TotalSeconds;
timelineSlider.Value = mediaElement.Position.TotalSeconds;
UpdateTimeDisplay();
}
}
private void UpdateTimeDisplay()
{
if (mediaElement.NaturalDuration.HasTimeSpan)
{
txtTime.Text = $"{mediaElement.Position.ToString(@"hh\:mm\:ss")} / {mediaElement.NaturalDuration.TimeSpan.ToString(@"hh\:mm\:ss")}";
}
else
{
txtTime.Text = "00:00:00 / --:--:--";
}
}
private void volumeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
mediaElement.Volume = volumeSlider.Value / 100;
}
private void mediaElement_MediaOpened(object sender, RoutedEventArgs e)
{
timelineSlider.Maximum = mediaElement.NaturalDuration.TimeSpan.TotalSeconds;
timelineSliderTimer.Start();
// 根据媒体类型设置默认音量
if (mediaTypeCombo.SelectedIndex == 0) // 音频
{
volumeSlider.Value = 70;
}
else // 视频
{
volumeSlider.Value = 50;
}
}
private void mediaElement_MediaEnded(object sender, RoutedEventArgs e)
{
btnPlayPause.Content = "▶";
timelineSlider.Value = 0;
txtTime.Text = "00:00:00 / 00:00:00";
}
private void fullscreenToggle_Click(object sender, RoutedEventArgs e)
{
if (isFullscreen)
{
this.WindowState = WindowState.Normal;
this.WindowStyle = WindowStyle.SingleBorderWindow;
fullscreenToggle.Content = "全屏";
}
else
{
this.WindowState = WindowState.Maximized;
this.WindowStyle = WindowStyle.None;
fullscreenToggle.Content = "退出";
}
isFullscreen = !isFullscreen;
}
private void mediaElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
// 视频大小自适应
if (mediaElement.NaturalVideoHeight > 0)
{
double aspectRatio = mediaElement.NaturalVideoWidth / (double)mediaElement.NaturalVideoHeight;
mediaElement.Width = this.ActualWidth - 20; // 减去边距
mediaElement.Height = mediaElement.Width / aspectRatio;
}
}
}
}
2. 添加必要的引用
在项目中添加以下引用:
- PresentationCore
- WindowsBase
- Microsoft.WindowsAPICodePack.Shell
- TagLibSharp (用于获取媒体元数据)
四、功能扩展
1. 添加播放列表功能
<!-- 在MainWindow.xaml中添加 -->
<ListBox x:Name="playlist" Margin="10" SelectionChanged="playlist_SelectionChanged"/>
<Button Content="添加到播放列表" Click="AddToPlaylist_Click" Margin="10"/>
// 在MainWindow.xaml.cs中添加
private void AddToPlaylist_Click(object sender, RoutedEventArgs e)
{
var dialog = new CommonOpenFileDialog
{
Title = "选择媒体文件",
IsFolderPicker = false,
Multiselect = true
};
if (mediaTypeCombo.SelectedIndex == 0) // 音频
{
dialog.Filters.Add(new CommonFileDialogFilter("音频文件", "*.mp3;*.wav;*.aac;*.flac"));
}
else // 视频
{
dialog.Filters.Add(new CommonFileDialogFilter("视频文件", "*.mp4;*.avi;*.mkv;*.wmv"));
}
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
foreach (var file in dialog.FileNames)
{
playlist.Items.Add(file);
}
}
}
private void playlist_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (playlist.SelectedItem != null)
{
PlayMedia(playlist.SelectedItem.ToString());
}
}
2. 添加媒体元数据显示
<!-- 在MainWindow.xaml中添加 -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Height="60" Background="#FF2D2D30" Margin="0,0,0,10">
<Image x:Name="albumArt" Width="50" Height="50" Margin="10"/>
<StackPanel Margin="10">
<TextBlock x:Name="txtTitle" FontSize="16" Foreground="White"/>
<TextBlock x:Name="txtArtist" FontSize="12" Foreground="#CCCCCB"/>
</StackPanel>
</StackPanel>
// 在PlayMedia方法中添加
try
{
var file = TagLib.File.Create(filePath);
albumArt.Source = GetAlbumArt(file);
txtTitle.Text = file.Tag.Title ?? Path.GetFileNameWithoutExtension(filePath);
txtArtist.Text = file.Tag.Performers.Length > 0 ? file.Tag.Performers[0] : "未知艺术家";
}
catch
{
// 如果无法读取元数据,使用默认值
albumArt.Source = null;
txtTitle.Text = Path.GetFileNameWithoutExtension(filePath);
txtArtist.Text = "未知艺术家";
}
// 添加获取专辑封面的方法
private ImageSource GetAlbumArt(TagLib.File file)
{
if (file.Tag.Pictures.Length > 0)
{
var ms = new MemoryStream(file.Tag.Pictures[0].Data.Data);
return BitmapFrame.Create(ms);
}
return null;
}
五、样式美化
1. 添加资源字典 (Styles.xaml)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- 按钮样式 -->
<Style TargetType="Button">
<Setter Property="Background" Value="#FF3A3A3C"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#FF5D5D60"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="8,4"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#FF5D5D60"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#FF2D2D30"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- 滑块样式 -->
<Style TargetType="Slider">
<Setter Property="Background" Value="#FF3A3A3C"/>
<Setter Property="Foreground" Value="#FF5D5D60"/>
<Setter Property="BorderBrush" Value="#FF5D5D60"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="5"/>
</Style>
<!-- 文本框样式 -->
<Style TargetType="TextBox">
<Setter Property="Background" Value="#FF3A3A3C"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#FF5D5D60"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="Margin" Value="5"/>
</Style>
<!-- 列表框样式 -->
<Style TargetType="ListBox">
<Setter Property="Background" Value="#FF3A3A3C"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#FF5D5D60"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="5"/>
</Style>
</ResourceDictionary>
2. 在App.xaml中引用
<Application x:Class="MediaPlayerApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary Source="Styles.xaml"/>
</Application.Resources>
</Application>
六、高级功能
1. 添加均衡器
<!-- 在MainWindow.xaml中添加 -->
<Expander Header="均衡器" Margin="10" Width="200">
<StackPanel>
<Slider x:Name="eqBass" Minimum="-15" Maximum="15" Value="0" TickFrequency="1" IsSnapToTickEnabled="True"/>
<Slider x:Name="eqMid" Minimum="-15" Maximum="15" Value="0" TickFrequency="1" IsSnapToTickEnabled="True"/>
<Slider x:Name="eqTreble" Minimum="-15" Maximum="15" Value="0" TickFrequency="1" IsSnapToTickEnabled="True"/>
</StackPanel>
</Expander>
// 在MainWindow.xaml.cs中添加
private void InitializeEqualizer()
{
// 这里需要使用音频处理库如NAudio来实现实际的均衡器效果
// 示例代码仅显示UI
}
// 在构造函数中调用
InitializeEqualizer();
2. 添加播放速度控制
<!-- 在MainWindow.xaml中添加 -->
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="速度:" VerticalAlignment="Center"/>
<Slider x:Name="playbackSpeed" Minimum="0.5" Maximum="2" Value="1" TickFrequency="0.25" IsSnapToTickEnabled="True" Width="100"/>
<TextBlock Text="{Binding ElementName=playbackSpeed, Path=Value, StringFormat={}x{0:F2}}" VerticalAlignment="Center"/>
</StackPanel>
// 在MainWindow.xaml.cs中添加
private void playbackSpeed_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// 注意:MediaElement不支持直接改变播放速度
// 可以使用MediaElement的Rate属性(仅部分系统支持)
try
{
mediaElement.SpeedRatio = playbackSpeed.Value;
}
catch
{
MessageBox.Show("您的系统不支持播放速度调整", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
七、打包发布
1. 创建安装程序
使用Visual Studio Installer Projects扩展创建安装包:
- 安装扩展:Visual Studio Installer Projects
- 新建项目:Setup Project
- 添加主程序文件和依赖项
- 配置快捷方式和文件关联
2. 发布为单文件
使用.NET Core的发布功能:
dotnet publish -c Release -r win-x64 --self-contained true /p:PublishSingleFile=true
八、总结
这个WPF媒体播放器实现了以下功能:
- 播放本地音频和视频文件
- 播放控制(播放/暂停/停止)
- 进度条和时间显示
- 音量控制
- 播放列表管理
- 媒体元数据显示
- 全屏模式
- 美观的UI界面
可以根据需要进一步扩展功能,如:
- 网络流媒体支持
- 字幕加载
- 播放历史记录
- 云同步播放列表
- 更高级的音频处理(均衡器、音效)
这个项目适合作为学习WPF和多媒体开发的良好起点,同时也可以作为实际应用的基础版本。