一、引言
- 音乐播放是生活日常中最常见的娱乐和经常使用的操作,我们应当了解和实现在Windows操作环境下,使用C#语言实现窗体上的音乐播放。
- 该博客得到目标是基本实现类似音乐app的音乐播放的一些基本但核心的功能,对控件的学习和实现进行进一步的练习和掌握。
二、开发环境搭建
- 使用的开发工具:Visual Studio 需要自己配置的库:NAudio,NAudio.Vorbis。这是两个不同的库,作者不一样,要注意!
- 安装和配置:在Visual Studio中,右键点击你的项目 -> 选择“管理NuGet程序包” -> 搜索“NAudio” -> 点击“安装”,NAudio.Vorbis,安装操作同上。
三、Windows窗体设计基础
- Windows窗体设计不同于在运行框内直接运行,是设计窗体界面,通过添加各种控件(显示),对于各种控件有不同的动作(如点击),不同的动作可以有不同的方法,再进行代码实现。
- 在VS页面选择创建新项目,选择Windows窗体应用(.NET Framework)进行创建。
- 本次博客中使用到的控件主要有:openFiledialog,listBox,axWindowsMediaPlayer,button,label,trackBar等。
- 简要窗体:
四、音乐播放功能实现
思路讲解:我们进行的是本地的音乐文件播放,并不是网页音乐数据流的播放(后续再发布博客进行实现),所以我们需要本地的音乐文件,对于音乐播放来说,文件的区别在于:ogg文件和其他文件。对于其他文件,比较好处理,可以直接使用axWindowsMediaPlayer进行播放和停止;对于ogg文件需要使用到NAudio和NAudio.Vorbis库的操作进行播放和停止。这part进行两者分开的操作。
- 4.1 加载音乐文件
- 对于ogg文件,我们创建控件button并改名为:播放ogg。在该控件内直接实现ogg文件的播放,定义string变量用来接受ogg文件,使用openFiledialog来选择文件,格式如下,并要求了文件格式。使用该种方法来播放ogg文件,用reader来解析ogg文件,再声明waveOut,再添加该reader:waveOut.Init(reader),再进行播放,实现跟axWindowsMediaPlayer一样的功能。
-
private void button5_Click(object sender, EventArgs e) { string oggFilePath ="" ; OpenFileDialog openFileDialog= new OpenFileDialog(); openFileDialog.Filter = "播放音频|*.ogg"; if(openFileDialog.ShowDialog() == DialogResult.OK) { oggFilePath=openFileDialog.FileName; } try { using (var reader =new VorbisWaveReader(oggFilePath)) { using (var waveOut = new WaveOutEvent()) { waveOut.Init(reader); waveOut.Play(); // 等待播放完成,或者你可以添加其他逻辑,比如监听播放状态变化事件 while (waveOut.PlaybackState == PlaybackState.Playing)//线程 { System.Threading.Thread.Sleep(1000); } } } } catch (Exception ex) { Console.WriteLine("发生错误: " + ex.Message); } }
对于其他格式的音乐播放,我们使用常规方法,先是button打开文件进行添加音乐,将添加的音乐用一个文件数组来接收,并放在listBox中,也就是像音乐软件中的歌单一样。事先先定义两个全局变量,一个用来放音乐文件的,一个是歌单(跟listBox里显示的同步,方便后面获取点击到的歌曲文件)。
-
string[] files; List<string> localmusiclist=new List<string>();
-
private void button1_Click(object sender, EventArgs e) { openFileDialog1.Filter = "选择音频|*mp3;*.wav;*.flac"; openFileDialog1.Multiselect= true; if (files != null) { Array.Clear(files, 0, files.Length);//范类的约束,置零 } if (openFileDialog1.ShowDialog() == DialogResult.OK) { //listBox1.Items.Clear();//列表清空 files = openFileDialog1.FileNames; string[] array = files;//复制 foreach(string x in array)//添加到歌单中 { listBox1 .Items.Add(x); localmusiclist.Add(x); } } }
- 4.2 播放和暂停控制
- 对于该示例中ogg文件是直接一直播放的,只能等播放完了才停止(后面讲解改正),所以我们实现对其他文件的常规播放的播放和暂停处理,我们已经有了listBox(歌单),我们想要做到点击歌单里的任意歌曲都进行播放,我们的思路是实现一个播放音乐的方法musicplay,在listBox里进行调用,我们获取listBox当前点击的文件的下标,用localmusiclist(index)传入给axWindowsMediaPlayer.URL,再axWindowsMediaPlayer.Play;进行播放,代码示例如下:
-
public void musicplay(string filename)//播放 { string extension=Path.GetExtension(filename);//得到它后缀名,可以作扩展,别的文件就可以有分支做别的播放处理 if (extension == ".ogg") { Console.WriteLine("this is ogg file."); } else { Console.WriteLine("this is not ogg file."); axWindowsMediaPlayer1.Ctlcontrols.play();//播放 } }
private void listBox1_SelectedIndexChanged(object sender, EventArgs e) { if (localmusiclist.Count > 0)//保证有音乐文件被选中 { axWindowsMediaPlayer1.URL = localmusiclist[listBox1.SelectedIndex]; musicplay(localmusiclist[listBox1.SelectedIndex]); label1.Text = Path.GetFileNameWithoutExtension(localmusiclist[listBox1.SelectedIndex]); } }
完成这两部分代码后,在添加了音乐文件(不是ogg)到列表当中去之后,再点击列表当中的任意歌曲,就能进行播放,并且URL每点击都更改,所以当前播放了音乐后,再点击另一首,就会自动停止当前的歌曲,播放选择的歌曲。也就是说axWindowsMediaPlayer.Play是根据它的URL 来播放的。
-
当然,我们也可以实现停止音乐的button控件,比较简单,直接使用axWindowsMediaPlayer的停止功能,示例如下:
-
private void button2_Click(object sender, EventArgs e) { axWindowsMediaPlayer1.Ctlcontrols.stop(); }
- 4.3音量控制
- 音量控制有专门的滑块控件,将其与axWindowsMediaPlayer的音量属性建立连接,就可以通过滑动滑块来控制音量,同时我们也可以将音量显示在一个label控件上,示例如下:
-
private void trackBar1_Scroll(object sender, EventArgs e) { axWindowsMediaPlayer1.settings.volume= trackBar1.Value; label2.Text= trackBar1.Text; }
- 4.4. 下一首/上一首功能
- 实现下一首或上一首的思路:在一个button控件里,从listBox里获取下一个文件的下标,再用localmusiclist(index)获取文件,将文件给到axWindowsMediaPlayer.URL,再进行播放,此外一个注意点是到达最后一首,即最大下标后,要转为列表第一首,这也符合现实生活。示例如下:
-
private void button3_Click(object sender, EventArgs e) { int nextIndex = listBox1.SelectedIndex + 1;//当前下标加一 if(nextIndex >= localmusiclist.Count)//超过了回到第一首 { nextIndex = 0; } axWindowsMediaPlayer1.URL = localmusiclist[nextIndex]; musicplay(axWindowsMediaPlayer1.URL); label1.Text = Path.GetFileNameWithoutExtension(localmusiclist[nextIndex]); listBox1.SelectedIndex = nextIndex; }
上一首功能与下一首功能类似,不做过多阐述。
五、出现的问题和改进思路
- 我们基本实现了对所有音乐文件的播放和基本功能实现,但我们会发现一些问题:
- 1.在该示例中,ogg文件点击播放后就一直在播放,而且在其播放过程中不能进行其他的操作。
- 2.我们不能把ogg文件也放在列表即歌单里,跟其他音乐文件一样的进行播放,停止,下一首等功能,这明显与现实生活我们要求的不同。
- 改进思路:在播放ogg文件时,不能进行其他操作,是因为播放的方式是单线程的,或者说线程被阻塞,不能进行其他操作,所以我们要改进播放方式,并且我们不想ogg文件是单独播放,所以我们在添加文件的时候,扩展到ogg文件也能添加进列表,再将改进后的播放方式也放在musicplay方法中,再在listBox里点击就可以进行播放。注意:由于ogg文件和其他文件属于两种不同的播放方式,所以播放方式不同,停止方式也不同,所以进行操作时,如在列表中当前播放的是ogg,再选择另一首如MP3,一般都要对现在播放的文件进行后缀判断,再进行停止,不然会出现两首歌都播放的情况。
六、改进后功能的实现
- 6.1 添加音乐文件
两个全局变量不变,对button(添加文件)进行修改,修改后示例如下:
-
private void button1_Click(object sender, EventArgs e) { openFileDialog1.Filter = "选择音频|*mp3;*.wav;*.flac;*.ogg"; string extension = Path.GetExtension(openFileDialog1.FileName); openFileDialog1.Multiselect= true; if (files != null) { Array.Clear(files, 0, files.Length);//范类的约束,置零 } if (openFileDialog1.ShowDialog() == DialogResult.OK) { //listBox1.Items.Clear();//列表清空 if (extension == ".ogg") { files = openFileDialog1.FileNames; } else { files = openFileDialog1.FileNames; } string[] array = files; foreach(string x in array) { listBox1 .Items.Add(x); localmusiclist.Add(x); } } }
- 6.2.播放和暂停控制
在播放方法musicplay中,将修改后的多线程的播放ogg方法放入里面,并同时注意:此时musicplay 的返回类型有一点变化。并且我们需要将reader和waveOut设为全局变量。并为了实现播放的停止控制,增加一个全局变量isPlaying。代码如下:
private WaveOutEvent waveOut;
private VorbisWaveReader reader;
private bool isPlaying = false;
public async void musicplay(string filename)//播放
{
string extension=Path.GetExtension(filename);//得到它后缀名,可以作扩展,别的文件就可以有分支做别的播放处理
if (extension == ".ogg")
{
Console.WriteLine("this is ogg file.");
try
{
using (reader = new VorbisWaveReader(filename))
{
using (waveOut = new WaveOutEvent())
{
waveOut.Init(reader);
waveOut.Play();
isPlaying = true;
await Task.Run(() =>
{
while (waveOut.PlaybackState == PlaybackState.Playing)
{
Thread.Sleep(100); // 这里仍然需要Sleep,但它在后台线程上运行
}
});
}
}
}
catch (Exception ex)
{
Console.WriteLine("发生错误: " + ex.Message);
}
}
else
{
Console.WriteLine("this is not ogg file.");
axWindowsMediaPlayer1.Ctlcontrols.play();
isPlaying=true;
}
}
ogg文件不能通过像axWindowsMediaPlayer那样修改了其URL就直接停止播放了,所以我们要定义一个停止的方法,示例如下:
private void StopAudio()//ogg文件播放停止方法
{
if (isPlaying)
{
waveOut.Stop();
waveOut.Dispose();
reader.Dispose();
isPlaying = false;
}
}
在listBox里实现点击任何一个首歌,停止当前播放的歌曲,再进行播放选择的歌曲,所以要先进性判断,当前播放的是ogg的话,就停止播放。只需这一个判断即可,因为我们后续的操作是将选择的文件放入URL中,如果当前是其他文件,说明是axWindowsMediaPlayer播放的,那么URL修改了,那么就会停止的。示例如下:
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (localmusiclist.Count > 0)//保证有音乐文件被选中
{
string extension = Path.GetExtension(axWindowsMediaPlayer1.URL);
if (isPlaying)
{
if (extension == ".ogg")
{
StopAudio();
}
}
axWindowsMediaPlayer1.URL = localmusiclist[listBox1.SelectedIndex];
musicplay(localmusiclist[listBox1.SelectedIndex]);
label1.Text = Path.GetFileNameWithoutExtension(localmusiclist[listBox1.SelectedIndex]);
}
}
当然也可以点击停止button,修改如下:
private void button2_Click(object sender, EventArgs e)
{
string extension = Path.GetExtension(axWindowsMediaPlayer1.URL);
if (isPlaying)
{
if (extension == ".ogg")
{
StopAudio();
}
else
{
axWindowsMediaPlayer1.Ctlcontrols.stop();
}
}
}
6.3.下一首和上一首
也比较简单,就是不同的播放方式,所以要先判断当前播放的是什么文件,是ogg 的话,调用停止方法,再跟之前一样。示例如下:
private void button3_Click(object sender, EventArgs e)
{
string extension = Path.GetExtension(axWindowsMediaPlayer1.URL);
if (extension == ".ogg")
{
StopAudio();
}
int nextIndex = listBox1.SelectedIndex + 1;
if(nextIndex >= localmusiclist.Count)
{
nextIndex = 0;
}
axWindowsMediaPlayer1.URL = localmusiclist[nextIndex];
musicplay(axWindowsMediaPlayer1.URL);
label1.Text = Path.GetFileNameWithoutExtension(localmusiclist[nextIndex]);
listBox1.SelectedIndex = nextIndex;
}
上一首功能不再赘述。
改进的主要思路重点:线程的改进,ogg加入进来之后两种播放方式的协调。
七、附录
源代码仓库地址:田扬帅/windows程序设计 仓库里的WindowsMusic1即是源码。
感谢收看!