今天整理到音频播放的部分,本来就想抽取一个简单的接口方便以后可能会用到,然而不知不觉就把常用的功能都给一起封装好了,实现只需一行代码就实现音频播放,核心其实就是调用MCI的API接口,具体的功能就是变换不同的MCI指令来实现。

==========  原创作品    作者:未闻    出处:博客园  ==========

一、常见的音频播放方式

* System.Media.SoundPlayer:播放wav

* MCI Command String:播放MP3、AVI等

* axWindowsMediaPlayer:COM组件,功能丰富易用

二、 注意事项

* 应用于窗体程序,不能应用于控制台程序(不知道是不是因为取不到窗体句柄,加Sleep也没用,知道的不妨留言告知)

三、代码

封装好的类,可以直接用了,这里用了单例简化了用法,其实只要别名不一样,还可以支持同时播放多个音频。

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; namespace System.Media
{
/// <summary>
/// 音频播放器(基于MCI-API接口)
/// 作者:未闻
/// 时间:2020.02.13
///
/// 详细的指令介绍
/// https://blog.csdn.net/psongchao/article/details/1487788
/// </summary>
public class AudioPlayer
{
#region API定义
[DllImport("winmm.dll")]
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); private void SendCommand(string cmd)
{
mciSendString(cmd, null, , );
}
private string SendCommandForResult(string cmd)
{
mciSendString(cmd, _temp, _temp.Capacity, );
return _temp.ToString();
}
#endregion public AudioPlayer(string alias)
{
AliasName = alias;
//// 获取声道
//var ret = SendCommandForResult($"status {AliasName} source");
//if (!string.IsNullOrWhiteSpace(ret))
// _source = _sourceMap.FirstOrDefault(pair => pair.Value.Equals(ret)).Key; //// 音频状态,是否静音
//ret = SendCommandForResult($"status {AliasName} audio");
//if (!string.IsNullOrWhiteSpace(ret))
// _audioStatus = _audioStatusMap.FirstOrDefault(pair => pair.Value.Equals(ret)).Key; timer.Tick += Timer_Tick;
} #region 单例
class Nested { public static AudioPlayer Instance = new AudioPlayer("AUDIO_PLAYER_SINGLETON"); }
public static AudioPlayer Instance => Nested.Instance;
#endregion // 播放别名,每个播放源(声音)采用一个别名来识别,可以支持同时播放多个声音
public string AliasName { get; private set; } private StringBuilder _temp = new StringBuilder();
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"}
};
private Timer timer = new Timer
{
Interval =
}; public event Action Progress;
public event Action Completed; private void Timer_Tick(object sender, EventArgs e)
{
if (!IsCompleted)
{
Progress?.Invoke();
return;
} Status = PlayerStatus.Stop;
timer.Stop();
Completed?.Invoke();
} /// <summary>
/// 准备
/// </summary>
/// <param name="fileName"></param>
/// <param name="autoPlay">是否自动播放,默认true</param>
public void Prepare(string fileName, bool autoPlay = true)
{
if (Status == PlayerStatus.Playing)
Stop(); if (string.IsNullOrWhiteSpace(fileName))
return; GetShortPathName(fileName, _temp, _temp.Capacity);
var mp3Path = _temp.ToString();
SendCommand($"open \"{mp3Path}\" alias {AliasName}"); //打开
if (autoPlay)
Play(); // 因为设置静音后一播放,会变成有声音,所以这里要设置一下
AudioStatus = _audioStatus;
Source = _source;
Volume = _vol;
} /// <summary>
/// 播放
/// </summary>
public void Play()
{
SendCommand($"play {AliasName}");
Status = PlayerStatus.Playing;
timer.Start();
} /// <summary>
/// 停止
/// </summary>
public void Stop()
{
SendCommand($"close {AliasName}");
Status = PlayerStatus.Stop;
timer.Stop();
} /// <summary>
/// 暂停
/// </summary>
public void Pause()
{
SendCommand($"pause {AliasName}");
Status = PlayerStatus.Pause;
timer.Stop();
} /// <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;
SendCommand($"setaudio {AliasName} {_audioStatusMap[value]}");
}
} private AudioSource _source = AudioSource.H;
/// <summary>
/// 播放声道
/// </summary>
public AudioSource Source
{
get => _source;
set
{
_source = value;
SendCommand($"setaudio {AliasName} source to {_sourceMap[value]}");
}
} private int _vol = ;
/// <summary>
/// 音量
/// </summary>
public int Volume
{
get => _vol;
//{
// var ret = SendCommandForResult($"status {AliasName} volume");
// if (string.IsNullOrWhiteSpace(ret))
// return 500;
// return Convert.ToInt32(ret);
//}
set
{
if (value < || value > )
return; _vol = value;
SendCommand($"setaudio {AliasName} volume to {value}");
}
} /// <summary>
/// 获取是否正在播放
/// </summary>
public bool IsPlaying => Status == PlayerStatus.Playing;
/// <summary>
/// 获取是否已播放结束
/// </summary>
public bool IsCompleted => Position >= Length; /// <summary>
/// 获取播放总时长
/// </summary>
public int Length
{
get
{
var ret = SendCommandForResult($"status {AliasName} length");
if (string.IsNullOrWhiteSpace(ret))
return ; return Convert.ToInt32(ret);
}
}
/// <summary>
/// 获取播放总时长(格式:00:00)
/// </summary>
public string LengthString
{
get
{
return Len2Time(Length);
}
} /// <summary>
/// 获取播放进度
/// </summary>
public int Position
{
get
{
var ret = SendCommandForResult($"status {AliasName} position");
if (string.IsNullOrWhiteSpace(ret))
return ; return Convert.ToInt32(_temp.ToString());
}
set
{
if (value < || value > Length)
return; SendCommand($"seek {AliasName} to {value}");
Play();
}
}
/// <summary>
/// 获取播放进度(格式:00:00)
/// </summary>
public string PositionString
{
get
{
return Len2Time(Position);
}
} /// <summary>
/// 把时长从int类型转换成格式为00:00的字符串
/// </summary>
/// <param name="len"></param>
/// <returns></returns>
private string Len2Time(int len)
{
int sec = len / % ;
int min = len / % ;
return string.Format("{0:D2}:{1:D2}", min, sec);
}
} public enum PlayerStatus
{
/// <summary>
/// 停止
/// </summary>
Stop = ,
/// <summary>
/// 播放中
/// </summary>
Playing = ,
/// <summary>
/// 暂停
/// </summary>
Pause =
}
public enum AudioSource
{
/// <summary>
/// 立体声
/// </summary>
H = ,
/// <summary>
/// 平均声道
/// </summary>
A = ,
/// <summary>
/// 左声道
/// </summary>
L = ,
/// <summary>
/// 右声道
/// </summary>
R =
}
}

四、调用示例

AudioPlayer player = AudioPlayer.Instance;
public Form1()
{
InitializeComponent();
player.Progress += Player_Progress;
player.Completed += Player_Completed;
} private void Player_Completed()
{
lblName.Text = "暂无曲目";
} private void Player_Progress()
{
UpdateProgress();
} private void btnOpenFile_Click(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
player.Prepare(openFileDialog1.FileName);
tbrProgress.Maximum = player.Length;
UpdateProgress();
lblName.Text = Path.GetFileName(openFileDialog1.FileName);
}
} /// <summary>
/// 更新当前播放进度
/// </summary>
private void UpdateProgress()
{
lblPos.Text = player.PositionString;
lblLen.Text = player.LengthString;
tbrProgress.Value = player.Position;
}

五、参考资料

MP3播放-基于MCI-API接口的更多相关文章

  1. libEasyPlayer RTSP windows播放器SDK API接口设计说明

    概述 libEasyPlayer实现对RTSP直播流进行实时采集和解码显示,稳定,高效,低延时:解码可采用intel硬件解码和软件解码两种方式,能实时进行录像和快照抓图,OSD叠加等功能. API接口 ...

  2. 基于网页api(接口)实现查快递

    之前在网上找到一款下载某慕课网站的java版软件,我想知道他是怎么实现:对于视频的下载的,毕竟网页源码中大都不会直接放视频的地址,但是没有公布源码,我就反编译,等到了部分“源码”,逻辑上还是有些问题, ...

  3. 基于Metaweblog API 接口一键发布到国内外主流博客平台

    之前的生活 之前一直使用evenote写博客和日志,其实还是挺方便的.但是我一直都希望能够同步到国内的博客和国外的blogspot等主流博客平台.而强大everote只提供了facebook.twit ...

  4. 如何利用百度音乐播放器的API接口来获取高音质歌曲

    第一步:在网页中打开以下网址: http://box.zhangmen.baidu.com/x?op=12&count=1&title=时间都去哪儿了$$王铮亮$$$$ 其中红色地方可 ...

  5. atitit.基于http json api 接口设计 最佳实践 总结o7

    atitit.基于http  json  api 接口设计 最佳实践 总结o7 1. 需求:::服务器and android 端接口通讯 2 2. 接口开发的要点 2 2.1. 普通参数 meth,p ...

  6. 在线音乐播放器-----酷狗音乐api接口抓取

    首先身为一个在线音乐播放器,需要前端和数据库的搭配使用. 在数据库方面,我们没有办法制作,首先是版权问题,再加上数据量.所以我们需要借用其他网络播放器的数据库. 但是这些在线播放器,如百度,酷狗,酷我 ...

  7. Yii2 基于RESTful架构的 advanced版API接口开发 配置、实现、测试 (转)

    环境配置: 开启服务器伪静态 本处以apache为例,查看apache的conf目录下httpd.conf,找到下面的代码 LoadModule rewrite_module modules/mod_ ...

  8. Yii2 基于RESTful架构的 advanced版API接口开发 配置、实现、测试

    环境配置: 开启服务器伪静态 本处以apache为例,查看apache的conf目录下httpd.conf,找到下面的代码 LoadModule rewrite_module modules/mod_ ...

  9. 基于C#的SolidWorks插件开发(1)--SolidWorks API接口介绍

    这是两年前毕业时写的一篇关于SolidWorks插件开发与公司PDM集成的毕业设计,最近闲来无事拿出来整理一下,大神们可以略过. 1.1   SolidWorks API接口 正确调用SolidWor ...

随机推荐

  1. html页脚固定在底部的方法

    <style type="text/css"> html { height: 100%; } body { height: 100%; margin: 0; paddi ...

  2. Python PyInstaller安装和使用教程

    安装 PyInstalle Python 默认并不包含 PyInstaller 模块,因此需要自行安装 PyInstaller 模块. 安装 PyInstaller 模块与安装其他 Python 模块 ...

  3. IOS系统唤醒微信内置地图

    针对前一篇文章 唤醒微信内置地图 后来发现在IOS系统中运行 唤醒地图会无效的问题.因为在IOS上无法解析这俩个字符串的问题! 需要对经纬度 使用 “parseFloat()”进行转换 返回一个浮点数 ...

  4. Django 博客实现简单的全文搜索

    作者:HelloGitHub-追梦人物 文中所涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 搜索是一个复杂的功能,但对于一些简单的搜索任务,我们可以使用 Django Mode ...

  5. 在Winform界面中使用DevExpress的TreeList实现节点过滤查询的两种方式

    在我较早的一篇随笔<在DevExpress程序中使用TeeList控件以及节点查询的处理>中,介绍了在树形列表TreeList控件上面,利用SearchControl实现节点的模糊查询过滤 ...

  6. FullPage.js-基于 jQuery 的插件全屏滚动插件

    fullPage.js 是一个基于 jQuery 的插件,它能够很方便.很轻松的制作出全屏网站.如今我们经常能见到全屏网站,尤其是国外网站.这些网站用几幅很大的图片或色块做背景,再添加一些简单的内容, ...

  7. 开发STM32MP1,你需要一块好开发板

    STM32MP1系列的出现吸引了很多STM32的新老用户的关注,但是很多的人都会担心一个问题:以前是基于Cortex M系列MCU惊醒开发,对于cortex-A架构的处理器以及Linux系统都不熟悉. ...

  8. CS0656 缺少编译器要求的成员“Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create”

    问题出现原因:在net core使用动态类型dynamic,在编译的时候提示错误信息如上. 解决方案: 1.不用dynamic类型 2.在使用的地方添加一个dll,Microsoft.CSharp,或 ...

  9. cf - 920 c 求能否实现交换

    C. Swap Adjacent Elements time limit per test 1 second memory limit per test 256 megabytes input sta ...

  10. 存储过程带参数和sqlcommand

    public DataSet SelectBillNo(string CarrierCode, string Date, string CompanyCode) { System.Collection ...