今天整理到音频播放的部分,本来就想抽取一个简单的接口方便以后可能会用到,然而不知不觉就把常用的功能都给一起封装好了,实现只需一行代码就实现音频播放,核心其实就是调用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. 使用EF操作Docker中的Mysql实例

    为啥我会选择mysql呢?因为我的服务器配置较低,而SqlServer在docker中的实例,服务器的运行内存应当保持在2G+,我没有这个条件,它会爆出这样的错误 sqlservr: This pro ...

  2. apache相关实验-1

    一.目录别名实验 当 apache 接受请求时,在默认情况下会将 DocumentRoot 目录中的文件送到客户端,如果想将某一不在 DocumentRoot 目录中的文件共享到网站上,并希望将它们留 ...

  3. 「洛谷P1196」「NOI2002」银河英雄传说 解题报告

    P1196 [NOI2002]银河英雄传说 题目描述 公元五八○一年,地球居民迁至金牛座α第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展. 宇宙历七九九年,银河系的 ...

  4. 傅立叶变换—DFT

    背景:最近看到实验室其他同学在用傅立叶变换解决问题,我也想通过并行来解决这个问题,所以看了一下傅立叶变换的东西,感觉涵盖的东西还能多,我只是初步做了一下了解(一定很片面,但是我主要是为了应用它,主要了 ...

  5. CentOS 安装图形化界面后重启出现许可等事项操作

    这是CentOS内核的初始设置页面,下面给出中文解释及操作方法. 1.CentOS Linux 7 初始设置(核心) 1)[!]许可证信息 (没有接受许可证) 请您选择[‘1’ 输入许可证信息 | ‘ ...

  6. vue项目使用v-charts的柱形图的各种样式和数据配置

    找了很多网上关于v-charts的柱形图使用,我发现我一模一样的配置就是没有效果,我原来是按需引入的, import VeHistogram from 'v-charts/lib/histogram' ...

  7. Jpa自定义查询报错(Failed to convert from type [java.lang.Object[]] to type)

    Jpa自定义查询报错 问题背景 今天遇到一个奇怪的报错"Failed to convert from type [java.lang.Object[]] to type",这个报错 ...

  8. Educational Codeforces Round 80 (Rated for Div. 2)

    A. Deadline 题目链接:https://codeforces.com/contest/1288/problem/A 题意: 给你一个 N 和 D,问是否存在一个 X , 使得 $x+\lce ...

  9. @Controller和@RestController

    @RestController=@Controller+@ResponseBody 1.使用RestController时,返回到前端的内容是Return里的内容,无法返回jsp/html等页面, 此 ...

  10. CSS动效集锦,视觉魔法的碰撞与融合(三)

    本文讲述的原理和相关demo 扇形DIV的使用——实现雷达扫描图 DIV环形布局—实现loading圈 动画的向量合成—实现抛物线动画 无限滚动动画—实现跑马灯效果 perspective和trans ...