From cc2c7993abcc700c2ff5f4e393f113f03cd42c1b Mon Sep 17 00:00:00 2001 From: Elijah Date: Mon, 8 Aug 2022 00:42:22 +0000 Subject: [PATCH] Initial commit. --- .gitignore | 5 +++ Makefile | 47 ++++++++++++++++++++ README.md | 74 +++++++++++++++++++++++++++++++ commands.h | 10 +++++ config.h | 16 +++++++ config.mk | 19 ++++++++ dirname.c | 31 +++++++++++++ dirname.h | 8 ++++ list.c | 30 +++++++++++++ list.h | 15 +++++++ log.h | 43 ++++++++++++++++++ loglevels.h | 14 ++++++ pipe.c | 17 +++++++ pipe.h | 8 ++++ pipeplayer.1 | 52 ++++++++++++++++++++++ pipeplayer.c | 60 +++++++++++++++++++++++++ player.c | 76 +++++++++++++++++++++++++++++++ player.h | 30 +++++++++++++ playlist.c | 93 ++++++++++++++++++++++++++++++++++++++ playlist.h | 16 +++++++ threads.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++ threads.h | 20 +++++++++ 22 files changed, 807 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 commands.h create mode 100644 config.h create mode 100644 config.mk create mode 100644 dirname.c create mode 100644 dirname.h create mode 100644 list.c create mode 100644 list.h create mode 100644 log.h create mode 100644 loglevels.h create mode 100644 pipe.c create mode 100644 pipe.h create mode 100644 pipeplayer.1 create mode 100644 pipeplayer.c create mode 100644 player.c create mode 100644 player.h create mode 100644 playlist.c create mode 100644 playlist.h create mode 100644 threads.c create mode 100644 threads.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..050f175 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Object files +*.o + +# Binary file +pipeplayer diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..20beec8 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +# pipeplayer - simple music player + +.POSIX: + +include config.mk + +SRC = pipeplayer.c dirname.c player.c playlist.c list.c threads.c pipe.c +OBJ = $(SRC:.c=.o) + +all: options $(NAME) + +options: + @echo $(NAME) build options: + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + @echo "CC = $(CC)" + +.c.o: + $(CC) $(CFLAGS) -c $< + +log.h: config.h loglevels.h +threads.o: commands.h playlist.h player.h log.h threads.h pipe.h +playlist.o: playlist.h dirname.h list.h log.h +player.o: player.h config.h log.h +pipeplayer.o: player.h playlist.h threads.h pipe.h +pipe.o: pipe.h +list.o: list.h + +$(OBJ): config.mk + +$(NAME): $(OBJ) + $(CC) -o $@ $(LDFLAGS) $(OBJ) + +clean: + rm -f $(NAME) $(OBJ) + +install: $(NAME) + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f $(NAME) $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/$(NAME) + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < $(NAME).1 > $(DESTDIR)$(MANPREFIX)/man1/$(NAME).1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/$(NAME).1 + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/$(NAME) + rm -f $(DESTDIR)$(MANPREFIX)/man1/$(NAME).1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff91d04 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# Pipeplayer + +Pipeplayer is a simple music daemon. It uses libmpg123 to decode files and libao to play sound. You can control it with named pipe (FIFO). + +## Before using this + +This program requires *playlist* file which is plain text of songs in the same directory. +For example, you have folder with this files: `song1.mp3`, `song_2.mp3`, `4.mp3` and `56.mp3`. +Your playlist file shoul'd look like: +``` +song1.mp3 +song_2.mp3 +4.mp3 +56.mp3 +``` +**Tip**: cd into folder with music and run command `ls -1N | tee playlist` to automatically generate playlist file. Note that order may differ. + +## Usage + +First of all, you need to create named pipe (FIFO). It can be done by running following command: +``` +mkfifo ~/.cache/.pipeplayer +``` + +Then, you can start daemon either during start of your desktop environment or window manager or even in tty with this command: +``` +pipeplayer ~/.cache/.pipeplayer +``` +Note: add `&` at the end of command to run process in background. + +Now you can send commands to created named pipe like this: +``` +echo l$HOME/Music/playlist > ~/.cache/.pipeplayer +``` + +You can find all commands in man page, after installation you can run: `man pipeplayer` + +# Installation + +You need a few things: +* git +* C compiler, such as **gcc**, **clang** or **tcc** +* make +* libmpg123 +* libao + +Install that stuff, clone this repository, cd into it and simply run make and sudo (or doas) make install: + +``` +git clone https://git.disroot.org/elijah.dev/pipeplayer.git +cd pipeplayer +make +sudo make install +``` +If you prefer gcc, then run `make CC=gcc` instead of `make`. +Also, if you prefer clang, run `make CC=clang STRIPFLAG=` + +Here some tips for installing dependencies on some distros: + +### Archlinux and arch-based distros +``` +pacman -Syu --needed tcc mpg123 libao +``` + +Guides for other distibutions will appear in future. If you know one, feel free to add. + +## TODO + +- [ ] Add pause command +- [ ] Add navigation inside song + +## Bugs + +If you have found bugs, you can open issue or contact me via email elijah.dev@disroot.org diff --git a/commands.h b/commands.h new file mode 100644 index 0000000..76f5246 --- /dev/null +++ b/commands.h @@ -0,0 +1,10 @@ +#ifndef __COMMANDS_H__ +#define __COMMANDS_H__ + +#define EXIT 'e' +#define LOAD 'l' +#define FORWARD 'f' +#define BACKWARD 'b' +#define NEWLINE '\n' + +#endif /* __COMMANDS_H__ */ diff --git a/config.h b/config.h new file mode 100644 index 0000000..bfdd408 --- /dev/null +++ b/config.h @@ -0,0 +1,16 @@ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#define BITS 8 + +// Logging options +#include "loglevels.h" + +/* 0 - off colors + * 1 - on colors */ +#define LOGGING_COLORS 1 + +// Levels (see log.h) +#define LOGGING_LEVEL INFO + +#endif /* __CONFIG_H__ */ diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..7f60810 --- /dev/null +++ b/config.mk @@ -0,0 +1,19 @@ +# Customize below to fit your system +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +# Name of program +VERSION = 1.0.0 +NAME = pipeplayer + +# Compiler +CC = tcc + +PKG_CONFIG = pkg-config + +LIBS = -lpthread `$(PKG_CONFIG) -libs libmpg123 ao` + +# Flags +STRIPFLAG = -s +CFLAGS = -O2 -Wall +LDFLAGS = -O2 $(STRIPFLAG) $(LIBS) diff --git a/dirname.c b/dirname.c new file mode 100644 index 0000000..38238ea --- /dev/null +++ b/dirname.c @@ -0,0 +1,31 @@ +/* Put dirname with slash in buffer + * Return length of buffer */ + +#include +#include +#include + +size_t +dirname(const char *path, char **buffer_ptr) +{ + size_t index = 0, slash_index = 0; + + // Find slash + for (; path[index] != '\0'; index++) + if (path[index] == '/') + slash_index = index; + + // If not slashes or one at the beginning, then / is dirname + if (slash_index == 0) { + *buffer_ptr = (char *) malloc(2); + strcpy(*buffer_ptr, "/"); + return (size_t)1; + } + + // Copy string + slash_index++; + *buffer_ptr = (char *) malloc(slash_index + 1); + strncpy(*buffer_ptr, path, slash_index); + (*buffer_ptr)[slash_index] = '\0'; + return slash_index; +} diff --git a/dirname.h b/dirname.h new file mode 100644 index 0000000..b6a7120 --- /dev/null +++ b/dirname.h @@ -0,0 +1,8 @@ +#ifndef __DIRNAME_H__ +#define __DIRNAME_H__ + +/* Get directory from path + * Return length, store in buffer */ +size_t dirname(const char *path, char **buffer); + +#endif /* __DIRNAME_H__ */ diff --git a/list.c b/list.c new file mode 100644 index 0000000..63daa5b --- /dev/null +++ b/list.c @@ -0,0 +1,30 @@ +#include +#include "list.h" + +node_t * +new_node(node_t *previous) +{ + node_t *node = (node_t *) malloc(sizeof(node_t)); + node->next = NULL; + if (previous != NULL) + previous->next = node; + return node; +} + +void +free_list(node_t *root) +{ + while (root != NULL) { + node_t *previous = root; + root = root->next; + free(previous); + } +} + +void +node_pop_first(node_t **root) +{ + node_t *current = *root; + *root = current->next; + free(current); +} diff --git a/list.h b/list.h new file mode 100644 index 0000000..b5f6a2b --- /dev/null +++ b/list.h @@ -0,0 +1,15 @@ +#ifndef __LIST_H__ +#define __LIST_H__ + +// Linked list implementation + +typedef struct node { + char *str; + struct node *next; +} node_t; + +node_t *new_node(node_t *previous); +void free_list(node_t *root); +void node_pop_first(node_t **root); + +#endif /* __LIST_H__ */ diff --git a/log.h b/log.h new file mode 100644 index 0000000..78e5662 --- /dev/null +++ b/log.h @@ -0,0 +1,43 @@ +#ifndef __LOG_H__ +#define __LOG_H__ + +#include "loglevels.h" +#include "config.h" + +// Default logging level is error +#ifndef LOGGING_LEVEL +#define LOGGING_LEVEL ERROR +#endif /* Logging level */ + +// Colors +#if LOGGING_COLORS == 1 +#define __LOGGING_ERROR_MESSAGE "[ERROR](B " +#define __LOGGING_INFO_MESSAGE "[INFO](B " +#define __LOGGING_DEBUG_MESSAGE "[DEBUG](B " +#else +#define __LOGGING_ERROR_MESSAGE "[ERROR] " +#define __LOGGING_INFO_MESSAGE "[INFO] " +#define __LOGGING_DEBUG_MESSAGE "[DEBUG] " +#endif /* Colors */ + +// Logging macro and include of stdio +#if LOGGING_LEVEL >= ERROR +#include +#define LOG_ERROR(...) printf(__LOGGING_ERROR_MESSAGE); printf(__VA_ARGS__); puts(""); +#else +#define LOG_ERROR(...) +#endif + +#if LOGGING_LEVEL >= INFO +#define LOG_INFO(...) printf(__LOGGING_INFO_MESSAGE); printf(__VA_ARGS__); puts(""); +#else +#define LOG_INFO(...) +#endif + +#if LOGGING_LEVEL >= DEBUG +#define LOG_DEBUG(...) printf(__LOGGING_DEBUG_MESSAGE); printf(__VA_ARGS__); puts(""); +#else +#define LOG_DEBUG(...) +#endif + +#endif /* __LOG_H__ */ diff --git a/loglevels.h b/loglevels.h new file mode 100644 index 0000000..b71d639 --- /dev/null +++ b/loglevels.h @@ -0,0 +1,14 @@ +#ifndef __LOG_LEVELS_H__ +#define __LOG_LEVELS_H__ + +/* Logging levels + * NOLOG means no log messages at all + * ERROR is error messages + * INFO provides error and info messages + * DEBUG provides all log messages */ +#define NOLOG 0 +#define ERROR 1 +#define INFO 2 +#define DEBUG 3 + +#endif /* __LOG_LEVELS_H__ */ diff --git a/pipe.c b/pipe.c new file mode 100644 index 0000000..652a251 --- /dev/null +++ b/pipe.c @@ -0,0 +1,17 @@ +#include +#include + +#include "pipe.h" + +size_t +pipe_readline(char **buffer_ptr, char *pipe_path) +{ + size_t len; + char *line = NULL; + FILE *fd = fopen(pipe_path, "r"); + size_t read = getline(&line, &len, fd); + line[--read] = '\0'; + fclose(fd); + *buffer_ptr = line; + return read; +} diff --git a/pipe.h b/pipe.h new file mode 100644 index 0000000..6a86766 --- /dev/null +++ b/pipe.h @@ -0,0 +1,8 @@ +#ifndef __PIPE_H__ +#define __PIPE_H__ + +#include + +size_t pipe_readline(char **buffer_ptr, char *pipe_path); + +#endif /* __PIPE_H__ */ diff --git a/pipeplayer.1 b/pipeplayer.1 new file mode 100644 index 0000000..76b937c --- /dev/null +++ b/pipeplayer.1 @@ -0,0 +1,52 @@ +.TH PIPEPLAYER 1 pipeplayer\-VERSION +.SH NAME +pipeplayer \- simple music daemon +.SH SYNOPSIS +.B pipeplayer +/path/to/pipe +.SH DESCRIPTION +.B pipeplayer +is simple music daemon controlled with named pipe. It uses libmpg123 to decode files and libao to play sound. +.SH CONTROL +.TP +.B l/path +load playlist from /path. +.TP +.B e +stop pipeplayer. +.TP +.B b +play previous song. +.TP +.B f +play next song. +.SH LOGGING +You can change LOGGING_LEVEL at +.B config.h +to one of the following values: +.TP +.B NOLOG +No logs. +.TP +.B ERROR +Show only error messages. +.TP +.B INFO +Show some information messages in addition to error messages. +.TP +.B DEBUG +Show all log messages. +.TP +Then you need to recompile pipeplayer. +.SH AUTHORS +Written by Elijah +.TP +.B Git repository: +https://git.disroot.org/elijah.dev/pipeplayer.git +.SH LICENSE +See the LICENSE file for the terms of redistribution. +.SH SEE ALSO +.BR mpg123 (1), +.BR libao.conf (5) +.SH BUGS +If you have found bugs, you can open issue on git page or contact me via email. diff --git a/pipeplayer.c b/pipeplayer.c new file mode 100644 index 0000000..c5965bc --- /dev/null +++ b/pipeplayer.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#include "player.h" +#include "playlist.h" +#include "threads.h" +#include "pipe.h" + +// Function on Ctrl-C +void +sigint_handler(int _) +{ + puts("Use 'e' command to exit"); +} + +// Entry point +int +main(int argc, char *argv[]) +{ + // Usage information + if (argc != 2) { + printf("Usage: %s [FIFO]\n", argv[0]); + return 1; + } + + if (access(argv[1], F_OK) != 0) { + printf("Cannot access file %s\n", argv[1]); + return 1; + } + + // mpg player + struct mpg_player player, *player_ptr = &player; + player.ao.need_close_device = false; + + // playlist + struct playlist playlist = { + .need_free = false, + .need_cancel = false, + .index = 0, + .length = 0, + .path = NULL, + }, *playlist_ptr = &playlist; + + // Set up Ctrl-C handler + signal(SIGINT, sigint_handler); + + // Initialize player + init_player(player_ptr); + + // Create and join input thread + run_threads(player_ptr, playlist_ptr, argv[1]); + + // Clear player and playlist + free_player(player_ptr); + free_playlist(playlist_ptr); + + return 0; +} diff --git a/player.c b/player.c new file mode 100644 index 0000000..c319f27 --- /dev/null +++ b/player.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +#include "player.h" +#include "config.h" +#include "log.h" + +void +init_player(struct mpg_player *player) +{ + LOG_DEBUG("Initializing player"); + + // Audio output + ao_initialize(); + player->ao.driver = ao_default_driver_id(); + // Mpg123 + mpg123_init(); + player->handler = mpg123_new(NULL, &player->error); + player->buffer_size = mpg123_outblock(player->handler); + player->buffer = (char*) malloc(player->buffer_size * sizeof(unsigned char)); +} + +void +free_player(struct mpg_player *player) +{ + LOG_DEBUG("Clearing memory of player"); + + free(player->buffer); + + if (player->ao.need_close_device) + ao_close(player->ao.device); + + mpg123_close(player->handler); + mpg123_delete(player->handler); + mpg123_exit(); + + ao_shutdown(); +} + +void +play(struct mpg_player *player, const char *path) +{ + /* Open the file and get the decoding format */ + LOG_INFO("Playing %s", path); + + struct audio_output *ao = &player->ao; + ao_sample_format *format = &ao->format; + + mpg123_open(player->handler, path); + mpg123_getformat( + player->handler, + &ao->rate, + &ao->channels, + &ao->encoding + ); + + /* Set the output format and open the output device */ + format->bits = mpg123_encsize(ao->encoding) * BITS; + format->rate = ao->rate; + format->channels = ao->channels; + format->byte_format = AO_FMT_NATIVE; + format->matrix = 0; + ao->device = ao_open_live(ao->driver, format, NULL); + ao->need_close_device = true; + + /* Decode and play */ + while (mpg123_read( + player->handler, + player->buffer, + player->buffer_size, + &player->done + ) == MPG123_OK) + ao_play(ao->device, player->buffer, player->done); +} diff --git a/player.h b/player.h new file mode 100644 index 0000000..999c0cd --- /dev/null +++ b/player.h @@ -0,0 +1,30 @@ +#ifndef __PLAYER_H__ +#define __PLAYER_H__ + +#include +#include +#include +#include + +struct audio_output { + ao_device *device; + bool need_close_device; + ao_sample_format format; + int channels, encoding; + int driver; + long rate; +}; + +struct mpg_player { + mpg123_handle *handler; + char *buffer; + size_t buffer_size, done; + int error; + struct audio_output ao; +}; + +void init_player(struct mpg_player *); +void free_player(struct mpg_player *); +void play(struct mpg_player *, const char *path); + +#endif /* __PLAYER_H__ */ diff --git a/playlist.c b/playlist.c new file mode 100644 index 0000000..140cf60 --- /dev/null +++ b/playlist.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include + +#include "playlist.h" +#include "dirname.h" +#include "list.h" +#include "log.h" + +void +free_playlist(struct playlist *playlist) +{ + if (!playlist->need_free) + return; + + LOG_DEBUG("Clearing playlist"); + + // Clear array items + for (playlist->index = 0; playlist->index < playlist->length; playlist->index++) + free(playlist->array[playlist->index]); + + // Clear array + free(playlist->array); + + // Clear playlist dir + free(playlist->dir); +} + +void +load_playlist(struct playlist *playlist) +{ + FILE *fp; + ssize_t read; + size_t length, len, count = 0; + char *line = NULL; + + // Clear previous playlist + free_playlist(playlist); + + LOG_INFO("Loading playlist from %s", playlist->path); + + // Read file setup + fp = fopen(playlist->path, "r"); + playlist->index = 0; + + // Return if cannot open file or first line is empty + if (fp == NULL) { + LOG_ERROR("Cannot load playlist from %s", playlist->path); + playlist->need_free = false; + playlist->length = 0; + return; + } + + // Get dirname and length of it + length = dirname(playlist->path, &playlist->dir); + + node_t *list = new_node(NULL), + *current = list; + + // Read lines + while ((read = getline(&line, &len, fp)) != EOF) { + // "read" is length of string + current = new_node(current); + line[read - 1] = '\0'; + current->str = (char *) malloc(read + length); + strncpy(current->str, playlist->dir, length); + strncpy(current->str + length, line, read); + count++; + } + + // Set playlist values + playlist->need_free = true; + playlist->length = count; + + // Allocate array + playlist->array = (char **) malloc(count * sizeof(char *)); + + // Copy strings (pointers) from list to array + current = list; + for (size_t index = 0; index < count; index++) { + current = current->next; + playlist->array[index] = current->str; + } + + // Clear list + free_list(list); + + // Close file and free line + fclose(fp); + if (line) + free(line); +} diff --git a/playlist.h b/playlist.h new file mode 100644 index 0000000..81e5c21 --- /dev/null +++ b/playlist.h @@ -0,0 +1,16 @@ +#ifndef __PLAYLIST_H__ +#define __PLAYLIST_H__ + +#include +#include + +struct playlist { + char *path, *dir, **array; + size_t index, length; + bool need_free, need_cancel; +}; + +void free_playlist(struct playlist *); +void load_playlist(struct playlist *); + +#endif /* __PLAYLIST_H__ */ diff --git a/threads.c b/threads.c new file mode 100644 index 0000000..ddf1092 --- /dev/null +++ b/threads.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include + +#include "commands.h" +#include "playlist.h" +#include "player.h" +#include "log.h" +#include "threads.h" +#include "pipe.h" + +void +run_threads(struct mpg_player *player, struct playlist *playlist, char *pipe_path) +{ + pthread_t thread_player = {0}, thread_handler = {0}; + + struct thread_arg arg = { + .player = player, + .playlist = playlist, + .pipe_path = pipe_path, + .thread_player = &thread_player + }; + + pthread_create(&thread_handler, NULL, input_thread, (void *)&arg); + pthread_join(thread_handler, NULL); +} + +void * +play_thread(void *vargp) +{ + struct thread_arg *arg = (struct thread_arg *)vargp; + struct playlist *playlist = arg->playlist; + struct mpg_player *player = arg->player; + + LOG_DEBUG("Starting player thread"); + LOG_DEBUG("playlist->path = %s", playlist->path); + LOG_DEBUG("playlist->index = %lu", playlist->index); + LOG_DEBUG("playlist->length = %lu", playlist->length); + + playlist->need_cancel = true; + + for (; playlist->index < playlist->length; playlist->index++) + play(player, playlist->array[playlist->index]); + + playlist->need_cancel = false; + + return 0; +} + +void +_restart_playlist_thread(struct playlist *playlist, void *vargp) +{ + struct thread_arg *arg = (struct thread_arg *)vargp; + pthread_t *thread_player = arg->thread_player; + + LOG_DEBUG("Killing player thread"); + + if (playlist->need_cancel) + pthread_cancel(*thread_player); + pthread_create(thread_player, NULL, play_thread, vargp); +} + +void * +input_thread(void *vargp) +{ + size_t length; + char *command, **command_ptr = (char **) malloc(sizeof(char *)); + bool run = true; + + struct thread_arg *arg = (struct thread_arg *)vargp; + struct playlist *playlist = arg->playlist; + char *pipe_path = arg->pipe_path; + + while (run) { + length = pipe_readline(command_ptr, pipe_path); + command = *command_ptr; + LOG_DEBUG("Command: %s", command); + + switch (*command) { + case EXIT: + case EOF: + LOG_DEBUG("Case: EXIT | EOF"); + if (playlist->need_cancel) + pthread_cancel(*arg->thread_player); + run = false; + break; + + case LOAD: + LOG_DEBUG("Case: LOAD"); + playlist->path = malloc(length); + strcpy(playlist->path, command + 1); + load_playlist(playlist); + _restart_playlist_thread(playlist, vargp); + break; + + case FORWARD: + LOG_DEBUG("Case: FORWARD"); + playlist->index = (playlist->index + 1) % playlist->length; + _restart_playlist_thread(playlist, vargp); + break; + + case BACKWARD: + LOG_DEBUG("Case: BACKWARD"); + playlist->index = (playlist->index + playlist->length - 1) % playlist->length; + _restart_playlist_thread(playlist, vargp); + break; + + default: + fprintf(stderr, "Unknown command: %s\n", command); + } + + // Clear command + if (command != NULL) + free(command); + } + + // Clear command pointer + free(command_ptr); + + return 0; +} diff --git a/threads.h b/threads.h new file mode 100644 index 0000000..45ac6e0 --- /dev/null +++ b/threads.h @@ -0,0 +1,20 @@ +#ifndef __THREADS_H__ +#define __THREADS_H__ + +#include +#include "player.h" +#include "playlist.h" + +struct thread_arg { + struct mpg_player *player; + struct playlist *playlist; + pthread_t *thread_player; + char *pipe_path; +}; + +// Thread function +void run_threads(struct mpg_player *player, struct playlist *playlist, char *pipe_path); +void *play_thread(void *vargp); +void *input_thread(void *vargp); + +#endif /* __THREADS_H__ */