第13回 ZIP書庫内の曲を再生する

前回「第12回 ZIP書庫内の曲を再生する準備」は音楽ファイルの管理にint情報を追加してzip書庫内の再生準備をした。
今回は一気にzip書庫内のファイル再生までを実装します。

zip書庫の解凍には.NET Framework 4.5で追加されたZipFileクラスを利用します。
これを利用するためにプロジェクトメニューにある「参照の追加」を選択して「参照マネージャー」を開き、
「アセンブリ」の中にある
System.IO.Compression
System.IO.Compression.FileSystem
の2項目にチェックをいれて参照の追加をする。

ZIPファイルに含まれる曲の再生の流れは、
実行ファイルのあるフォルダの下にtmpフォルダを作成し、
そこに音楽ファイルを解凍/再生している。
Windows Media Playerのコントロールでは再生時に勝手に音楽ファイルのある場所にファイルを書き出すことがあるため、
解凍したファイルだけを削除するのではなく、tmpフォルダ内に含まれるすべてのファイルを削除しています。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.IO.Compression;		//追加
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using WMPLib;

namespace MP3Player
{
	public partial class Form1 : Form
	{
		WindowsMediaPlayer _mediaPlayer = new WindowsMediaPlayer();

		Timer _timer = new Timer();



		class AudioItem
		{
			public string strFile = "";
			public int nEntryIndex = -1;

			public AudioItem(string strFile, int nEntryIndex = -1)
			{
				this.strFile = strFile;
				this.nEntryIndex = nEntryIndex;
			}
		}


		List<AudioItem> _listFiles;			//音楽ファイル一覧
		int _nCurrentIndex = -1;			//↑へのインデックス


		List<int> _listPlayHistory = new List<int>();		//過去の再生曲
		List<int> _listPlayNext = new List<int>();			//未来の再生曲。なければランダム


		bool _bStopButtonPushed = false;	//「停止」ボタンを押したらtrue

		public Form1()
		{
			InitializeComponent();

			CreateTmpFolder();			//tmpフォルダの作成		//追加

			_listFiles = EnumFiles(@"G:\Desktop\mp3\");		//再生したいmp3保存フォルダを指定


			//フォームが表示されたときの処理
			Shown += delegate
			{
				//シークバーにフォーカスがあると、再生位置が表示されないのでここで再生ボタンにフォーカスを与える
				buttonPlay.Focus();
			};

			//追加
			//終了時処理
			FormClosed += delegate
			{
				CleanTempFolder();			//tmpフォルダ内の削除
			};


			//再生位置のシーク
			trackBarSeek.ValueChanged += delegate
			{
				if (trackBarSeek.Focused)
				{
					_mediaPlayer.controls.currentPosition = (double)trackBarSeek.Value / 100;
				}
			};


			//音量の調節
			_mediaPlayer.settings.volume = 20;
			trackBarVolume.Maximum = 100;			//音量はゼロから100
			trackBarVolume.Value = _mediaPlayer.settings.volume;
			trackBarVolume.ValueChanged += delegate
			{
				_mediaPlayer.settings.volume = (int)trackBarVolume.Value;
			};


			_timer.Interval = 300;			//300msecごとに処理を実行
			_timer.Tick += delegate
			{
				if (_mediaPlayer.playState == WMPPlayState.wmppsPlaying || _mediaPlayer.playState == WMPPlayState.wmppsPaused)
				{
					trackBarSeek.Maximum = (int)(_mediaPlayer.controls.currentItem.duration * 100);
					//シークバーにフォーカスがないときだけ再生位置を表示する
					if (trackBarSeek.Focused == false)
					{
						try
						{
							//シークバーに再生位置を表示
							//曲変更タイミングによっては例外出る可能性ある
							trackBarSeek.Value = (int)(_mediaPlayer.controls.currentPosition * 100);
						}
						catch (Exception)
						{
						}
					}

					//再生時間の表示
					labelTime.Text = _mediaPlayer.controls.currentPositionString;
				}

				//止まっていたら再び再生。ただし停止ボタンが押されている場合は無視
				if (_mediaPlayer.playState == WMPPlayState.wmppsStopped)
				{
					if (_bStopButtonPushed == false)
						Play();
				}
			};
			_timer.Start();


			_mediaPlayer.PlayStateChange += delegate(int nNewState)
			{
				WMPPlayState state = (WMPPlayState)nNewState;

				switch (state)
				{
					case WMPPlayState.wmppsStopped:
						buttonPlay.Text = ">";
						trackBarSeek.Value = 0;
						break;

					case WMPPlayState.wmppsPaused:
						_timer.Stop();
						buttonPlay.Text = ">";
						break;

					case WMPPlayState.wmppsPlaying:
						_bStopButtonPushed = false;
						buttonPlay.Text = "||";
						_timer.Start();
						break;

					case WMPPlayState.wmppsTransitioning:
						break;
				}
			};

			Play();
		}





		/// <summary>
		/// 曲を再生する
		/// </summary>
		void Play(bool bPlayNext = true)
		{
			if (_listFiles.Count == 0)
				return;

			if (bPlayNext || _listPlayHistory.Count == 0)
			{
				//次の曲を再生する
				if (_nCurrentIndex >= 0)
				{
					_listPlayHistory.Add(_nCurrentIndex);					//今の曲を履歴として保存
					if (_listPlayHistory.Count > 100)						//履歴の最大保存数は100
						_listPlayHistory.RemoveAt(0);
				}

				_nCurrentIndex = GetNextIndex();
			}
			else
			{
				//前の曲を再生する
				if (_nCurrentIndex >= 0)
					_listPlayNext.Insert(0, _nCurrentIndex);					//今の曲を次の曲として保存
				_nCurrentIndex = _listPlayHistory[_listPlayHistory.Count - 1];	//一番最新の履歴曲を今の曲とする
				_listPlayHistory.RemoveAt(_listPlayHistory.Count - 1);			//一番最新の履歴曲を削除
			}

			//追加
			CleanTempFolder();			//tmpフォルダ内の削除

			//追加
			string strFile = _listFiles[_nCurrentIndex].strFile;
			if (_listFiles[_nCurrentIndex].nEntryIndex >= 0)
			{
				//zip書庫ならtmpフォルダに解凍する
				bool ret = ExtractFileFromZip(strFile, _listFiles[_nCurrentIndex].nEntryIndex, GetTmpFile(), out strFile);
				if (ret == false)
					return;
			}

			//変更
			_mediaPlayer.URL = strFile;			//再生曲をセット
			_mediaPlayer.controls.play();

			//曲名/タイトルの表示
			{
				string strTitle;
				string strArtist;
				string strAlbum;

				GetMp3Info(strFile, out strTitle, out strArtist, out strAlbum);		//変更

				labelTitle.Text = strTitle;
				labelArtist.Text = strArtist;
			}
		}



		/// <summary>
		/// 次の再生曲インデックスを決める
		/// </summary>
		int GetNextIndex()
		{
			if (_listFiles.Count == 0)
				return -1;

			int nIndex;
			Random rnd = new Random(Environment.TickCount);

			while (true)
			{
				if (_listPlayNext.Count == 0)
				{
					//未来の曲リストが空ならランダムで曲を決定
					nIndex = rnd.Next(0, _listFiles.Count);
				}
				else
				{
					//未来の曲リストから次の曲を決定
					nIndex = _listPlayNext[0];
					_listPlayNext.RemoveAt(0);
				}

				//同じ曲は連続で演奏しない
				if (nIndex == _nCurrentIndex)
					continue;

				break;
			}

			return nIndex;
		}



		/// <summary>
		/// フォルダ内のファイルを一覧して返す
		/// </summary>
		List<AudioItem> EnumFiles(string strFolder)
		{
			List<AudioItem> ret = new List<AudioItem>();

			//指定フォルダ以下の全子フォルダから全ファイルを抜き出す
			IEnumerable<string> listFiles = Directory.EnumerateFiles(strFolder, "*.*", SearchOption.AllDirectories);

			foreach (string strFile in listFiles)
			{
				//見つかったファイルの拡張子を取り出し
				string strExt = Path.GetExtension(strFile).ToLower();
				if (strExt == "")
					continue;

				//変更
				//mp3/m4aならそのまま追加
				if (strExt == ".mp3" || strExt == ".m4a")
				{
					ret.Add(new AudioItem(strFile));
					continue;
				}

				//追加
				//ZIPファイルなら書庫に含まれるmp3/m4aファイルのエントリーを追加
				if (strExt == ".zip")
				{
					try
					{
						using (ZipArchive archive = ZipFile.OpenRead(strFile))
						{
							for (int i = 0; i < archive.Entries.Count; i++)
							{
								string strExtension = Path.GetExtension(archive.Entries[i].FullName).ToLower();
								if (strExtension == ".mp3" || strExtension == ".m4a")
								{
									ret.Add(new AudioItem(strFile, i));
								}
							}
						}
					}
					catch (Exception)
					{
					}
				}
			}

			return ret;
		}



		//追加
		/// <summary>
		/// テンポラリフォルダ内の削除
		/// </summary>
		public static void CleanTempFolder()
		{
			//テンポラリフォルダ内を全削除
			IEnumerable<string> listFiles = Directory.EnumerateFiles(GetTmpFolder(), "*", SearchOption.TopDirectoryOnly);
			foreach (string file in listFiles)
			{
				try
				{
					File.Delete(file);
				}
				catch (Exception)
				{
				}
			}
		}

		//追加
		/// <summary>
		/// tmpフォルダの作成
		/// </summary>
		public static void CreateTmpFolder()
		{
			try
			{
				Directory.CreateDirectory(GetTmpFolder());
			}
			catch (Exception)
			{
			}
		}

		
		//追加
		/// <summary>
		/// テンポラリファイル名の作成
		/// </summary>
		public static string GetTmpFile()
		{
			return GetTmpFolder() + DateTime.Now.Ticks;
		}

		//追加
		/// <summary>
		/// テンポラリフォルダを返す
		/// </summary>
		public static string GetTmpFolder()
		{
			return GetExeFolder() + @"tmp\";
		}


		//追加
		/// <summary>
		/// 実行ファイルのあるフォルダパスを返す
		/// </summary>
		public static string GetExeFolder()
		{
			FileInfo cFileInfo2 = new FileInfo(Application.ExecutablePath);		//実行ファイルのあるフォルダを取得するために実行ファイルの詳細を取得
			return cFileInfo2.DirectoryName + @"\";
		}


		//追加
		/// <summary>
		/// ZIPファイルからの1ファイル解凍
		/// </summary>
		/// <param name="strZipFile">解凍したいzipファイルへのパス</param>
		/// <param name="nEntryIndex">zip書庫内のエントリーインデックス</param>
		/// <param name="strOutFileName">解凍先のフォルダ名+ファイル名(拡張子なしで指定)</param>
		/// <param name="strOutFilePath">解凍先ファイルのフルパス。↑に拡張子が付加される</param>
		/// <returns></returns>
		public static bool ExtractFileFromZip(string strZipFile, int nEntryIndex, string strOutFileName, out string strOutFilePath)
		{
			strOutFilePath = "";
			if (strZipFile == "" || strOutFileName == "" || nEntryIndex < 0)
				return false;
			try
			{
				bool ret = false;

				using (ZipArchive archive = ZipFile.OpenRead(strZipFile))
				{
					if (nEntryIndex < archive.Entries.Count)
					{
						string strExtension = Path.GetExtension(archive.Entries[nEntryIndex].FullName);
						archive.Entries[nEntryIndex].ExtractToFile(strOutFileName + strExtension, true);
						strOutFilePath = strOutFileName + strExtension;
						ret = true;
					}
				}
				return ret;
			}
			catch (Exception)
			{
			}
			return false;
		}




		private void buttonPlay_Click(object sender, EventArgs e)
		{
			if (_mediaPlayer.playState == WMPPlayState.wmppsPlaying)
				_mediaPlayer.controls.pause();
			else if (_nCurrentIndex >= 0)
				_mediaPlayer.controls.play();
		}

		private void buttonStop_Click(object sender, EventArgs e)
		{
			if (_mediaPlayer.playState == WMPPlayState.wmppsPlaying || _mediaPlayer.playState == WMPPlayState.wmppsPaused)
			{
				_bStopButtonPushed = true;
				_mediaPlayer.controls.stop();
			}
		}

		private void buttonNext_Click(object sender, EventArgs e)
		{
			Play();
		}

		private void buttonBack_Click(object sender, EventArgs e)
		{
			Play(false);		//前の曲を再生
		}






		/// <summary>
		/// MP3ファイルからの曲名情報取得
		/// 
		/// TagLib# Portable利用
		/// https://github.com/timheuer/taglib-sharp-portable
		/// </summary>
		public static bool GetMp3Info(string strFile, out string strTitle, out string strArtist, out string strAlbum)
		{
			strArtist = "";
			strTitle = "";
			strAlbum = "";
			try
			{
				using (FileStream fs = new FileStream(strFile, FileMode.Open, FileAccess.Read))
				{
					AudioFileAbstraction abstraction = new AudioFileAbstraction(strFile, fs);

					using (TagLib.File file = TagLib.File.Create(abstraction))
					{
						strTitle = file.Tag.Title;
						strAlbum = file.Tag.Album;
						strArtist = file.Tag.FirstPerformer;

						if (strArtist == null || strArtist == "")
						{
							if (file.Tag.AlbumArtists.Length > 0)
								strArtist = file.Tag.AlbumArtists[0];
						}

						if (strArtist == null || strArtist == "")
							strArtist = file.Tag.FirstArtist;		//古いのも使う
						if (strArtist == null || strArtist == "")
							strArtist = file.Tag.FirstComposer;
						if (strArtist == null || strArtist == "")
							strArtist = file.Tag.JoinedAlbumArtists;

						file.Dispose();
					}
				}

				if (strArtist == null)
					strArtist = "";
				if (strTitle == null)
					strTitle = "";
				if (strAlbum == null)
					strAlbum = "";

				return true;
			}
			catch (Exception)
			{
			}
			strArtist = "";
			strTitle = "";
			strAlbum = "";
			return false;
		}



		/// <summary>
		/// TagLibからの情報取得用
		/// </summary>
		class AudioFileAbstraction : TagLib.File.IFileAbstraction
		{
			string _strName = "";
			Stream _stream = null;

			public AudioFileAbstraction(string Name, Stream Stream)
			{
				_strName = Name;
				_stream = Stream;
			}

			void TagLib.File.IFileAbstraction.CloseStream(Stream stream)
			{
				stream.Close();
			}

			string TagLib.File.IFileAbstraction.Name
			{
				get { return _strName; }
			}

			Stream TagLib.File.IFileAbstraction.ReadStream
			{
				get { return _stream; }
			}

			Stream TagLib.File.IFileAbstraction.WriteStream
			{
				get { return _stream; }
			}
		}


	}
}

プロジェクトファイルをダウンロード


カテゴリー「MP3プレーヤーを作る(C#)」 のエントリー