前言:
该程序通过WinForm实现了一个简单的音乐播放器功能,用户可以通过该程序免费收听自己喜欢的歌曲,其特点和优势在于支持各种格式音乐文件的播放,同时还支持音乐文件下载,其中主要包括以下功能:
1. 能够读取mp3,flac,wav,ogg文件,并播放其中的音频。
2. 能够处理可能出现的异常,如文件不存在、文件读取错误等。
3. 具有良好的用户界面,方便用户进行操作。
4. 具有良好的兼容性,能在不同版本的C#中正常运行。
5. 能够下载网上的音乐文件到本地。(更新版)
目录
一、效果展示
- 用户交互界面
- 点击选择按钮
将自动跳转到系统文件夹,用户可以多选想要导入到该音乐播放器的音乐
支持mp3,flac,wav,ogg格式
导入成功
系统会显示所有导入成功的音乐
用户可选择任意一首进行播放
- 点击暂停按钮
系统将停止播放音乐
- 点击下一首按钮
自动播放下一首歌曲,如果当前为最后一首,则跳转到第一首播放
- 点击上一首按钮
自动播放上一首歌曲,如果当前为第一首,则跳转到最后一首播放
- 拖动音量调节器
可以自行调整音量的大小,调节范围:0-100
- 下载功能
在文本框中输入想要下载的音乐的下载地址,点击下载按钮,播放器便会自动开始下载,并把文件下载的相关信息显示在下面,包括文件名、总大小等,用户可以实时监控下载进程
在这里,我们可以自定义我们想要下载的文件夹,下载完成后,我们便可以像上面选择本地音乐文件一样选择我们刚刚下载好的音乐文件进行播放,非常方便。
二、代码实现
预处理:
1、在COM组件中安装Windows Media Player
2、在NuGet包中安装NAudio.Voribis,NAduio(用作ogg文件的解析)
>>Step1 交互界面设计
在设计台中设计用户的交互界面
其中使用到的控件有:
1、listBox(显示列表)
2、Windows Music Player(音乐播放器)
3、openFileDialog(导入文件)
4、TrackBar(音量调节)
5、Button(进行操作)
6、PictureBox&&Panel&&Label(美化交互界面)
7、TextBox(输入下载地址)
8、ListView(显示下载信息)
设计好后如图:
>>Step2 代码功能实现
(一)音乐播放功能
1、设置初始变量
public partial class Form1 : Form
{
//文件存储路径
string[] files;
//本地音乐列表
List<string> LocalMusicLists = new List<string> { };
//用作音频输出
private WaveOutEvent outputDevice = null;
//用作读取OGG格式实例
private VorbisWaveReader vorbisReader = null;
2、定义处理音频播放停止事件和窗体关闭事件时的释放资源方式
// 播放停止事件处理器
// 当音频播放停止时触发
private void OnPlaybackStopped(object sender, StoppedEventArgs args)
{
// 通过Invoke确保在UI线程上执行
this.Invoke((MethodInvoker)(() =>
{
DisposeWave();
//出现异常
if (args.Exception != null)
{
//显示错误信息
MessageBox.Show($"播放时发生错误: {args.Exception.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}));
}
// 释放WaveOut和VorbisReader资源
private void DisposeWave()
{
if (outputDevice != null)
{
//如果当前播放状态不是停止,则先停止播放
if (outputDevice.PlaybackState != PlaybackState.Stopped)
{
outputDevice.Stop();
}
//释放outputDevice占用的资源
outputDevice.Dispose();
outputDevice = null;
}
if (vorbisReader != null)
{
// 释放vorbisReader占用的资源
vorbisReader.Dispose();
vorbisReader = null;
}
}
// 窗体关闭时的事件处理器
// 窗体即将关闭时触发
protected override void OnFormClosing(FormClosingEventArgs e)
{
// 调用基类的OnFormClosing方法
base.OnFormClosing(e);
// 释放音频资源
DisposeWave();
}
这一步为优化操作,这样做的好处有:
1、确保资源释放:通过在
OnPlaybackStopped
和OnFormClosing
事件处理器中调用DisposeWave
方法,可以确保在音频播放停止或窗体关闭时,相关的音频输出设备(outputDevice
)和音频读取器(vorbisReader
)资源得到正确的释放,有助于防止资源泄漏,尤其是当应用程序长时间运行或频繁打开和关闭时。2、线程安全:
OnPlaybackStopped
方法中使用Invoke
来确保更新UI或释放资源的操作在UI线程上执行,因为UI元素通常只能在其创建它们的线程(即UI线程)上被访问和修改,这样做确保了线程安全性。3、错误处理:如果播放时发生异常,通过
MessageBox
向用户显示错误信息。
3、 自定义musicplay播放函数
- 作用:根据提供的文件名来播放音乐文件
- 处理时根据文件的后缀名设置分支,分为ogg格式和其它格式两种情况
- 如果在播放文件过程中发生异常(例如文件不存在、文件损坏等),则捕捉并抛出
private void musicplay(string filename)
{
try
{ //尝试播放新音乐之前确保先释放所有旧资源
DisposeWave();
//获取文件扩展名
string extension = Path.GetExtension(filename);
//判断文件后缀名
if (extension == ".ogg")
{
//处理ogg文件
OggDeal(filename);
}
else
{
//直接播放
axWindowsMediaPlayer1.URL = filename;
axWindowsMediaPlayer1.Ctlcontrols.play();
}
}
catch (Exception ex)
{
MessageBox.Show($"无法播放音乐文件 {filename}: {ex.Message}", "播放错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
4、自定义ogg情况处理函数
- 作用:处理ogg格式音乐文件,增加解析格式多样性
- 将ogg文件的读取、初始化、播放封装到该函数中
- 如果在播放文件过程中发生异常(例如文件不存在、文件损坏等),则捕捉并抛出
//处理ogg情况
private void OggDeal(string oggFilePath)
{
//释放之前的资源
DisposeWave();
try
{
//初始化vorbis格式读取器和输出设备
vorbisReader = new VorbisWaveReader(oggFilePath);
outputDevice = new WaveOutEvent();
//处理播放停止事件
//订阅事件
outputDevice.PlaybackStopped += OnPlaybackStopped;
//初始化音频设备
outputDevice.Init(vorbisReader);
//开始播放音频
outputDevice.Play();
}
catch (Exception ex)
{
MessageBox.Show($"播放音频文件时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
//发生错误时,释放资源
DisposeWave(); //
}
}
5、自定义listbox函数
- 作用:响应列表框中选择项变化的事件,方便与用户进行交互
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
//检查列表是否非空
if (LocalMusicLists.Count>0)
{
axWindowsMediaPlayer1.URL=LocalMusicLists[listBox1.SelectedIndex];//从选定的歌曲开始放
musicplay(axWindowsMediaPlayer1.URL);//选中控制栏上的按钮
label1.Text=Path.GetFileNameWithoutExtension(LocalMusicLists[listBox1.SelectedIndex]);//显示当前播放歌曲的歌名
}
}
6、button1——选择功能
- 作用:让用户通过一个对话框选择文件,并将这些文件添加到播放列表和用户界面的列表框中
- 在文件加载过程中若发生异常,则捕捉并抛出
//选择按钮
private void button1_Click(object sender, EventArgs e)
{
try
{
//过滤器
openFileDialog1.Filter = "选择音频|*.mp3;*.flac;*.wav;*.ogg";
//打开多选属性,使得一次可以选中多首歌
openFileDialog1.Multiselect = true;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
//清除上一次的结果
listBox1.Items.Clear();
//取多个文件
files = openFileDialog1.FileNames;
foreach (string x in files)
{
//添加至listbox
listBox1.Items.Add(x);
//添加到播放列表
LocalMusicLists.Add(x);
}
}
}
catch (Exception ex)
{
MessageBox.Show($"加载音频文件时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
7、 button2——停止播放功能
- 作用:控制音乐播放的暂停
- 分两种情况进行处理:ogg格式和其他格式
//停止播放按钮
private void button2_Click(object sender, EventArgs e)
{
//分情况处理:ogg or 其它
//用Windows Media Player控件播放的情况
if (axWindowsMediaPlayer1.playState == WMPLib.WMPPlayState.wmppsPlaying)
{
axWindowsMediaPlayer1.Ctlcontrols.pause();
}
//用NAudio库播放的OGG文件的情况
else if (outputDevice != null && outputDevice.PlaybackState == PlaybackState.Playing)
{
outputDevice.Pause();
}
}
8、button3——下一首功能
- 作用:控制音乐器播放下一首
- 如果到达末尾,则调到第一首播放
//下一首按钮
private void button3_Click(object sender, EventArgs e)
{
//目的一:播放下一曲
//目的二:如果到达末尾,则调到第一首播放
try
{
// 检查列表是否有音乐
if (LocalMusicLists.Count > 0)
{
//如果没有选中的项,默认从第一首开始,否则选中下一首
int currentIndex = listBox1.SelectedIndex;
currentIndex = currentIndex == -1 ? 0 : currentIndex + 1;
//到达最后一首歌曲时,循环到第一首
if (currentIndex >= LocalMusicLists.Count)
{
currentIndex = 0;
}
string nextSong = LocalMusicLists[currentIndex];
musicplay(nextSong);
label1.Text = Path.GetFileNameWithoutExtension(nextSong);
listBox1.SelectedIndex = currentIndex;
}
else
{
MessageBox.Show("播放列表中没有歌曲。", "播放信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
catch (Exception ex)
{
MessageBox.Show($"切换歌曲时出错: {ex.Message}", "播放错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
9、button4——上一首功能
- 作用:控制音乐器播放上一首
- 如果在第一首,则调到最后一首播放
//上一首按钮
private void button1_Click_1(object sender, EventArgs e)
{
//目的一:播放上一曲
//目的二:如果在第一首,则跳到最后一首播放
try
{
//检查列表是否有音乐
if (LocalMusicLists.Count > 0)
{
//获取当前选中的项索引
int currentIndex = listBox1.SelectedIndex;
//如果没有选中的项,默认从最后一首开始,否则选中上一首
currentIndex = currentIndex == -1 ? LocalMusicLists.Count - 1 : currentIndex - 1;
//到达第一首歌曲时,跳到最后一首
if (currentIndex < 0)
{
currentIndex = LocalMusicLists.Count - 1;
}
string previousSong = LocalMusicLists[currentIndex];
musicplay(previousSong);
label1.Text = Path.GetFileNameWithoutExtension(previousSong);
listBox1.SelectedIndex = currentIndex;
}
else
{
MessageBox.Show("播放列表中没有歌曲。", "播放信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
catch (Exception ex)
{
MessageBox.Show($"切换歌曲时出错: {ex.Message}", "播放错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
10、TtackBar——音量调节功能
- 作用:通过拖动滑块调整媒体播放器的音量
- 在TrackBar的属性中可以更改调节的最大值、最小值、默认值
- 本程序采用的范围为:0-100
//音量调节功能
private void trackBar1_Scroll(object sender, EventArgs e)
{
//设置音量调节方式
axWindowsMediaPlayer1.settings.volume=trackBar1.Value;
}
(二)音乐文件下载功能
1、自定义一个文件下载类
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace Gac
{
public class DownLoadFile
{
public int ThreadNum = 3;
List<Thread> list = new List<Thread>();
public DownLoadFile()
{
//订阅
doSendMsg += Change;
}
//处理下载消息变化
private void Change(DownMsg msg)
{
//根据消息标志处理
if (msg.Tag==DownStatus.Error||msg.Tag==DownStatus.End)
{
//重新启动
StartDown(1);
}
}
//添加下载任务到队列
public void AddDown(string DownUrl,string Dir, int Id = 0,string FileName="")
{
//创建新线程
Thread tsk = new Thread(() =>
{
download(DownUrl, Dir, FileName,Id);
});
list.Add(tsk);
}
//启动下载任务
public void StartDown(int StartNum=3)
{
for (int i2 = 0; i2 < StartNum; i2++)
{
lock (list)
{
for (int i = 0; i < list.Count; i++)
{
//启动未开始或暂停的线程
if (list[i].ThreadState == System.Threading.ThreadState.Unstarted || list[i].ThreadState == ThreadState.Suspended)
{
list[i].Start();
break;
}
}
}
}
}
//声明事件委托
public delegate void dlgSendMsg(DownMsg msg);
//下载消息事件
public event dlgSendMsg doSendMsg;
//public event doSendMsg;
//public dlgSendMsg doSendMsg = null;
private void download(string path, string dir,string filename,int id = 0)
{
try
{
//发送下载开始消息
DownMsg msg = new DownMsg();
msg.Id = id;
msg.Tag = 0;
doSendMsg(msg);
//初始化文件下载器
FileDownloader loader = new FileDownloader(path, dir, filename, ThreadNum);
loader.data.Clear();
//发送下载开始消息并获取文件大小
msg.Tag = DownStatus.Start;
msg.Length = (int)loader.getFileSize(); ;
doSendMsg(msg);
//初始化下载进度监听器
DownloadProgressListener linstenter = new DownloadProgressListener(msg);
linstenter.doSendMsg = new DownloadProgressListener.dlgSendMsg(doSendMsg);
//执行文件下载
loader.download(linstenter);
}
catch (Exception ex)
{
//发送下载错误信息
DownMsg msg = new DownMsg();
msg.Id = id;
msg.Length = 0;
msg.Tag =DownStatus.Error;
msg.ErrMessage = ex.Message;
doSendMsg(msg);
Console.WriteLine(ex.Message);
}
}
}
}
首先我们自己定义一个文件下载的类,这是我们实现音乐下载功能的核心和灵魂,该类主要的目的是实现一个简单的多线程文件下载器,它能同时下载多个文件,并且在下载过程中可以处理异常和发送状态信息,提高下载效率,其中包括以下几个核心方法:
-
AddDown 方法: 将一个新的下载任务添加到任务列表中,每个任务都是一个独立的线程。
-
StartDown 方法: 启动指定数量的下载任务。如果有未启动或暂停的任务,就启动它们。
-
download 方法: 实际执行文件下载的方法。在这里,首先发送下载开始的消息,然后初始化文件下载器和进度监听器,最后执行文件下载操作。
需要特别注明的是,这里给出的只是文件下载这个类中的核心代码,即DownLoadFile部分,但是在代码中我们可以看到,我们还使用了一些其它的自定义的类,比如通过DownloadProgressListener来实现文件下载时的实时监测进度功能, DownloadThreadFile和Downloader来实现一个多线程文件下载器的功能等,可以自行前往git仓库查看。
2、引用自定义的文件下载类
我们将上述自定义的文件下载类封装后,便可在音乐播放器的项目中对其进行引用,这样我们就可以通过我们自定义的文件下载类很方便得实现音乐文件下载的功能。
3、创建一个DownLoadFile对象
DownLoadFile dlf = new DownLoadFile();
4、初始化df对象的设置
//初始化df对象的设置
private void Form1_Load(object sender, EventArgs e)
{
//设置下载文件对象的线程数量为3
dlf.ThreadNum = 3;
//订阅SendMsgHander事件
dlf.doSendMsg += SendMsgHander;
}
-
设置线程数量:通过设置 ThreadNum 属性,可以控制下载过程中的并行度,以优化下载性能和资源利用。
-
事件订阅:通过订阅 doSendMsg 事件,并指定事件处理方法,可以响应下载过程中的特定事件,例如发送消息或更新界面状态。
5、定义函数实时监控和更新文件下载状态
private void SendMsgHander(DownMsg msg)
{
//根据消息标签不同进行不同处理
switch (msg.Tag)
{
case DownStatus.Start:
//在主线程上更新界面,显示开始下载的状态和时间
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[8].Text = "开始下载";
listView1.Items[msg.Id].SubItems[7].Text = DateTime.Now.ToString();
});
break;
case DownStatus.GetLength:
//在主线程上更新界面,显示获取到文件长度信息和连接成功状态
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[3].Text = msg.LengthInfo;
listView1.Items[msg.Id].SubItems[8].Text = "连接成功";
});
break;
case DownStatus.End:
case DownStatus.DownLoad:
//在主线程上更新界面,显示下载进度、速度、剩余时间等信息
this.Invoke(new MethodInvoker(() =>
{
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[2].Text = msg.SizeInfo;
listView1.Items[msg.Id].SubItems[4].Text = msg.Progress.ToString() + "%";
listView1.Items[msg.Id].SubItems[5].Text = msg.SpeedInfo;
listView1.Items[msg.Id].SubItems[6].Text = msg.SurplusInfo;
if (msg.Tag == DownStatus.DownLoad)
{
listView1.Items[msg.Id].SubItems[8].Text = "下载中";
}
else
{
listView1.Items[msg.Id].SubItems[8].Text = "下载完成";
}
//保持界面更新
Application.DoEvents();
});
}));
break;
//错误情况
case DownStatus.Error:
//在主线程上更新界面,显示下载失败状态和错误消息
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[6].Text = "失败";
listView1.Items[msg.Id].SubItems[8].Text = msg.ErrMessage;
//保持界面更新
Application.DoEvents();
});
break;
}
}
这段代码的主要目的是在文件下载过程中,实时更新用户界面,反映不同下载状态,并处理相关事件通知。具体而言,它通过订阅下载文件对象的事件来响应不同的下载状态,包括开始下载、获取文件长度、下载中、下载完成和错误处理。每个状态变化都会通过ListView反映给用户,以便他们了解当前下载进度和状态。
-
Invoke方法:在多线程编程中,直接操作界面元素可能导致跨线程异常。因此,此处使用了 Invoke 方法来确保所有界面更新操作都在主线程上进行。这样做可以避免跨线程访问异常,保证界面更新的稳定性和可靠性。
-
Application.DoEvents():在界面更新后,使用 Application.DoEvents()确保处理界面事件,以便立即反映界面的变化。这对于保持界面的动态响应性非常重要,尤其是在长时间的操作过程中,能够让用户及时看到状态的变化。
6、定义下载button的功能
private void button2_Click_1(object sender, EventArgs e)
{
try
{
//从txetbox获取用户输入的文本链接
string downloadUrl = textBox1.Text;
//对下载链接进行URI编码
string path = Uri.EscapeUriString(downloadUrl);
//检查下载链接是否为空
if (string.IsNullOrEmpty(downloadUrl))
{
MessageBox.Show("请输入有效的下载链接。", "下载错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
//从下载链接中自动获取文件名
string filename = Path.GetFileName(path);
//自定义文件保存的位置
string dir = @"D:\windows程序设计";
//向 ListView 中添加新的下载项,显示初始状态信息
ListViewItem item = listView1.Items.Add(new ListViewItem(new string[] { (listView1.Items.Count + 1).ToString(), filename, "0", "0", "0%", "0", "0", DateTime.Now.ToString(), "等待中", downloadUrl }));
//获取当前下载项在ListView中的索引
int id = item.Index;
//调用下载管理器的方法,添加下载任务
dlf.AddDown(path, dir, id, id.ToString());
//启动下载管理器开始下载任务
dlf.StartDown();
}
catch (Exception ex)
{
MessageBox.Show($"下载过程中发生错误: {ex.Message}", "下载错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
这段代码的主要目的是实现用户输入下载链接后的文件下载功能。具体而言,代码首先获取用户输入的下载链接,然后进行一系列的处理,包括对下载链接进行 URI 编码、检查链接是否为空、提取文件名、设置文件保存目录、在界面上添加下载任务等。最后,调用下载管理器开始下载任务,并处理可能出现的异常。
-
Uri.EscapeUriString():对下载链接进行 URI 编码,以确保链接格式正确。
-
Path.GetFileName(path):通过该方法从 URI 中提取文件名。
-
dlf.AddDown() & dlf.StartDown():调用下载管理器中的方法,添加下载任务并开始下载过程。
至此,音乐文件下载部分的全部代码我们就已经完成了,我们通过自定义一个文件下载类,巧妙地将其插入到音乐播放器中,使播放器的功能更加强大,不仅支持多种格式的音乐播放,还可以下载音乐文件到本地。
三、总结
本程序通过 WinForm 实现了一个多媒体播放器,用户可以通过简洁的图形界面播放 MP3 和 OGG 音乐文件,同时支持通过网络链接进行音乐文件的下载。程序设计充分考虑了易用性、兼容性和异常处理,旨在提供稳定且用户友好的音乐播放体验。