Initial commit.

This commit is contained in:
Elijah 2022-08-08 00:42:22 +00:00
commit cc2c7993ab
22 changed files with 807 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# Object files
*.o
# Binary file
pipeplayer

47
Makefile Normal file
View file

@ -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

74
README.md Normal file
View file

@ -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

10
commands.h Normal file
View file

@ -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__ */

16
config.h Normal file
View file

@ -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__ */

19
config.mk Normal file
View file

@ -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)

31
dirname.c Normal file
View file

@ -0,0 +1,31 @@
/* Put dirname with slash in buffer
* Return length of buffer */
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
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;
}

8
dirname.h Normal file
View file

@ -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__ */

30
list.c Normal file
View file

@ -0,0 +1,30 @@
#include <stdlib.h>
#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);
}

15
list.h Normal file
View file

@ -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__ */

43
log.h Normal file
View file

@ -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 <stdio.h>
#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__ */

14
loglevels.h Normal file
View file

@ -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__ */

17
pipe.c Normal file
View file

@ -0,0 +1,17 @@
#include <stdio.h>
#include <stdlib.h>
#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;
}

8
pipe.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef __PIPE_H__
#define __PIPE_H__
#include <stddef.h>
size_t pipe_readline(char **buffer_ptr, char *pipe_path);
#endif /* __PIPE_H__ */

52
pipeplayer.1 Normal file
View file

@ -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 <elijah.dev@disroot.org>
.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.

60
pipeplayer.c Normal file
View file

@ -0,0 +1,60 @@
#include <stdbool.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#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;
}

76
player.c Normal file
View file

@ -0,0 +1,76 @@
#include <mpg123.h>
#include <ao/ao.h>
#include <stdbool.h>
#include <stdlib.h>
#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);
}

30
player.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef __PLAYER_H__
#define __PLAYER_H__
#include <stdbool.h>
#include <stddef.h>
#include <mpg123.h>
#include <ao/ao.h>
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__ */

93
playlist.c Normal file
View file

@ -0,0 +1,93 @@
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#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);
}

16
playlist.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef __PLAYLIST_H__
#define __PLAYLIST_H__
#include <stdbool.h>
#include <stddef.h>
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__ */

123
threads.c Normal file
View file

@ -0,0 +1,123 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#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;
}

20
threads.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef __THREADS_H__
#define __THREADS_H__
#include <pthread.h>
#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__ */