今天整理到音频播放的部分,本来就想抽取一个简单的接口方便以后可能会用到,然而不知不觉就把常用的功能都给一起封装好了,核心其实就是调用MCI的API接口,具体的功能就是变换不同的MCI指令来实现。
========== 原创作品 作者:未闻 出处:博客园 ==========
一、常见的音频播放方式
* System.Media.SoundPlayer:播放wav
* MCI Command String:播放MP3、AVI等
* axWindowsMediaPlayer:COM组件,功能丰富易用
二、 注意事项
* 应用于窗体程序,不能应用于控制台程序(不知道是不是因为取不到窗体句柄,加Sleep也没用,知道的不妨留言告知)
三、代码
封装好的类,可以直接用了,这里用了单例简化了用法,其实只要别名不一样,还可以支持同时播放多个音频。

/// <summary>
/// MP3播放器(基于MCI-API接口)
/// 作者:未闻
/// 时间:2020.02.13
///
/// 详细的指令介绍
/// https://blog.csdn.net/psongchao/article/details/1487788
/// </summary>
public class MP3Player
{
// 播放标记,MCI接口是基于这个标记来处理,看播放暂停等代码就能明白,可以存在多个不同Tag的播放器
private const string C_TAG_PLAYER = "MCI_MP3_PLAYER";
#region 单例模式实现
class Nested { public static MP3Player Instance = new MP3Player(); }
private MP3Player()
{
//// 获取声道
//mciSendString($"status {C_TAG_PLAYER} source", _temp, _temp.Capacity, 0);
//_source = _sourceMap.FirstOrDefault(pair => pair.Value.Equals(_temp.ToString())).Key;
//// 音频状态,是否静音
//mciSendString($"status {C_TAG_PLAYER} audio", _temp, _temp.Capacity, 0);
//_audioStatus = _audioStatusMap.FirstOrDefault(pair => pair.Value.Equals(_temp.ToString())).Key;
}
public static MP3Player Instance => Nested.Instance;
#endregion
#region API定义
[DllImport("winmm.dll")]
public static extern int mciSendString(string m_strCmd, StringBuilder m_strReceive, int m_v1, int m_v2);
[DllImport("Kernel32", CharSet = CharSet.Auto)]
static extern int GetShortPathName(string path, StringBuilder shortPath, int shortPathLength);
#endregion
private StringBuilder _temp = new StringBuilder(260);
private Dictionary<AudioSource, string> _sourceMap = new Dictionary<AudioSource, string>
{
{AudioSource.H, "stereo"},
{AudioSource.A, "average"},
{AudioSource.L, "left"},
{AudioSource.R, "right"}
};
private Dictionary<bool, string> _audioStatusMap = new Dictionary<bool, string> {
{true, "on"},
{false, "off"}
};
/// <summary>
/// 播放
/// </summary>
/// <param name="fileName"></param>
public void Play(string fileName)
{
if (Status == PlayerStatus.Playing)
{
Stop();
}
if (string.IsNullOrWhiteSpace(fileName))
return;
GetShortPathName(fileName, _temp, _temp.Capacity);
var mp3Path = _temp.ToString();
mciSendString($"open \"{mp3Path}\" alias {C_TAG_PLAYER}", null, 0, 0); //打开
mciSendString($"play {C_TAG_PLAYER}", null, 0, 0);
Status = PlayerStatus.Playing;
// 因为设置静音后一播放,会变成有声音,所以这里要设置一下
AudioStatus = _audioStatus;
Source = _source;
}
/// <summary>
/// 停止
/// </summary>
public void Stop()
{
mciSendString($"close {C_TAG_PLAYER}", null, 0, 0);
Status = PlayerStatus.Stop;
}
/// <summary>
/// 暂停
/// </summary>
public void Pause()
{
mciSendString($"pause {C_TAG_PLAYER}", null, 0, 0);
Status = PlayerStatus.Pause;
}
/// <summary>
/// 播放状态
/// </summary>
public PlayerStatus Status { get; private set; } = PlayerStatus.Stop;
private bool _audioStatus = true;
/// <summary>
/// 音频状态(true 开启,false 静音)
/// </summary>
public bool AudioStatus
{
get => _audioStatus;
set
{
_audioStatus = value;
mciSendString($"setaudio {C_TAG_PLAYER} {_audioStatusMap[value]}", null, 0, 0);
}
}
private AudioSource _source = AudioSource.H;
/// <summary>
/// 播放声道
/// </summary>
public AudioSource Source
{
get => _source;
set
{
_source = value;
mciSendString($"setaudio {C_TAG_PLAYER} source to {_sourceMap[value]}", null, 0, 0);
}
}
private int _vol;
/// <summary>
/// 音量
/// </summary>
public int Volume
{
get => _vol;
set
{
if (value < 0 || value > 1000)
return;
_vol = value;
mciSendString($"setaudio {C_TAG_PLAYER} volume to {value}", null, 0, 0);
}
}
/// <summary>
/// 获取是否正在播放
/// </summary>
public bool IsPlaying => Status == PlayerStatus.Playing;
/// <summary>
/// 获取是否已播放结束
/// </summary>
public bool IsCompleted => Position >= Length;
/// <summary>
/// 获取播放总时长
/// </summary>
public int Length
{
get
{
mciSendString($"status {C_TAG_PLAYER} length", _temp, _temp.Capacity, 0);
return Convert.ToInt32(_temp.ToString());
}
}
/// <summary>
/// 获取播放总时长(格式:00:00:00)
/// </summary>
public string LengthString
{
get
{
return Len2Time(Length);
}
}
/// <summary>
/// 获取播放进度
/// </summary>
public int Position
{
get
{
mciSendString($"status {C_TAG_PLAYER} position", _temp, _temp.Capacity, 0);
return Convert.ToInt32(_temp.ToString());
}
}
/// <summary>
/// 获取播放进度(格式:00:00:00)
/// </summary>
public string PositionString
{
get
{
return Len2Time(Position);
}
}
/// <summary>
/// 把时长从int类型转换成格式为00:00:00的字符串
/// </summary>
/// <param name="len"></param>
/// <returns></returns>
private string Len2Time(int len)
{
int sec = len / 1000 % 60;
int min = len / 60000 % 60;
int hour = len / 3600000;
return string.Format("{0:D2}:{1:D2}:{2:D2}", hour, min, sec);
}
}
public enum PlayerStatus
{
/// <summary>
/// 停止
/// </summary>
Stop = 0,
/// <summary>
/// 播放中
/// </summary>
Playing = 1,
/// <summary>
/// 暂停
/// </summary>
Pause = 2
}
public enum AudioSource
{
/// <summary>
/// 立体声
/// </summary>
H = 0,
/// <summary>
/// 平均声道
/// </summary>
A = 1,
/// <summary>
/// 左声道
/// </summary>
L = 2,
/// <summary>
/// 右声道
/// </summary>
R = 3
}
四、调用示例
MP3Player player = MP3Player.Instance;
private void btnPlay_Click(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
player.Play(openFileDialog1.FileName);
tsslTotal.Text = player.LengthString;// 获取总时长
tmrTick.Start();
}
}
private void timer1_Tick(object sender, EventArgs e)
{
tsslPosition.Text = player.PositionString;// 更新当前播放进度
if (player.IsCompleted)
{
tmrTick.Stop();
MessageBox.Show("播放结束。");
}
}
五、参考资料
来源:https://www.cnblogs.com/yokeqi/p/12305967.html
