EternalWindows
MCI / CDの再生

セットされたCDが音楽CDであると確認できたら、実際にそれを再生してみましょう。 今回のプログラムは、再生の処理と一時停止の処理が含まれています。

#include <windows.h>

#pragma comment(lib, "winmm.lib")

BOOL IsExistCDAudio(MCIDEVICEID mciDeviceId);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static MCIDEVICEID mciDeviceId = 0;

	switch (uMsg) {

	case WM_CREATE: {
		MCI_OPEN_PARMS mciOpen;
		MCI_PLAY_PARMS mciPlay;
	
		mciOpen.lpstrDeviceType = (LPCTSTR)MCI_DEVTYPE_CD_AUDIO;
		mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD_PTR)&mciOpen);
	
		mciDeviceId = mciOpen.wDeviceID;

		if (!IsExistCDAudio(mciDeviceId))
			return -1;

		mciPlay.dwCallback = (DWORD_PTR)hwnd;
		mciSendCommand(mciDeviceId, MCI_PLAY, MCI_NOTIFY, (DWORD_PTR)&mciPlay);

		return 0;
	}

	case WM_LBUTTONUP: {
		MCI_STATUS_PARMS mciStatus;
		
		mciStatus.dwItem = MCI_STATUS_MODE;
		mciSendCommand(mciDeviceId, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&mciStatus);

		if (mciStatus.dwReturn == MCI_MODE_PLAY)
			mciSendCommand(mciDeviceId, MCI_PAUSE, 0, 0);
		else if (mciStatus.dwReturn == MCI_MODE_PAUSE)
			mciSendCommand(mciDeviceId, MCI_RESUME, 0, 0);
		else
			;
		return 0;
	}

	case MM_MCINOTIFY: {
		MCI_PLAY_PARMS mciPlay;

		if (wParam != MCI_NOTIFY_SUCCESSFUL) {
			if (wParam == MCI_NOTIFY_FAILURE)
				MessageBox(NULL, TEXT("CDトレイがオープンされました。"), NULL, MB_ICONWARNING);
			return 0;
		}
		
		mciSendCommand(mciDeviceId, MCI_SEEK, MCI_SEEK_TO_START, 0);

		mciPlay.dwCallback = (DWORD_PTR)hwnd;
		mciSendCommand(mciDeviceId, MCI_PLAY, MCI_NOTIFY, (DWORD_PTR)&mciPlay);

		return 0;
	}

	case WM_DESTROY:
		mciSendCommand(mciDeviceId, MCI_STOP, 0, 0);
		mciSendCommand(mciDeviceId, MCI_CLOSE, 0, 0);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

BOOL IsExistCDAudio(MCIDEVICEID mciDeviceId)
{
	MCI_STATUS_PARMS mciStatus;
	
	mciStatus.dwItem = MCI_STATUS_MEDIA_PRESENT;
	mciSendCommand(mciDeviceId, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&mciStatus);
	if (mciStatus.dwReturn == 0) {
		if (MessageBox(NULL, TEXT("CDがセットされていません。CDをセットしますか"), NULL, MB_YESNO) == IDYES) {
			MCI_SET_PARMS mciSet;
			mciSendCommand(mciDeviceId, MCI_SET, MCI_SET_DOOR_OPEN, (DWORD_PTR)&mciSet);
		}
		return FALSE;
	}

	mciStatus.dwItem  = MCI_CDA_STATUS_TYPE_TRACK;
	mciStatus.dwTrack = 1;
	mciSendCommand(mciDeviceId, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&mciStatus);
	if (mciStatus.dwReturn != MCI_CDA_TRACK_AUDIO) {
		MessageBox(NULL, TEXT("音楽CDではありません。"), NULL, MB_ICONWARNING);
		return FALSE;
	}

	return TRUE;
}

再生を行うには、mciSendCommandの第2引数にMCI_PLAYを指定することになります。 第3引数に指定しているMCI_NOTIFYは再生終了後に通知を受け取るという意味であり、 MCI_PLAY_PARMS.dwCallbackにウインドウハンドルを指定すれば、 再生終了後にMM_MCINOTIFYが送られることになります。 MM_MCINOTIFYの処理は、次のようになっています。

case MM_MCINOTIFY: {
	MCI_PLAY_PARMS mciPlay;

	if (wParam != MCI_NOTIFY_SUCCESSFUL) {
		if (wParam == MCI_NOTIFY_FAILURE)
			MessageBox(NULL, TEXT("CDトレイがオープンされました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	mciSendCommand(mciDeviceId, MCI_SEEK, MCI_SEEK_TO_START, 0);

	mciPlay.dwCallback = (DWORD_PTR)hwnd;
	mciSendCommand(mciDeviceId, MCI_PLAY, MCI_NOTIFY, (DWORD_PTR)&mciPlay);

	return 0;
}

ループ再生のために、MCI_PLAYコマンドを実行するのは当然ですが、 その前にMCI_SEEKコマンドにMCI_SEEK_TO_STARTを指定し、 現在位置を先頭トラックに設定しておく必要があります。 wParamは、再生が終了した理由で送られた場合はMCI_NOTIFY_SUCCESSFULが格納されていますが、 一時停止によって送られ場合はMCI_NOTIFY_ABORTEDが格納されます。 また、CDトレイがオープンされた場合は、MCI_NOTIFY_FAILUREが格納されます。 これらの場合は、ループ再生を行う必要がないので、 MCI_NOTIFY_SUCCESSFUL以外は考慮しないようにします。 なお、Windows VistaではMM_MCINOTIFYが送られてこないことを確認しています。

一時停止の処理は、次のようになっています。

case WM_LBUTTONUP: {
	MCI_STATUS_PARMS mciStatus;
	
	mciStatus.dwItem = MCI_STATUS_MODE;
	mciSendCommand(mciDeviceId, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&mciStatus);

	if (mciStatus.dwReturn == MCI_MODE_PLAY)
		mciSendCommand(mciDeviceId, MCI_PAUSE, 0, 0);
	else if (mciStatus.dwReturn == MCI_MODE_PAUSE)
		mciSendCommand(mciDeviceId, MCI_RESUME, 0, 0);
	else
		;
	return 0;
}

MCIデバイスの現在の状態は、MCI_STATUS_PARMS.dwItemにMCI_STATUS_MODEを指定することで確認できます。 返された値がMCI_MODE_PLAYである場合は、再生状態ということなのでMCI_PAUSEで一時停止し、 MCI_MODE_PAUSEの場合は一時停止状態であるため、MCI_RESUMEで再開するようにしています。


戻る