音乐播放器播放列表实现解析
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
#include
#include
#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末尾。
销毁默认播放列表
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;
}
释放播放链表每一项内容。
通过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;
}
通过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;
}
保存修改过的默认播放列表
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;
}
至此播放列表的实现已经完毕,其中包括初始化播放链表,通过歌曲绝对路径添加歌曲,删除歌曲及保存播放列表功能。接下来实现为显示播放时间进度及歌词。
创建定时器线程,该线程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组件上,实现同步显示歌词功能。
销毁定时器
void EndTimeEvent()
{
if (g_timeId != 0)
{
timeKillEvent(g_timeId);
g_timeId = 0;
}
}
下一篇音乐播放器同步歌词源码解析