今天整理到音頻播放的部分,本來就想抽取一個簡單的接口方便以後可能會用到,然而不知不覺就把常用的功能都給一起封裝好了,核心其實就是調用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, 0, 0); } private string SendCommandForResult(string cmd) { mciSendString(cmd, _temp, _temp.Capacity, 0); 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(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"} }; private Timer timer = new Timer { Interval = 1000 }; 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 = 500; /// <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 < 0 || value > 1000) 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 0; 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 0; return Convert.ToInt32(_temp.ToString()); } set { if (value < 0 || 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 / 1000 % 60; int min = len / 60000 % 60; return string.Format("{0:D2}:{1:D2}", 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 } }
四、調用示例
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; }
五、參考資料