音乐播放器之源码解析三

来源:互联网 发布:衣柜设计软件用哪款好 编辑:程序博客网 时间:2024/05/18 02:04

mpxUI代码解析

全局变量

/****************************************************************************** author menghun3@gmail.com** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation.* * source code address https://github.com/menghun3/mpx******************************************************************************/#include <windows.h>#include <CommCtrl.h>#include <stdio.h>#include <strsafe.h>#include <conio.h>#include <ShlObj.h>#include "mpx.h"#include "playlist.h"#include "lyric.h"#pragma comment (lib, "Comctl32.lib")#pragma comment (lib, "mpx.lib")#pragma comment (lib, "winmm.lib")// 版本号#define MPXUI_MAJOR_VERSION 0#define MPXUI_MINOR_VERSION 1#define MPXUI_REVISION_VERSION 0#define MPXUI_BUILD_VERSION 0//component http://msdn.microsoft.com/en-us/library/windows/desktop/bb775491(v=vs.85).aspx//control library http://msdn.microsoft.com/en-us/library/windows/desktop/bb773169(v=vs.85).aspxtypedef enum{C_BTN_PLAY = 1,C_BTN_PAUSE,C_BTN_PREV,C_BTN_NEXT,C_BTN_STOP,C_BTN_FROM_PL,C_LYRIC_STATIC,C_TIME_STATIC,C_PL_ADD,C_PL_ADDDIR,C_PL_DEL,C_PL_SAVE,C_LAST}COMPONENT_ID;HINSTANCE g_hInst = NULL;HWND g_hWnd = NULL;int playIndex = 0;static int status = -1; // C_BTN_PLAY:play;C_BTN_PAUSE:pause;C_BTN_STOP:stopUINT g_timeId = 0;


全局变量中包含表示播放状态的宏和表示组件的宏

主界面函数

int APIENTRY WinMain(HINSTANCE hInstance, //应用程序的实例句柄,HINSTANCE hPrevInstance,LPSTR lpCmdLine, //命令行int nCmdShow) //显示方式{MSG msg;HWND hWnd;char szTitle[]="mpx"; // The title bar textWNDCLASSEX wcex={0};g_hInst = hInstance;wcex.cbSize = sizeof(WNDCLASSEX); //WNDCLASSEX结构体大小wcex.style = CS_HREDRAW | CS_VREDRAW; //位置改变时重绘wcex.lpfnWndProc = (WNDPROC)WndProc; //消息处理函数wcex.hInstance = 0; //当前实例句柄wcex.hbrBackground = (HBRUSH)COLOR_WINDOWFRAME; //背景色wcex.lpszClassName = "mpxclass"; //参窗口类名wcex.hIcon =0; //图标wcex.hCursor =LoadCursor(NULL, IDC_ARROW); //光标wcex.lpszMenuName =0; //菜单名称wcex.hIconSm =0; //最小化图标RegisterClassEx(&wcex); //注册窗口类hWnd = CreateWindowEx(WS_EX_ACCEPTFILES,"mpxclass", szTitle, WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN|WS_VISIBLE, //创建窗口CW_USEDEFAULT,CW_USEDEFAULT,800, 400, NULL, NULL, 0, NULL);if (!hWnd){return FALSE;}g_hWnd = hWnd;while (GetMessage(&msg, NULL, 0, 0)) // 消息循环:{TranslateMessage(&msg); //转化虚拟按键到字符消息DispatchMessage(&msg); //分派消息调用回调函数}return msg.wParam;}


创建一个800*400的窗口,其他设置参考MSDN例子。

各个组件全局变量

HWND btnPrev = NULL;HWND btnPlay = NULL;HWND btnStop = NULL;HWND btnNext = NULL;HWND playlistLB = NULL;HWND lyricStatic = NULL;HWND timeStatic = NULL;HWND btnPLAdd = NULL;HWND btnPLAddDir = NULL;HWND btnPLDel = NULL;HWND btnPLSave = NULL;


 包含前一曲播放按钮、播放/暂停按钮、停止按钮、下一曲播放按钮、播放列表组件、歌词显示组件、显示播放进度时间组件、添加歌曲按钮、添加文件夹歌曲按钮、删除歌曲按钮、保存歌曲按钮。这些组件在创建窗口时创建。

创建组件部分代码如下,分别调用对应的组件创建函数。

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){PAINTSTRUCT ps;HDC hdc;RECT rect;static RECT oldRect;switch (message) {……case WM_CREATE:{GetWindowRect(hWnd, &oldRect);CreateCtrlBtn(hWnd);CreateListBox(hWnd);CreateLyricStatic(hWnd);CreateTimeStatic(hWnd);CreatePlayListBtn(hWnd);}break;……default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;}


CreateCtrlBtn

创建控制歌曲播放按钮函数。

void CreateCtrlBtn(HWND hWnd){btnPrev = CreateWindowEx(WS_EX_ACCEPTFILES, WC_BUTTON, "prev", WS_VISIBLE|WS_CHILD, 0, 0, 50, 30, hWnd, C_BTN_PREV, g_hInst, NULL);btnPlay = CreateWindowEx(WS_EX_ACCEPTFILES, WC_BUTTON, "play", WS_VISIBLE|WS_CHILD, 60, 0, 50, 30, hWnd, C_BTN_PLAY, g_hInst, NULL);btnStop = CreateWindowEx(WS_EX_ACCEPTFILES, WC_BUTTON, "stop", WS_VISIBLE|WS_CHILD, 120, 0, 50, 30, hWnd, C_BTN_STOP, g_hInst, NULL);btnNext = CreateWindowEx(WS_EX_ACCEPTFILES, WC_BUTTON, "next", WS_VISIBLE|WS_CHILD, 180, 0, 50, 30, hWnd, C_BTN_NEXT, g_hInst, NULL);}

其中C_BTN_PREV表示该组件ID,该使用方式不正式。

CreateListBox函数

创建播放列表

void CreateListBox(HWND hWnd){int i = 0;POINT p = {10, 70};int ident = 0;int plItemCount = 0;int colWidth = 0;int colMaxWidth = 300;RECT rect;GetWindowRect(hWnd, &rect);PlayListInit();plItemCount = GetDefaultPlaylistTotalItem();playlistLB = CreateWindowEx(WS_EX_ACCEPTFILES, WC_LISTBOX, "default", WS_VISIBLE | LBS_COMBOBOX | WS_CHILD|WS_VSCROLL|LBN_DBLCLK|LBS_NOTIFY, 0, 70, 380, rect.bottom - rect.top - 120, hWnd, C_BTN_FROM_PL, g_hInst, NULL); for (i = 0; i < plItemCount; i++){char *val = NULL;char *pval = NULL;char plItem[MAX_PATH] = {0};val = GetItemFromDefaultPlaylist(i);pval = strrchr(val, '\\');if (pval == NULL){pval = val;}else{pval++;}sprintf(plItem, "%03d.%s", i+1, pval);SendMessage(playlistLB, LB_INSERTSTRING, (-1), plItem);}}


调用PlayListInit()初始化默认播放列表,然后通过GetItemFromDefaultPlaylist函数获取所有歌曲路径并将其插入到播放列表组件中。

PlayListInit()实现见播放列表文件。

CreateLyricStatic函数

创建歌词组件

void CreateLyricStatic(HWND hWnd){RECT rect;GetWindowRect(hWnd, &rect);lyricStatic = CreateWindowEx(WS_EX_ACCEPTFILES, WC_STATIC, "lyric", WS_VISIBLE | WS_CHILD|WS_VSCROLL|LBS_NOTIFY|SS_CENTER, 400, 70, 380, rect.bottom - rect.top - 120, hWnd, C_LYRIC_STATIC, g_hInst, NULL);}


CreateTimeStatic函数

创建显示播放时间的组件

void CreateTimeStatic(HWND hWnd){timeStatic = CreateWindowEx(WS_EX_ACCEPTFILES, WC_STATIC, "00:00->00:00", WS_VISIBLE | WS_CHILD|LBS_NOTIFY|SS_CENTER, 240, 0, 100, 30, hWnd, C_TIME_STATIC, g_hInst, NULL);}


CreatePlayListBtn函数

创建添加歌曲等按钮

void CreatePlayListBtn(HWND hWnd){RECT rect;int yPos = GetWindowRect(hWnd, &rect);yPos = 40;btnPLAdd = CreateWindowEx(WS_EX_ACCEPTFILES, WC_BUTTON, "add", WS_VISIBLE|WS_CHILD, 0, yPos, 50, 20, hWnd, C_PL_ADD, g_hInst, NULL);btnPLAddDir = CreateWindowEx(WS_EX_ACCEPTFILES, WC_BUTTON, "dir", WS_VISIBLE|WS_CHILD, 60, yPos, 50, 20, hWnd, C_PL_ADDDIR, g_hInst, NULL);btnPLDel = CreateWindowEx(WS_EX_ACCEPTFILES, WC_BUTTON, "del", WS_VISIBLE|WS_CHILD, 120, yPos, 50, 20, hWnd, C_PL_DEL, g_hInst, NULL);btnPLSave = CreateWindowEx(WS_EX_ACCEPTFILES, WC_BUTTON, "save", WS_VISIBLE|WS_CHILD, 180, yPos, 50, 20, hWnd, C_PL_SAVE, g_hInst, NULL);}


至此所有组件已经创建完毕,接下来操作这些组件达到控制目的。所有的实现都在WndProc回调函数中。

WndProc函数

控制组件操作

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){PAINTSTRUCT ps;HDC hdc;RECT rect;static RECT oldRect;switch (message) {case WM_PAINT: //重绘消息case WM_COMMAND:……default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;}


按钮的操作大部分在WM_COMMAND消息中,当各个组件被操作时此处接收到消息,通过LOWORD(wParam)获取组件ID,即如C_BTN_PLAY播放/暂停按钮。

case WM_COMMAND:{int id = LOWORD(wParam);switch (id){case C_BTN_PREV:{char *p = NULL;static WCHAR songpath[MAX_PATH] = {0};char lyricpath[MAX_PATH] = {0};memset(songpath, 0, MAX_PATH);playIndex--;if (playIndex < 0){playIndex =  GetDefaultPlaylistTotalItem();}p = GetItemFromDefaultPlaylist(playIndex);if (p == NULL){break;}MakeLyricPathFromSongPath(p, lyricpath);LyricDestroy();LyricInit(lyricpath);MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p, strlen(p), songpath, MAX_PATH);EndTimeEvent();mpxPlayFile(songpath);BeginTimeEvent();status = C_BTN_PLAY;SetWindowText(btnPlay, "pause");SetWindowText(hWnd, p);}break;case C_BTN_PLAY:{if (status != C_BTN_PLAY ){mpxPlay();status = C_BTN_PLAY;SetWindowText(btnPlay, "pause");}else if (status == C_BTN_PLAY){mpxPause();status = C_BTN_PAUSE;SetWindowText(btnPlay, "play");}}break;case C_BTN_STOP:{if (status == C_BTN_PLAY || status == C_BTN_PAUSE){mpxStop();status = C_BTN_STOP;SetWindowText(btnPlay, "play");}}break;case C_BTN_NEXT:{char *p = NULL;static WCHAR songpath[MAX_PATH] = {0};char lyricpath[MAX_PATH] = {0};memset(songpath, 0, MAX_PATH);playIndex++;if (playIndex > GetDefaultPlaylistTotalItem()){playIndex = 0;}p = GetItemFromDefaultPlaylist(playIndex);if (p == NULL){break;}MakeLyricPathFromSongPath(p, lyricpath);LyricDestroy();LyricInit(lyricpath);MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p, strlen(p), songpath, MAX_PATH);EndTimeEvent();mpxPlayFile(songpath);BeginTimeEvent();status = C_BTN_PLAY;SetWindowText(btnPlay, "pause");SetWindowText(hWnd, p);}break;case C_BTN_FROM_PL:{int selid = HIWORD(wParam);switch (selid) { case LBN_DBLCLK:{// Get selected index.int lbItem = (int)SendMessage(playlistLB, LB_GETCURSEL, 0, 0); char *p = NULL;static WCHAR songpath[MAX_PATH] = {0};char lyricpath[MAX_PATH] = {0};memset(songpath, 0, MAX_PATH);playIndex = lbItem;p = GetItemFromDefaultPlaylist(playIndex);if (p == NULL){break;}MakeLyricPathFromSongPath(p, lyricpath);LyricDestroy();LyricInit(lyricpath);MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p, strlen(p), songpath, MAX_PATH);EndTimeEvent();mpxPlayFile(songpath);BeginTimeEvent();status = C_BTN_PLAY;SetWindowText(btnPlay, "pause");SetWindowText(hWnd, p);}break;default:{}break;}}break;case C_PL_ADD:{AddFile(hWnd);}break;case C_PL_ADDDIR:{AddDirFile(hWnd);}break;case C_PL_DEL:{DelFile(hWnd);}break;case C_PL_SAVE:{DefaultPlaylistSave();}break;default:break;}}break;


实现双击播放列表中某一歌曲播放歌曲功能。LOWORD(wParam)IDC_BTN_FROM_PL时表示播放列表被操作,而消息HIWORD(wParam)表示操作该播放列表的消息类型,其中LBN_DBLCLK表示双击播放列表。

case C_BTN_FROM_PL:{int selid = HIWORD(wParam);switch (selid) { case LBN_DBLCLK:{// Get selected index.int lbItem = (int)SendMessage(playlistLB, LB_GETCURSEL, 0, 0); char *p = NULL;static WCHAR songpath[MAX_PATH] = {0};char lyricpath[MAX_PATH] = {0};memset(songpath, 0, MAX_PATH);playIndex = lbItem;p = GetItemFromDefaultPlaylist(playIndex);if (p == NULL){break;}MakeLyricPathFromSongPath(p, lyricpath);LyricDestroy();LyricInit(lyricpath);MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p, strlen(p), songpath, MAX_PATH);EndTimeEvent();mpxPlayFile(songpath);BeginTimeEvent();status = C_BTN_PLAY;SetWindowText(btnPlay, "pause");SetWindowText(hWnd, p);}break;default:{}break;}}break;


通过SendMessage(playlistLB, LB_GETCURSEL,0,0)获取被双击的条目索引,该索引与播放列表歌曲一一对应,因此可以通过GetItemFromDefaultPlaylist(playIndex)获取要播放的歌曲路径。调用MakeLyricPathFromSongPath(p, lyricpath);转换歌曲名称为歌词名称并调用LyricInit(lyricpath)初始化歌词,之后调用mpxPlayFile(songpath)开始播放歌曲并创建定时器BeginTimeEvent()检查播放进度。同时设置播放状态及播放按钮状态。

其中,在初始化歌词之前需要销毁之前的歌词,创建定时器之前需要销毁之前的定时器,每一个定时器只用于当前播放歌曲。

playIndex为全局变量,记录播放列表当前播放曲目,播放上一曲或者播放下一曲只需将其自减或者自增然后其他步骤同上所述。

 

播放上一曲、播放下一曲、暂停、播放及停止功能实现

int id = LOWORD(wParam);switch (id){case C_BTN_PREV:{char *p = NULL;static WCHAR songpath[MAX_PATH] = {0};char lyricpath[MAX_PATH] = {0};memset(songpath, 0, MAX_PATH);playIndex--;if (playIndex < 0){playIndex =  GetDefaultPlaylistTotalItem();}p = GetItemFromDefaultPlaylist(playIndex);if (p == NULL){break;}MakeLyricPathFromSongPath(p, lyricpath);LyricDestroy();LyricInit(lyricpath);MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p, strlen(p), songpath, MAX_PATH);EndTimeEvent();mpxPlayFile(songpath);BeginTimeEvent();status = C_BTN_PLAY;SetWindowText(btnPlay, "pause");SetWindowText(hWnd, p);}break;case C_BTN_PLAY:{if (status != C_BTN_PLAY ){mpxPlay();status = C_BTN_PLAY;SetWindowText(btnPlay, "pause");}else if (status == C_BTN_PLAY){mpxPause();status = C_BTN_PAUSE;SetWindowText(btnPlay, "play");}}break;case C_BTN_STOP:{if (status == C_BTN_PLAY || status == C_BTN_PAUSE){mpxStop();status = C_BTN_STOP;SetWindowText(btnPlay, "play");}}break;case C_BTN_NEXT:{char *p = NULL;static WCHAR songpath[MAX_PATH] = {0};char lyricpath[MAX_PATH] = {0};memset(songpath, 0, MAX_PATH);playIndex++;if (playIndex > GetDefaultPlaylistTotalItem()){playIndex = 0;}p = GetItemFromDefaultPlaylist(playIndex);if (p == NULL){break;}MakeLyricPathFromSongPath(p, lyricpath);LyricDestroy();LyricInit(lyricpath);MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p, strlen(p), songpath, MAX_PATH);EndTimeEvent();mpxPlayFile(songpath);BeginTimeEvent();status = C_BTN_PLAY;SetWindowText(btnPlay, "pause");SetWindowText(hWnd, p);}break;


播放上一曲、播放下一曲除了歌曲索引与双击播放列表播放歌曲不同其他都一样。

 

添加、删除歌曲保存歌曲目录实现,LOWORD(wParam) ID为对应的C_PL_ADD等值

case C_PL_ADD:{AddFile(hWnd);}break;case C_PL_ADDDIR:{AddDirFile(hWnd);}break;case C_PL_DEL:{DelFile(hWnd);}break;case C_PL_SAVE:{DefaultPlaylistSave();}break;


分别调用了AddFileAddDirFileDelFileDefaultPlaylistSave函数实现。歌曲列表实现见playlist.c文件。

AddFile函数

添加歌曲功能

int AddFile(HWND hWnd){OPENFILENAME fn;char    filefilter[] ="All Supported files\0*.mp1;*.mp2;*.mp3;*.m3u;*.ogg;*.pls;*.wav\0MPEG audio files (*.mp1;*.mp2;*.mp3)\0*.mp1;*.mp2;*.mp3\0Vorbis files (*.ogg)\0Playlist files (*.m3u;*.pls)\0*.m3u;*.pls\0WAV files (*.wav)\0*.wav\0All Files (*.*)\0*.*\0";BOOL    retVal = FALSE;char    initialfilename[MAX_PATH * 100] = "";fn.lStructSize = sizeof(OPENFILENAME);fn.hwndOwner = hWnd;fn.hInstance = NULL;fn.lpstrFilter = filefilter;fn.lpstrCustomFilter = NULL;fn.nMaxCustFilter = 0;fn.nFilterIndex = 0;fn.lpstrFile = initialfilename;fn.nMaxFile = MAX_PATH * 200;fn.lpstrFileTitle = NULL;fn.nMaxFileTitle = 0;fn.lpstrInitialDir = "./";fn.lpstrTitle = NULL;fn.Flags =OFN_ALLOWMULTISELECT | OFN_HIDEREADONLY | OFN_EXPLORER |OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_ENABLESIZING;fn.nFileOffset = 0;fn.nFileExtension = 0;fn.lpstrDefExt = NULL;fn.lCustData = 0;fn.lpfnHook = NULL;fn.lpTemplateName = NULL;retVal = GetOpenFileName(&fn);if (retVal != FALSE){char path_buffer[MAX_PATH];char *pval = NULL;strcpy(path_buffer, fn.lpstrFile);pval = strrchr(path_buffer, '\\');if (pval == NULL){pval = path_buffer;}else{pval++;}SendMessage(playlistLB, LB_INSERTSTRING, (-1), pval);DefaultPlaylistAddItem(path_buffer);}return 0;}


GetOpenFileName函数获取添加的文件路径,使用SendMessage(playlistLB,LB_INSERTSTRING, (-1), pval)将歌曲名称发送到播放列表playlistBL组件,并调用DefaultPlaylistAddItem(path_buffer);函数将歌曲路径添加到默认播放列表中。

psSendMessage消息函数详见MSDN,其中LB_INSERTSTRINGlistbox组件插入到末尾的消息。

AddDirFile函数

添加整个文件夹歌曲

int AddDirFile(HWND hWnd){BROWSEINFO browseinfo;LPITEMIDLIST itemlist;int     image = 0;char    directorychoice[MAX_PATH];char    fullpath[MAX_PATH];HANDLE  found;WIN32_FIND_DATA finddata;char    pathbuf2[MAX_PATH];chardirBuf[MAX_PATH];browseinfo.hwndOwner = hWnd;browseinfo.pidlRoot = NULL;browseinfo.pszDisplayName = directorychoice;browseinfo.lpszTitle = "Choose a directory to add";browseinfo.ulFlags = BIF_EDITBOX;browseinfo.lpfn = NULL;browseinfo.lParam = 0;browseinfo.iImage = image;itemlist = SHBrowseForFolder(&browseinfo);if (itemlist == NULL){return 1;}SHGetPathFromIDList(itemlist,dirBuf);if (dirBuf[strlen(dirBuf) - 1] == '\\'&& strcmp(dirBuf, "\\") != 0) dirBuf[strlen(dirBuf) - 1] ='\0';strcpy(fullpath, dirBuf);if (strcmp(fullpath, "\\") == 0)strcat(fullpath, ".\\*.mp3");elsestrcat(fullpath, "\\*.mp3");found = FindFirstFile(fullpath, &finddata);do {char    somepath[MAX_PATH];strcpy(somepath, dirBuf);if (strcmp(somepath, "\\") == 0)strcpy(somepath, "\\.");sprintf(pathbuf2, "%s\\%s", somepath, finddata.cFileName);if ((finddata.cFileName[0] != '.' && finddata.cFileName[1] != 0)&& (finddata.cFileName[0] != '.'&& finddata.cFileName[1] != '.'&& finddata.cFileName[2] != 0)){char *pval = NULL;pval = strrchr(pathbuf2, '\\');if (pval == NULL){pval = pathbuf2;}else{pval++;}SendMessage(playlistLB, LB_INSERTSTRING, (-1), pval);DefaultPlaylistAddItem(pathbuf2);}}while (FindNextFile(found, &finddata));FindClose(found);return 0;}


DelFile函数

删除歌曲功能

int DelFile(HWND hWnd){// Get selected index.int lbItem = -1;lbItem = (int)SendMessage(playlistLB, LB_GETCURSEL, 0, 0);if (lbItem != -1){DefaultPlaylistDeleteItem(lbItem);SendMessage(playlistLB, LB_DELETESTRING, lbItem, 0);}return 0;}


调用SendMessage(playlistLB, LB_GETCURSEL,0,0)获取当前选中的歌曲索引,并调用DefaultPlaylistDeleteItem(lbItem);SendMessage(playlistLB,LB_DELETESTRING, lbItem,0);分别删除播放列表中的歌曲和播放列表组件中项。

 

下一篇音乐播放器播放列表实现解析

 

 

原创粉丝点击