MP3播放-基于MCI-API接口
今天整理到音频播放的部分,本来就想抽取一个简单的接口方便以后可能会用到,然而不知不觉就把常用的功能都给一起封装好了,实现只需一行代码就实现音频播放,核心其实就是调用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接口的更多相关文章
- libEasyPlayer RTSP windows播放器SDK API接口设计说明
概述 libEasyPlayer实现对RTSP直播流进行实时采集和解码显示,稳定,高效,低延时:解码可采用intel硬件解码和软件解码两种方式,能实时进行录像和快照抓图,OSD叠加等功能. API接口 ...
- 基于网页api(接口)实现查快递
之前在网上找到一款下载某慕课网站的java版软件,我想知道他是怎么实现:对于视频的下载的,毕竟网页源码中大都不会直接放视频的地址,但是没有公布源码,我就反编译,等到了部分“源码”,逻辑上还是有些问题, ...
- 基于Metaweblog API 接口一键发布到国内外主流博客平台
之前的生活 之前一直使用evenote写博客和日志,其实还是挺方便的.但是我一直都希望能够同步到国内的博客和国外的blogspot等主流博客平台.而强大everote只提供了facebook.twit ...
- 如何利用百度音乐播放器的API接口来获取高音质歌曲
第一步:在网页中打开以下网址: http://box.zhangmen.baidu.com/x?op=12&count=1&title=时间都去哪儿了$$王铮亮$$$$ 其中红色地方可 ...
- atitit.基于http json api 接口设计 最佳实践 总结o7
atitit.基于http json api 接口设计 最佳实践 总结o7 1. 需求:::服务器and android 端接口通讯 2 2. 接口开发的要点 2 2.1. 普通参数 meth,p ...
- 在线音乐播放器-----酷狗音乐api接口抓取
首先身为一个在线音乐播放器,需要前端和数据库的搭配使用. 在数据库方面,我们没有办法制作,首先是版权问题,再加上数据量.所以我们需要借用其他网络播放器的数据库. 但是这些在线播放器,如百度,酷狗,酷我 ...
- Yii2 基于RESTful架构的 advanced版API接口开发 配置、实现、测试 (转)
环境配置: 开启服务器伪静态 本处以apache为例,查看apache的conf目录下httpd.conf,找到下面的代码 LoadModule rewrite_module modules/mod_ ...
- Yii2 基于RESTful架构的 advanced版API接口开发 配置、实现、测试
环境配置: 开启服务器伪静态 本处以apache为例,查看apache的conf目录下httpd.conf,找到下面的代码 LoadModule rewrite_module modules/mod_ ...
- 基于C#的SolidWorks插件开发(1)--SolidWorks API接口介绍
这是两年前毕业时写的一篇关于SolidWorks插件开发与公司PDM集成的毕业设计,最近闲来无事拿出来整理一下,大神们可以略过. 1.1 SolidWorks API接口 正确调用SolidWor ...
随机推荐
- js中this指向问题(call,apply,bind)
call.apply.bind的作用是改变函数运行时this的指向. 如果你传的 context 就 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下 ...
- spring cloud 微服务之 -- 配置文件拆分之道
0-前言 在spring cloud微服务架构中,基本上每个拆分的微服务都会部署多个运行实例,这些运行实例,配置基本都是一样的,不同的是少数配置,比如端口,而这些不同的配置又是必不可少的 那我们怎么来 ...
- Ural1057. Amount of Degrees 题解 数位DP
题目链接: (请自行百度进Ural然后查看题号为1057的那道题目囧~) 题目大意: Create a code to determine the amount of integers, lying ...
- 1091 N-自守数 (15 分)C语言
如果某个数 K 的平方乘以 N 以后,结果的末尾几位数等于 K,那么就称这个数为"N-自守数".例如 3×92^2 =25392,而 25392 的末尾两位正好是 92,所以 ...
- 1063 计算谱半径 (20 分)C语言
在数学中,矩阵的"谱半径"是指其特征值的模集合的上确界.换言之,对于给定的 n 个复数空间的特征值 { a1+b1 i,⋯,an +bn i },它们的模为实部与 ...
- 用实例理解设计模式——代理模式(Python版)
代理模式:为其他对象提供一种代理以控制对这个对象的访问. 在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 代理模式分为: 静态代理 动态代 ...
- apache相关实验-2
一.Apache+openssl 实现 https HTTPS(全称:Hypertext Transfer Protocol Secure,超文本传输安全协议),是以安全为目标的 HTTP 通道,简单 ...
- docker命令总结(二)
上次只是给大家把命令的作用以及简单使用列出来了(大家可以查看:docker命令总结(一)),那这篇文章会详细介绍每条命令的参数,命令比较多建议大家使用搜索,进行查看 search docker sea ...
- 动态规划之抢劫问题-LT213
找到大问题和小问题之间共有的特性,列出一定的状态转移规律,然后设计满足条件的小问题解决方案,最后凭借记忆中的中间值快速求出最终解 动态规划问题的复杂性在于你永远不知道下一个题目中的状态是什么,有什么样 ...
- Netty--【详解】
Netty概述:1.netty是基于Java NIO的网络应用框架,client-server框架2.Netty是一个高性能.异步事件驱动的NIO框架,它提供了对TCP.UDP和文件传输的支持,作为一 ...