C#-WinForm-简单的音频播放器(基于WindowsMediaPlayer控件)(一)
趁着暑假写写小项目,开学又不能安心敲代码了。整个程序的界面设计参考自《WinForm项目实战》,本篇文章中只是实现了音频播放器的基本功能,之后会继续完善其他功能。
(初始界面)
(播放界面)
创建完成后,先添加WMP(WindowsMediaPlayer)控件,打开工具箱,右键->选择项->COM组件->勾选WMP控件。
添加完成后,可以在工具箱里找到WMP控件,然后将其加入窗口中。由于我们要自己编写对播放器的各种操作所以需要对控件进行修改,去掉自带的控制按钮。
选中WMP控件->右键->属性->常规->选择模式:None->播放选项:按比例拉伸->音量设置:最大;高级->启用控件(禁用);完成设置,返回界面,整个WMP控件就仅显示视频了接下来就可以开始添加按钮,进度条,播放列表所需要的控件了。
名称 类型 Name属性 播放按钮 Button btnPlay 暂停按钮 Button btnPause 停止按钮 Button btnStop 上一曲按钮 Button btnBack 下一曲按钮 Button btnNext 播放列表 ListBox lstSongList 进度条 TrackBar tkbMove 音量条 TrackBar tkbVol 歌曲名显示框 TextBox txtSongName 播放时间显示框 TextBox txtTime 状态栏 StatusStrip stsInfo 菜单栏 MenuStrip mianMenu 右键菜单 ContextMenuStrip contextMenu 计时器 Timer timerPlay 打开文件对话框 OpenFileDialog odlgFile 系统托盘 NotifyIcon notifyIcon1
完成之后就可以进行编码了。
先贴代码
private void tsmiOpenFile_Click(object sender, EventArgs e)
{
//文件对话框初始路径
this.odlgFile.InitialDirectory = @"F:\音乐";
//文件过滤条件
this.odlgFile.Filter = "媒体文件|*.mp3;*.wav;*.wma;*.avi;*.mpg;*.asf;*.wmv";
if(odlgFile.ShowDialog()==DialogResult.OK)
{
//多个文件逐个加入播放列表中
for(int i = 0; i < odlgFile.FileNames.Length; i++)
{
string filePath = odlgFile.FileNames[i];
//判断播放列表中是否已经存在,否则跳过添加
if (!IsExistInList(filePath))
{
SongsInfo songsInfo = new SongsInfo(filePath);
lstSongList.Items.Add(songsInfo);
}
else
continue;
}
}
}
同时导入多个音频文件至播放列表需要注意要讲openFileDialog的Multiselect设置为true。
歌曲导入程序后,我们显然需获取并储存每首歌曲的各种信息,以便之后需要能够直接读取,所以在这里新一个SongsInfo类来存储歌曲的各种信息(歌曲名、路径、歌手等等)
SongsInfo类的代码如下:
using Shell32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SimpleMediaPlayer
{
class SongsInfo
{
private string fileName; //1
private string filePath;
private string filesize; //2
private string artist; //13
private string album; //14
private string year; //15
private string originName; //21
private string duration; //27
private string byteRate; //28
public string FileName { get => fileName; set => fileName = value; }
public string FilePath { get => filePath; set => filePath = value; }
public string Filesize { get => filesize; set => filesize = value; }
public string Artist { get => artist; set => artist = value; }
public string Album { get => album; set => album = value; }
public string Year { get => year; set => year = value; }
public string OriginName { get => originName; set => originName = value; }
public string Duration { get => duration; set => duration = value; }
public string ByteRate { get => byteRate; set => byteRate = value; }
public SongsInfo(string fPath)
{
SetSongInfo(fPath);
}
public void SetSongInfo(string strPath)
{
try
{
ShellClass sh = new ShellClass();
Folder dir = sh.NameSpace(Path.GetDirectoryName(strPath));
FolderItem item = dir.ParseName(Path.GetFileName(strPath));
fileName = dir.GetDetailsOf(item, 0);
if (fileName == string.Empty)
fileName = "未知";
FilePath = strPath;
filesize = dir.GetDetailsOf(item, 1);
if (filesize == string.Empty)
filesize = "未知";
artist = dir.GetDetailsOf(item, 13);
if (artist == string.Empty)
artist = "未知";
album = dir.GetDetailsOf(item, 14);
if (album == string.Empty)
album = "未知";
year = dir.GetDetailsOf(item, 15);
if (year == string.Empty)
year = "未知";
OriginName = dir.GetDetailsOf(item, 21);
if (OriginName == string.Empty)
OriginName = "未知";
duration = dir.GetDetailsOf(item, 27);
if (duration == string.Empty)
duration = "未知";
byteRate = dir.GetDetailsOf(item, 28);
if (byteRate == string.Empty)
byteRate = "未知";
//可自行将内容输出到控制台中查看情况
//for (int i = -1; i < 50; i++)
//{
// string str = dir.GetDetailsOf(item, i);
// Console.WriteLine(i + " && " + str);
//}
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
在这个类中我们使用ShellClass来获取文件属性(虽然我也试过其他方法,如读取MP3文件最后128字节中所存储的信息,尝试很久没能成功,ShellClass相对更简单直接)
①首先映入COM组件
打开解决方案资源管理器->引用->添加引用->COM->类型库->勾选Microsoft Shell Controls And Automation;
②设置Shell32的嵌入互操作类型为Fasle,否则会引起互操作类型异常
选中Shell32引用->属性->设置嵌入互操作类型(Embed Interop Type)为False;
想要得到其他文件信息,网上资料很多。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SimpleMediaPlayer
{
public partial class Form1 : Form
{
AxWMPLib.AxWindowsMediaPlayer testAWM = new AxWMPLib.AxWindowsMediaPlayer();
SongsInfo currPlaySong = new SongsInfo(null);
public Form1()
{
InitializeComponent();
//委托WMP的播放状态改变事件
testAWM.PlayStateChange += new AxWMPLib._WMPOCXEvents_PlayStateChangeEventHandler(AxWmp_PlayStateChange);
}
private void Form1_Load(object sender, EventArgs e)
{
this.odlgFile.Multiselect = true;
//设置lstSongList(播放列表)的显示成员与值(listbox的数据绑定)
lstSongList.DisplayMember = "fileName";
lstSongList.ValueMember = "filePath";
ReloadStatus();
}
private void ReloadStatus()
{
txtSongName.Text = "Windows Media Player";
txtTime.Text = "00:00/00:00";
tsslCurrentPlayState.Text = "就绪";
tkbVol.Value = tkbVol.Maximum / 2;
lbVolumeVal.Text = "音量:";
tkbMove.Value = 0;
//默认选中第一首曲目
if(lstSongList.Items.Count>0 && lstSongList.SelectedIndex== -1)
{
lstSongList.SelectedIndex = 0;
}
}
}
//遍历播放列表查重
private bool IsExistInList(string path)
{
for(int i = 0; i < lstSongList.Items.Count; i++)
{
if(path == ((SongsInfo)lstSongList.Items[i]).FilePath)
return true;
}
return false;
}
//ListBox鼠标双击事件
private void lstSong_MouseDoubleClick(object sender, MouseEventArgs e)
{
int index = lstSongList.IndexFromPoint(e.Location);
if (index != ListBox.NoMatches)
Play(index);
}
之后还需要完善更多的功能(查看详细信息,优化显示,查看歌曲信息,右键菜单等)
//播放
private void btnPlay_Click(object sender, EventArgs e)
{
if (lstSongList.SelectedIndex >= 0)
{
Play(lstSongList.SelectedIndex);
}
else
MessageBox.Show("请选择要播放的曲目");
}
}
//暂停
private void btnPause_Click(object sender, EventArgs e)
{
AxWmp.Ctlcontrols.pause();
}
//停止
private void btnStop_Click(object sender, EventArgs e)
{
AxWmp.Ctlcontrols.stop();
}
//上一曲
private void btnBack_Click(object sender, EventArgs e)
{
if(lstSongList.SelectedIndex == -1)
{
MessageBox.Show("请先添加曲目至目录");
}
else if(lstSongList.SelectedIndex > 0)
{
AxWmp.Ctlcontrols.stop();
lstSongList.SelectedIndex -= 1;
Play(lstSongList.SelectedIndex);
}
else
{
AxWmp.Ctlcontrols.stop();
lstSongList.SelectedIndex = lstSongList.Items.Count - 1;
Play(lstSongList.SelectedIndex);
}
}
//下一曲
private void btnNext_Click(object sender, EventArgs e)
{
if (lstSongList.SelectedIndex == -1)
{
MessageBox.Show("请先添加曲目至目录");
}
else if (lstSongList.SelectedIndex < lstSongList.Items.Count-1)
{
AxWmp.Ctlcontrols.stop();
lstSongList.SelectedIndex += 1;
Play(lstSongList.SelectedIndex);
}
else
{
AxWmp.Ctlcontrols.stop();
lstSongList.SelectedIndex = 0;
Play(lstSongList.SelectedIndex);
}
}
private void Play(int index)
{
//除暂停重新播放以外,更改播放路径
if (AxWmp.playState.ToString() != "wmppsPaused")
{
currPlaySong = (SongsInfo)lstSongList.Items[index];
AxWmp.URL = currPlaySong.FilePath;
AxWmp.Ctlcontrols.play();
}
else
{
AxWmp.Ctlcontrols.play();
}
}
更友好的一点的显示需要将播放和暂停键整合,以及根据目前程序状态自动更新控制按钮的Enable属性,之后进行完善
private void timerPlay_Tick(object sender, EventArgs e)
{
string currPlayTime = null;
currPlayTime = AxWmp.Ctlcontrols.currentPositionString;
txtTime.Text = currPlayTime + "/" + currPlaySong.Duration.Remove(0, 3);
tkbMove.Value = (int)AxWmp.Ctlcontrols.currentPosition;
}
//音量条值改变事件
private void tkbVol_ValueChanged(object sender, EventArgs e)
{
AxWmp.settings.volume = tkbVol.Value;
lbVolumeVal.Text = tkbVol.Value.ToString() + "%";
}
//鼠标悬浮在音量条上则改变label显示音量值
private void tkbVol_MouseHover(object sender, EventArgs e)
{
lbVolumeVal.Text = tkbVol.Value.ToString() + "%";
}
//鼠标离开音量条,恢复label显示
private void tkbVol_MouseLeave(object sender, EventArgs e)
{
lbVolumeVal.Text = "音量:";
}
//进度条滑动事件
private void tkbMove_Scroll(object sender, EventArgs e)
{
AxWmp.Ctlcontrols.currentPosition = (double)this.tkbMove.Value;
}
暂时比较粗糙,能够实现基本功能,之后也要继续完善(音量条点击弹出,进度条的快进、快退等)
private void AxWmp_PlayStateChange(object sender, AxWMPLib._WMPOCXEvents_PlayStateChangeEvent e)
{
//Console.WriteLine("状态改变");
//Console.WriteLine(e.newState.ToString());
switch (e.newState)
{
case 0: // Stopped
tsslCurrentPlayState.Text = "未知状态";
break;
case 1: // Stopped
tsslCurrentPlayState.Text = "停止";
timerPlay.Stop();
ReloadStatus();
break;
case 2: // Paused
tsslCurrentPlayState.Text = "暂停";
timerPlay.Stop();
break;
case 3: // Playing
tsslCurrentPlayState.Text = "播放";
timerPlay.Start();
txtSongName.Text = currPlaySong.FileName;
txtTime.Text = "00:00/" + currPlaySong.Duration.Remove(0, 3);
tkbMove.Maximum = (int)AxWmp.currentMedia.duration;
break;
case 4: // ScanForward
tsslCurrentPlayState.Text = "ScanForward";
break;
case 5: // ScanReverse
tsslCurrentPlayState.Text = "ScanReverse";
break;
case 6: // Buffering
tsslCurrentPlayState.Text = "正在缓冲";
break;
case 7: // Waiting
tsslCurrentPlayState.Text = "Waiting";
break;
case 8: // MediaEnded
tsslCurrentPlayState.Text = "MediaEnded";
break;
case 9: // Transitioning
tsslCurrentPlayState.Text = "正在连接";
break;
case 10: // Ready
tsslCurrentPlayState.Text = "准备就绪";
break;
case 11: // Reconnecting
tsslCurrentPlayState.Text = "Reconnecting";
break;
case 12: // Last
tsslCurrentPlayState.Text = "Last";
break;
default:
tsslCurrentPlayState.Text = ("Unknown State: " + e.newState.ToString());
break;
}
}
在不同状态下所需要的操作,最好是放到case结构里去,因为执行到这部分时,播放器才算是正真的处于该状态下。这部分单独分块,留作之后对播放器的几种状态熟悉之后再来补充内容。
//系统托盘双击事件
private void notifyIcon1_DoubleClick(object sender, EventArgs e)
{
this.Visible = true;
notifyIcon1.Visible = false;
this.WindowState = FormWindowState.Normal;
}
//窗口尺寸改变事件
private void Form1_SizeChanged(object sender, EventArgs e)
{
if(this.WindowState == FormWindowState.Minimized)
{
this.Visible = false;
notifyIcon1.Visible = true;
}
}
//右键菜单显示主界面单击事件
private void tsmiOpenForm_Click(object sender, EventArgs e)
{
this.Visible = true;
notifyIcon1.Visible = false;
this.WindowState = FormWindowState.Normal;
}
//右键菜单退出单击事件
private void tsmiQuit_Click(object sender, EventArgs e)
{
this.Close();
}
//程序关闭后必须手动释放notif资源,否则会造成系统托盘图标堆积
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
notifyIcon1.Dispose();
}
系统托盘的图标在notifyIcon空间的Icon属性里设置,并未ContextMenu添加项(显示主界面tsmiOpenForm、退出tsmiQuit),并通过notifyIcon的ContextMenuStrip属性绑定到notifyIcon控件上。系统托盘主要依赖以上几个事件就能够完成,但是右键菜单选项还不够完善,之后还需进行编写。
以上整个最基础的音频播放器就算是编写完成了,能够实现基本的功能,之后还会对播放器的循环、播放列表同步、音量条和进度条的显示进行优化。
如果你对本篇文章感兴趣,你可以继续浏览以下两篇博客,以增强这个播放器的功能和外观。
C#-WinForm-简单的音频播放器(二)
C#简单音乐播放器(三)
项目下载地址:https://gitee.com/Rhine/Simple_MediePlayer