前言
在之前的文章中,简单介绍了音乐播放器,那篇文章的音乐播放器主要是用于播放本地音乐文件,可以播放.mp3和.ogg文件。
本篇文章将接着完善音乐播放器的功能,新增网络连接和音乐下载功能。
目录
一、知识点讲解
1、网络连接
这里是一个简单的网络连接的示例:
string url = "https://xxx"; // API地址
定义一个字符串变量 url,存储API地址。
using (HttpClient client = new HttpClient())
{
}
用 HttpClient 类建立一个 HTTP 客户端 client,并使用 using 语句确保在使用完毕后释放相关资源。
try
{
HttpResponseMessage response = await client.GetAsync(url); // 发送GET请求
response.EnsureSuccessStatusCode(); // 确保请求成功
string responseBody = await response.Content.ReadAsStringAsync(); // 读取响应内容
Console.WriteLine(responseBody); // 输出响应内容
}
使用 client 发送一个异步的 GET 请求到 url 指定的地址,并将返回的 HttpResponseMessage 对象存储在 response 变量中。
await 关键字用于等待异步操作完成,使得程序不会阻塞在此处,而是允许主线程继续执行其他任务。
使用 response 的 Content 属性读取响应内容,并异步将其作为字符串读取到 responseBody 变量中。ReadAsStringAsync() 方法用于读取响应内容并返回一个 Task<string> 对象,使用 await 关键字等待异步操作完成。
catch (HttpRequestException e)
{
Console.WriteLine($"HTTP请求失败:{e.Message}");
}
如果在 try 块中发生 HttpRequestException 异常,执行 catch 块中的代码。
2、异步操作
(1)同步和异步
同步:在同步编程中,一个任务的执行是阻塞式的,即当前任务在执行时,其他任务必须等待当前任务完成后才能继续执行。
异步:在异步编程中,一个任务的执行不会阻塞其他任务的执行。当一个任务需要等待某个操作完成时,它会让出控制权,允许其他任务继续执行。当操作完成时,该任务继续执行。
(2)使用异步操作的原因
在点击按钮事件处理中需要异步操作的主要原因是避免阻塞UI 线程。
在桌面应用程序中,用户界面操作是在主线程上进行的。如果在 UI 线程上执行文件下载这样耗时的操作,会导致界面卡顿甚至崩溃,尤其是在网络条件不佳或者文件较大的情况下。
通过使用异步方法,并结合 await
关键字等待操作完成,可以确保界面保持响应性,用户可以继续与应用程序进行交互,同时文件下载等操作在后台进行。
3、URL获取
获取音乐的 URL 取决于音乐文件的来源和访问方式。以下是几种常见的情况和获取方法:
(1)公开的音乐资源
如果音乐是公开的、可以直接访问的资源,可以在浏览器中播放音乐时,右键点击播放器中的下载按钮或者直接右键点击播放器中的音乐,选择“复制音频地址”或类似选项,获取音乐的直接播放地址。
(2)API 或网站提供的音乐文件
有些网站或服务提供 API 或者特定的音乐文件访问接口。你可以查阅他们的文档来了解如何获取音乐文件的 URL。
例如某云提供API文档:
不仅是音乐下载,其余类型的文档也可以寻找对应的API
二、代码实现
1、代码分布讲解
(1)引入命名空间
网络连接主要使用 HttpClient
类来发起 HTTP 请求并获取响应。
因此引入命名空间中需要引入以下内容:
using System;
using NAudio.Wave;
using NAudio.Vorbis;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;
- System.IO:用于文件操作,在这次项目中,需要把下载的文件保存到本地
- System.Net.Http:包含 HttpClient 类,用于发送 HTTP 请求
- System.Threading.Tasks:用于异步操作
- System.Windows.Forms:用于 Windows 窗体应用程序
在之前的代码中,还添加了NAudio库和NAudio.Vorbis库,用于音乐文件的播放。
NAudio库和NAudio.Vorbis库的添加方法详见前篇的三.1.(1)。
(2)设置连接的网站
private const string audioUrl = "https://www.gqt.org.cn/ccylmaterial/song/200612/W020180622624077722652.mp3";
这里我选择了一个可以下载音乐文件的网站,将网址赋值给audioUrl字符串。
私有的常量字符串 audioUrl包含了一个音频文件的URL地址。这个地址指向一个位于 https://www.gqt.org.cn/ccylmaterial/song/200612/ 路径下的MP3音频文件,文件名是 W020180622624077722652.mp3。
URL地址用于指向具体的网络资源,即一个MP3音频文件。
(3)点击按键事件处理
若想实现点击button后,立即完成文件下载,那么可以在button1_Click中完成
private async void button1_Click(object sender, EventArgs e)
{
await DownloadAudioAsync(audioUrl);
}
我这里用的是button1,如果接着上次的代码来写,记得修改button控件。
这段代码是按钮 button1 的点击事件处理方法。使用 async 关键字标记为异步方法,因为涉及到异步操作,所以需要用 await 关键字来等待异步操作完成。
在Windows Forms应用程序中,直接在事件处理程序中使用async void方法没有返回Task或Task<TResult>,因此不能在外部等待或捕获由该方法引发的异常。
如果在下载音乐文件的函数方法中没有写异常捕获与处理的相关代码,那么应该在事件处理程序中捕获这些异常,以免应用程序崩溃。
以下是一个改进后的button1_Click事件处理程序的例子:
private async void button1_Click(object sender, EventArgs e)
{
try
{
await DownloadAudioAsync(audioUrl);
}
catch (Exception ex)
{
MessageBox.Show("音频下载失败:" + ex.Message);
}
}
(4)下载音乐文件方法
using (var httpClient = new HttpClient())
使用 HttpClient 类创建一个名为 httpClient 的实例,用于发起 HTTP 请求。
using (var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
发起一个 GET 请求,并通过 await 关键字等待响应返回。
response.EnsureSuccessStatusCode();
确保响应状态码为成功状态,否则抛出异常。
using (var stream = await response.Content.ReadAsStreamAsync())
{
var fileName = GetFileName(response);
var filePath = Path.Combine(@"D:\Desktop", fileName);
}
使用 response.Content.ReadAsStreamAsync() 方法获取响应音频文件的内容流,然后使用GetFileName() 方法获取文件名,再构建完整的文件路径,将音频文件保存在 D:\ 目录下。
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
await stream.CopyToAsync(fileStream);
}
使用 FileStream 打开文件流,将音频内容流写入文件流中。
MessageBox.Show("下载完成:" + filePath, "下载成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
弹出消息框显示下载成功,并显示下载的文件路径。
完整代码如下:
private async Task DownloadAudioAsync(string url)
{
using (var httpClient = new HttpClient())
{
try
{
using (var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode(); // 确保响应成功
using (var stream = await response.Content.ReadAsStreamAsync())
{
var fileName = GetFileName(response);
var filePath = Path.Combine(@"D:\Desktop", fileName);
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
await stream.CopyToAsync(fileStream);
}
MessageBox.Show("下载完成:" + filePath, "下载成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
catch (Exception ex)
{
MessageBox.Show($"下载失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
(5)获取文件名
创建一个私有方法,返回一个字符串类型的文件名。接受一个 HttpResponseMessage 对象作为参数,表示 HTTP 响应信息。
string fileName = null;
声明一个字符串变量 fileName,用于存储文件名。
if (!string.IsNullOrEmpty(response.Content.Headers.ContentDisposition?.FileName))
{
fileName = response.Content.Headers.ContentDisposition.FileName;
}
检查响应的Content-Disposition头部是否包含文件名。如果包括,则从响应内容头部获取文件名,然后将其赋给 fileName 变量。
? 是空值条件操作符,用于在 FileName 可能为空的情况下避免空引用异常。
else
{
Uri.TryCreate(response.RequestMessage.RequestUri.ToString(), UriKind.Absolute, out Uri uri);
fileName = Path.GetFileName(uri.LocalPath);
}
如果响应头中没有包含文件名,则执行 else 块的代码。
创建一个对象 uri,尝试解析请求消息的绝对 URI,将请求消息的 URI转换为字符串形式,再使用 Path.GetFileName 方法从 URI 的本地路径部分获取文件名。
uri.LocalPath 表示 URI 的路径部分。
完整代码如下:
private string GetFileName(HttpResponseMessage response)
{
string fileName = null;
if (!string.IsNullOrEmpty(response.Content.Headers.ContentDisposition?.FileName))
{
fileName = response.Content.Headers.ContentDisposition.FileName;
}
else
{
Uri.TryCreate(response.RequestMessage.RequestUri.ToString(), UriKind.Absolute, out Uri uri);
fileName = Path.GetFileName(uri.LocalPath);
}
return fileName;
}
2、完整代码
至此,整个音乐播放器代码如下:
音乐播放器窗体设计部分,以及某些控件的使用,详见前篇。这里包括下载音乐文件、播放本地.mp3和.ogg音乐文件的功能。
using System.Diagnostics.Eventing.Reader;
using System.Threading;
using NAudio;
using NAudio.Wave;
using NAudio.Vorbis;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
namespace WinFormsApp
{
public partial class Form1 : Form
{
string[] files;
List<string> localmusiclist = new List<string> { };
public Form1()
{
InitializeComponent();
}
private void musicplay(string filename)
{
axWindowsMediaPlayer1.URL = filename;
string extension = Path.GetExtension(filename);
if (extension == ".ogg") { Console.WriteLine("这是.ogg文件"); }
else if (extension == ".mp3") // 最好也检查一下扩展名是否存在
{
axWindowsMediaPlayer1.Ctlcontrols.play();
}
else
{
// 处理没有扩展名或无法识别的文件
Console.WriteLine("无法识别的文件格式");
}
}
private void button1_Click(object sender, EventArgs e)
{
openFileDialog1.Filter = "选择音频|*.mp3";//;*.flac;*.wav
openFileDialog1.Multiselect = true;
if (openFileDialog1.ShowDialog() == DialogResult.OK)//用户点击确定按钮
{
listBox1.Items.Clear();//上一次的结果不会影响到下一次
localmusiclist.Clear();
if (files != null)
{
Array.Clear(files, 0, files.Length);//把array里面的所有元素设置为默认,并清空
}
files = openFileDialog1.FileNames;//这个选择多个文件,filename选择一个文件
string[] array = files;
foreach (string x in array)
{
listBox1.Items.Add(x);
localmusiclist.Add(x);//选择多个音乐文件,能以行显示
}
}
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (localmusiclist.Count > 0)
{
axWindowsMediaPlayer1.URL = localmusiclist[listBox1.SelectedIndex];
//axWindowsMediaPlayer1.Ctlcontrols.play();
musicplay(axWindowsMediaPlayer1.URL);
label1.Text = Path.GetFileNameWithoutExtension(localmusiclist[listBox1.SelectedIndex]);
}
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
axWindowsMediaPlayer1.settings.volume = trackBar1.Value;
}
private void button2_Click(object sender, EventArgs e)
{
axWindowsMediaPlayer1.Ctlcontrols.stop();
}
private void button3_Click(object sender, EventArgs e)
{
if (localmusiclist.Count > 0)
{
int index = listBox1.SelectedIndex + 1;
if (index >= localmusiclist.Count())
{
index = 0;
}
axWindowsMediaPlayer1.URL = localmusiclist[index];
//axWindowsMediaPlayer1.Ctlcontrols.play();
musicplay(axWindowsMediaPlayer1.URL);
label1.Text = Path.GetFileNameWithoutExtension(localmusiclist[index]);
listBox1.SelectedIndex = index;
}
}
private void button4_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "打开音频|*.ogg";
string oggFilePath = "";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
oggFilePath = openFileDialog.FileName;
}
using (var reader = new VorbisWaveReader(oggFilePath))
{
using (var outputDevice = new WaveOutEvent())
{
outputDevice.Init(reader);
outputDevice.Play();
// 等待播放完成或按需添加其他逻辑
while (outputDevice.PlaybackState == PlaybackState.Playing)
{
System.Threading.Thread.Sleep(100);
}
}
}
}
private void axWindowsMediaPlayer1_Enter(object sender, EventArgs e)
{
}
private const string audioUrl = "https://www.gqt.org.cn/ccylmaterial/song/200612/W020180622624077722652.mp3";
private async void button5_Click(object sender, EventArgs e)
{
await DownloadAudioAsync(audioUrl);
}
private async Task DownloadAudioAsync(string url)
{
using (var httpClient = new HttpClient())
{
try
{
// 发起 GET 请求并获取响应
using (var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode(); // 确保响应成功
using (var stream = await response.Content.ReadAsStreamAsync())
{
// 获取文件名,如果服务器没有提供文件名,可以自行指定
var fileName = GetFileName(response);
// 指定保存文件的路径,这里保存在当前用户的下载目录下
var filePath = Path.Combine(@"D:\Desktop", fileName);
// 使用 FileStream 写入文件
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
await stream.CopyToAsync(fileStream);
}
MessageBox.Show("下载完成:" + filePath, "下载成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
catch (Exception ex)
{
MessageBox.Show($"下载失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private string GetFileName(HttpResponseMessage response)
{
// 从响应头或 URL 中获取文件名
string fileName = null;
if (!string.IsNullOrEmpty(response.Content.Headers.ContentDisposition?.FileName))
{
fileName = response.Content.Headers.ContentDisposition.FileName;
}
else
{
Uri.TryCreate(response.RequestMessage.RequestUri.ToString(), UriKind.Absolute, out Uri uri);
fileName = Path.GetFileName(uri.LocalPath);
}
return fileName;
}
}
}
3、运行结果
点击按钮后,自动下载音乐文件
下载后即可在本地找到这个音乐文件,可以播放该音乐文件
三、代码分析
1、代码思路
要想实现用户点击按钮时,实现音乐文件下载。需要执行异步操作以避免阻塞用户界面。获取音乐文件的过程是使用 HttpClient 类来发送 HTTP 请求,获取音乐文件的URL,构造 HttpClient 实例,并使用 GetAsync 方法发送 GET请求获取音乐文件的响应。检查响应的状态码,确保请求成功。请求成功后尝试获取音乐文件内容的流,使用FileStream将下载的音乐文件内容写入本地路径。在存储文件的过程中需要获取文件名,可以从音乐文件URL中解析出文件名。
2、难点分析
(1)异步操作和线程管理
异步操作容易导致线程管理问题。需要使用 async 和 await 来正确处理异步操作,避免 UI 线程阻塞和响应问题,并获取正确的异步方法的返回值和异常处理。
(2)文件下载和存储管理
下载音乐文件时,可以使用流处理方式来下载和保存文件,确保下载的文件内容正确写入到目标文件中。
(3)文件名的获取和处理
从URL 中正确获取文件名,处理不同的编码和路径格式。在生成文件名时,需要考虑文件名符合文件系统的要求。