音乐播放器之源码解析四

时间:2021-08-12 17:19:24

音乐播放器播放列表实现解析

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获取歌曲路径,pos0开始表示第一首歌

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位置删除歌曲,pos0开始表示第一项

// 删除一项,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递增并表示播放下一曲。

curposstoppos获取到的值需要除以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;
	}
}


下一篇音乐播放器同步歌词源码解析