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, 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
    }
}
View Code

四、調用示例

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;
}

五、參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章