音乐播放器播放列表实现解析
playlist.h头文件
#ifndef PLAYLIST_H #define PLAYLIST_H int PlayListInit(); int PlayListDestroy(); char *GetItemFromDefaultPlaylist(int pos); int GetDefaultPlaylistTotalItem(); int DefaultPlaylistAddItem(char *itempath); int DefaultPlaylistDeleteItem(int pos); int DefaultPlaylistSave(); #endif
默认播放列表为default.m3u,位于可执行文件相同目录,歌曲条目用单链表存储,默认播放列表为g_playlisthead头及指向末尾项的g_playlisttail两个指针控制。
#include <windows.h> #include <stdio.h> #include <string.h> #define DEFAULT_PLAYLIST "default.m3u" typedef struct listNode listNode; struct listNode { char val[MAX_PATH]; listNode *next; }; listNode *g_playlisthead = NULL; listNode *g_playlisttail = NULL; int bChange = 0; int g_playlistitemcount = 0; char g_defaultlistpath[MAX_PATH] = {0};
PlayListInit函数
创建播放列表
int PlayListInit() { return MakePlayList(); } int MakePlayList() { FILE *fp; char defaultlistpath[MAX_PATH] = {0}; char *pplpath; char buf[MAX_PATH] = {0}; GetModuleFileName(NULL, defaultlistpath, MAX_PATH); pplpath = strrchr(defaultlistpath, '\\'); pplpath[1] = '\0'; strcpy(g_defaultlistpath, defaultlistpath); sprintf(defaultlistpath, "%s%s", defaultlistpath, DEFAULT_PLAYLIST); fp = fopen(defaultlistpath, "r"); if (fp == NULL) { printf("open %s error\n", DEFAULT_PLAYLIST); return -1; } do { char *p; listNode *lnode; p = fgets(buf, MAX_PATH, fp); if (p == NULL) { break; } g_playlistitemcount++; lnode = malloc(sizeof(listNode)); memset(lnode->val, 0, MAX_PATH); strncpy(lnode->val, buf, strlen(buf) - 1); //不读取\n lnode->next = NULL; if (g_playlisthead == NULL) { g_playlisthead = lnode; g_playlisttail = g_playlisthead; } else { g_playlisttail->next = lnode; g_playlisttail = lnode; } } while (1); return 0; }
打开default.m3u文件并循环获取每一行,每一行表示一首歌曲,分别放入播放链表g_playlisthead末尾。
PlayListDestroy函数
销毁默认播放列表
int PlayListDestroy() { return DestroyDefaultPlayList(); } int DestroyDefaultPlayList() { listNode *lnode; listNode *lnodenext; if (g_playlisthead == NULL) { return 0; } lnode = g_playlisthead; while (lnode) { lnodenext = lnode->next; free(lnode); lnode = lnodenext; } return 0; }
释放播放链表每一项内容。
GetItemFromDefaultPlaylist函数
通过pos获取歌曲路径,pos从0开始表示第一首歌
char *GetItemFromDefaultPlaylist(int pos) { int i = 0; listNode *lnode; if (g_playlisthead == NULL) { return NULL; } if (pos >= g_playlistitemcount) { return NULL; } lnode = g_playlisthead; while (i < pos && lnode != NULL) { i++; lnode = lnode->next; } if (lnode == NULL) { return NULL; } return lnode->val; }
GetDefaultPlaylistTotalItem函数
获取默认播放列表所有条目个数,g_playlistitemcount记录条目总数,在添加歌曲时递增,删除歌曲时递减。
int GetDefaultPlaylistTotalItem() { return g_playlistitemcount; }
DefaultPlaylistAddItem函数
添加歌曲,传入歌曲的绝对路径
// 添加一项,itempath绝对路径 int DefaultPlaylistAddItem(char *itempath) { WIN32_FIND_DATA finddata; HANDLE handle; handle = FindFirstFile(itempath, &finddata); if (INVALID_HANDLE_VALUE == handle) { return -1; } if (finddata.cFileName) { listNode *lnode; lnode = malloc(sizeof(listNode)); memset(lnode->val, 0, MAX_PATH); strcpy(lnode->val, itempath); lnode->next = NULL; if (g_playlisthead == NULL) { g_playlisthead = lnode; g_playlisttail = g_playlisthead; } else { g_playlisttail->next = lnode; g_playlisttail = lnode; } g_playlistitemcount++; bChange++; } return 0; }
DefaultPlaylistDeleteItem函数
通过pos位置删除歌曲,pos从0开始表示第一项
// 删除一项,pos从0开始 int DefaultPlaylistDeleteItem(int pos) { listNode *lnode; listNode *lnodeprev; int i = 0; if (pos >= g_playlistitemcount || g_playlisthead == NULL) { return 0; } bChange++; // 只有一项 if (g_playlistitemcount == 1) { free(g_playlisthead); g_playlisthead = NULL; g_playlisttail = NULL; g_playlistitemcount--; return 0; } // 删除第一项 if (pos == 0) { lnode = g_playlisthead; g_playlisthead = lnode->next; free(lnode); lnode = NULL; return 0; } // 删除最后一项 if (pos == g_playlistitemcount -1) { lnode = g_playlisthead; while (lnode->next->next) { lnode = lnode->next; } g_playlisttail = lnode; lnode = lnode->next; g_playlisttail->next = NULL; free(lnode); lnode = NULL; g_playlistitemcount--; return 0; } // 删除中间一项 lnodeprev = g_playlisthead; while (i < pos - 1) { lnodeprev = lnodeprev->next; i++; } lnode = lnodeprev->next; free(lnode); lnode = NULL; lnodeprev->next = NULL; g_playlistitemcount--; return 0; }
DefaultPlaylistSave函数
保存修改过的默认播放列表
int DefaultPlaylistSave() { FILE *fp; listNode *lnode; char plpath[MAX_PATH] = {0}; if (bChange == 0 || g_playlistitemcount == 0 || g_playlisthead == NULL) { return 0; } sprintf(plpath, "%s%s", g_defaultlistpath, DEFAULT_PLAYLIST); fp = fopen(plpath, "w+"); if (fp == NULL) { printf("open %s error\n", DEFAULT_PLAYLIST); return -1; } lnode = g_playlisthead; while (lnode) { fputs(lnode->val, fp); //fwrite(lnode->val, 1, strlen(lnode->val), fp); fputs("\n", fp); lnode = lnode->next; } fflush(fp); fclose(fp); return 0; }
至此播放列表的实现已经完毕,其中包括初始化播放链表,通过歌曲绝对路径添加歌曲,删除歌曲及保存播放列表功能。接下来实现为显示播放时间进度及歌词。
BeginTimeEvent函数
创建定时器线程,该线程TimeEvenProc中包含显示播放时间进度、一首歌结束后默认播放下一曲、同步显示歌词功能。
void TimeEventProc(UINT wTimerID, UINT msg,DWORD dwUser,DWORD dwl,DWORD dw2) { long long curpos = 0; long long stoppos = 0; int curtime = 0; int stoptime = 0; char *p = NULL; char *szLryic = NULL; char szTime[1024] = {0}; double rate = 0.0; mpxGetPositions(&curpos, &stoppos); if (curpos == stoppos) { // 播放下一曲 WCHAR songpath[MAX_PATH] = {0}; char lyricpath[MAX_PATH] = {0}; char *p; playIndex++; if (playIndex > GetDefaultPlaylistTotalItem()) { playIndex = 0; } p = GetItemFromDefaultPlaylist(playIndex); if (p == NULL) { return; } MakeLyricPathFromSongPath(p, lyricpath); LyricDestroy(); LyricInit(lyricpath); MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p, strlen(p), songpath, MAX_PATH); mpxPlayFile(songpath); status = C_BTN_PLAY; SetWindowText(btnPlay, "pause"); SetWindowText(g_hWnd, p); } curtime = (int)(curpos / (double)10000000); stoptime = (int)(stoppos / (double)10000000); sprintf(szTime, "%02d:%02d->%02d:%02d", curtime/60, curtime%60, stoptime/60, stoptime%60); SendMessage(timeStatic, WM_SETTEXT, 0, szTime); szLryic = GetLyricByStartTime(curtime); if (szLryic != NULL) { SendMessage(lyricStatic, WM_SETTEXT, 0, szLryic); } } void BeginTimeEvent() { if((g_timeId = timeSetEvent(500, 1,(LPTIMECALLBACK)TimeEventProc,(DWORD_PTR)0,TIME_PERIODIC)) == 0) { printf("time set event error!\n"); } }
mpxGetPositions获取播放的当前位置和停止位置,当播放结束后有可能获取不到值,此时两个变量的值为初始值0,此时也表示播放已结束,此时playIndex递增并表示播放下一曲。
curpos和stoppos获取到的值需要除以10000000的结果才表示秒,然后调用SendMessage(timeStatic, WM_SETTEXT, 0, szTime)将当前播放的时间和结束时间显示在timeStatic组件上表示播放歌曲的时间进度。
通过curtime单位为1/100秒,传入GetLyricByStartTime(curtime)函数获取当前时间对应的歌词,找的对应的歌词则将歌词显示在lyricStatic组件上,实现同步显示歌词功能。
EndTimeEvent函数
销毁定时器
void EndTimeEvent() { if (g_timeId != 0) { timeKillEvent(g_timeId); g_timeId = 0; } }
下一篇音乐播放器同步歌词源码解析