主要实现及功能:
1,播放,暂停及继续,上一页下一页,上一首下一首和退出
2,歌曲结束根据播放模式来自动下一首
3,可根据不同格式导入歌曲
4,ANSI控制码实现简单的界面制作
5, 通过管道来向mplayer播放器发送指令
6, 在播歌曲滑动显示
主要技术点:
父进程捕捉SIGCHLD信号通过信号处理函数wait收尸来获得mplayer的退出状态,当mplayer正常结束时,信号处理函数创建一个分离的线程重新打开mplayer播放器根据播放模式来自动播放下一首,main线程通过temp标志位来获取mplayer的控制权
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glob.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <pthread.h>
#include "mplayer.h"
#include "double_link.h"
#define PATTERN "/home/ll/Music/*.mp3"
#define FIFOPATH "music.fifo"
#define MUSICSIZE 256
#define OPTSIZE 512
#define NAMEBUFSIZE 256
#define ROW 15
#define LINE 10
/* 音乐名字数组*/
char musicBuff[MUSICSIZE] = {0};
/* 链表头结点 */
LList_st *head = NULL;
/* 链表当前位置 */
llist *cur = NULL;
/* 进程号 */
pid_t pid;
/* golb参数 */
glob_t buff;
/* 播放模式 */
int playWay = ORDER;
/* 播放器状态 */
int tmp = 0;
/* 主界面 */
void interface()
{
int i, j, k;
printf("\033[2J\033[5;25H\033[44m--====================--------------------\033[0m\n");
printf("%c[6;25H",'\033');
for (i = 0; i < ROW; i++)
{
printf("\033[44m||\033[0m");
for (j = 0; j < LINE; j++)
{
printf(" ");
}
printf("\033[44m||\t\t||\033[0m\n\033[24C");
}
printf("\033[6;35H%s\n","歌单");
printf("\033[6;53H\033[42m%s\033[0m\n","按键说明");
printf("\033[8;52H%s\n","播放 'b'");
printf("\033[9;52H%s\n","暂停 'p'");
printf("\033[10;52H%s\n","退出 'q'");
printf("\033[12;52H%s\n","上一首 'a'");
printf("\033[13;52H%s\n","下一首 'd'");
printf("\033[15;52H%s\n","上一页 'w'");
printf("\033[16;52H%s\n","下一页 's'");
printf("\033[18;52H\033[42m%s\033[0m\n"," 播放模式 ");
printf("\033[21;25H\033[44m--====================--------------------\033[0m\n");
}
/* 歌曲路径和名字导入链表 */
LList_st *loading()
{
int i, ret;
musicNode name;
LList_st *head = NULL;
head = llist_create(sizeof(name));
if (head == NULL)
{
fprintf(stderr,"llist_create() faild\n");
exit(1);
}
glob(PATTERN,0,NULL,&buff);
for (i = 0; i < buff.gl_pathc; i++)
{
strncpy(name.musicName,buff.gl_pathv[i],NAMESIZE);
ret = llist_insert(head,&name,BEHIND);
if (ret < 0)
{
fprintf(stderr,"llist_insert() fail\n");
exit(1);
}
}
return head;
}
/* 从链表中获取歌曲名字 */
char *getNameFromLink(void *record)
{
musicNode *dest = record;
return dest->musicName;
}
/* 获取不包含路径的名字 */
char *getRealName(char *name)
{
char *pos = NULL;
pos = strrchr(name,'/');
if (pos == NULL)
return name;
return pos+1;
}
/* 模式显示 */
void showPatten()
{
if (playWay == ORDER)
printf("\033[19;52H%s\n"," 顺序播放 ");
else if (playWay == SIGLOOP)
printf("\033[19;52H%s\n"," 循环播放 ");
else
printf("\033[19;52H%s\n"," 随机播放 ");
}
/* 歌曲页数 */
int songPage()
{
int i;
int firstPage = 0;
for (i = 0; i < buff.gl_pathc; i++)
{
if (strcmp(buff.gl_pathv[i],getNameFromLink(cur->data)) == 0)
{
firstPage = i;
break;
}
}
return firstPage;
}
/* 歌单的滑动显示,因为ansi控制码不好控制,根据需求自行添加 */
/*
void *curSongNameList(void *argc)
{
int i, j, len;
int count;
char *buf = (char *)argc;
len = strlen(buf);
while (1)
{
for (j = 0; j < len; j += 3)
{
count = SIZE;
printf("\033[10;40H");
for (i = j; i < len && (i - j) < 3*N; i++)
{
printf("%c",buf[i]);
}
fflush(stdout);
usleep(700000);
printf("\033[10;40H");
while (count--)
printf(" ");
pthread_testcancel();
}
}
}
*/
/* 歌单展示
*当当前链表歌曲和播放歌曲一样时,以红色背景色显示
*
*/
void showList(llist *cur,enum song_way pageTmp)
{
char *name = NULL;
char nameBuf[NAMEBUFSIZE] = {0};
int i = 0;
int n = 0;
int firstPage;
firstPage = songPage(cur);
i = 12 * (firstPage / 12);
if (i % 12 == 0)
{
interface();
}
printf("\033[8;31H");
if (pageTmp == NEXT)
{
if (i + 12 < buff.gl_pathc)
i += 12;
}
else if (pageTmp == LAST)
{
if (i - 12 >= 0)
i -= 12;
}
for (; i < buff.gl_pathc && n < 12; i++, n++)
{
name = getRealName(buff.gl_pathv[i]);
if (strlen(name) < 18)
{
if (i == firstPage)
printf("\033[41m%s\033[0m\n\033[30C",name);
else
printf("%s\n\033[30C",name);
}
else
{
if (i == firstPage)
printf("\033[41m%s\033[0m\n\033[30C",strncpy(nameBuf,name,9));
else
printf("%s\n\033[30C",strncpy(nameBuf,name,18));
}
}
showPatten();
}
/* 线程函数,根据播放模式不同来播放下一首 */
void *playPat(void *args)
{
int n;
if (playWay == ORDER)
{
cur = cur->next;
if (cur == &head->head)
cur = cur->next;
strncpy(musicBuff,getNameFromLink(cur->data),MUSICSIZE);
showList(cur,0);
fflush(stdout);
pid = fork();
createProgress(pid, musicBuff);
}
else if (playWay == SIGLOOP)
{
fflush(stdout);
pid = fork();
createProgress(pid, musicBuff);
}
else
{
srand((unsigned int)time(NULL));
n = (1 + rand() % 10);
while (n--)
{
cur = cur->next;
}
if (cur == &head->head)
cur = cur->next;
strncpy(musicBuff,getNameFromLink(cur->data),MUSICSIZE);
showList(cur,0);
fflush(stdout);
pid = fork();
createProgress(pid,musicBuff);
}
tmp = 1;
}
/* 信号函数,接到信号时判断子进程是否正常结束,如果正常结束就根据播放模式播放下一首 */
void actionHandler(int s)
{
int status;
pthread_t tid;
wait(&status);
if (WIFEXITED(status))
{
pthread_create(&tid, NULL, playPat, NULL);
pthread_detach(tid);
tmp = 0;
}
}
/* 创建子进程播放歌曲并将3个标准流置null */
void createProgress(pid_t pid,char *musicBuff)
{
int fd;
if (pid < 0)
{
perror("fork()");
exit(1);
}
if (pid == 0)
{
fd = open("/dev/null",O_RDWR);
if (fd < 0)
{
perror("open()");
exit(1);
}
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
if (fd > 2)
close(fd);
execlp("mplayer", "mplayer", "-slave", "-quiet", "-input", "file=music.fifo", musicBuff, NULL);
perror("execlp()");
exit(1);
}
}
/* 导入歌曲 */
void loadfile()
{
int fd;
char optName[OPTSIZE] = {0};
fd = open(FIFOPATH, O_WRONLY);
if (fd < 0)
{
perror("open()");
exit(1);
}
sprintf(optName, "%s %s\n", "loadfile", musicBuff);
write(fd, optName, strlen(optName));
close(fd);
}
/* 歌曲暂停 */
void songPause()
{
int fd;
fd = open(FIFOPATH, O_WRONLY);
if (fd < 0)
{
perror("open()");
exit(1);
}
write(fd, "pause\n", strlen("pause\n"));
close(fd);
}
/* 歌曲退出 */
void songStop()
{
int fd;
fd = open(FIFOPATH, O_WRONLY);
if (fd < 0)
{
perror("open()");
exit(1);
}
write(fd, "quit\n", strlen("quit\n"));
close(fd);
}
/* 运行函数
* 关闭系统回显和getchar()以\n结尾的设置
*
* flag播放状态 0:未播放 1:正在播放 2:暂停
* tmp检测第一次是否创建了子进程,防止意外杀死主进程 0:未创建子进程 1:已创建子进程
*
*/
void operationFunction()
{
struct termios new,old;
struct sigaction sa;
char ch;
tcgetattr(0,&old);
tcgetattr(0,&new);
new.c_lflag = new.c_lflag & ~(ICANON | ECHO);
new.c_cc[VMIN] = 1;
new.c_cc[VTIME] = 0;
tcsetattr(0,TCSANOW,&new);
printf("\033[?25l"); //隐藏光标
interface();
unlink(FIFOPATH);
if (mkfifo(FIFOPATH, 0666) < 0)
{
perror("mkfifo()");
exit(1);
}
sa.sa_handler = actionHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGCHLD, &sa, NULL);
head = loading();
cur = head->head.next;
showList(cur,0);
while(1)
{
ch = getchar();
switch (ch)
{
/* 播放 */
case 'b':
if (tmp == 0)
{
if (cur == NULL)
cur = cur->next;
strncpy(musicBuff,getNameFromLink(cur->data),MUSICSIZE);
showList(cur,0);
fflush(stdout);
pid = fork();
tmp = 1;
createProgress(pid,musicBuff);
}
break;
/* 上一页 */
case 'w':
interface();
showList(cur,LAST);
break;
/* 下一页 */
case 's':
interface();
showList(cur,NEXT);
break;
/* 上一首 */
case 'a':
cur = cur->prev;
if (cur == &head->head)
cur = cur->prev;
strncpy(musicBuff,getNameFromLink(cur->data),MUSICSIZE);
showList(cur,0);
if (tmp == 1)
loadfile();
break;
/* 下一首 */
case 'd':
cur = cur->next;
if (cur == &head->head)
cur = cur->next;
strncpy(musicBuff,getNameFromLink(cur->data),MUSICSIZE);
showList(cur, 0);
if (tmp == 1)
loadfile();
break;
/* 暂停 | 恢复播放 */
case 'p':
if (tmp == 1)
songPause();
break;
case 't':
playWay++;
if (playWay > 3)
playWay = 1;
showPatten();
break;
default:
break;
}
/* 退出 */
if (ch == 'q')
{
if (tmp)
{
songStop();
kill(pid, SIGKILL);
}
break;
}
}
tcsetattr(0,TCSANOW,&old); //还原终端原始设置
printf("\033[?25h\033[2J");
}