tmus

TUI Music Player
git clone https://git.sinitax.com/sinitax/tmus
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

commit 1bd07952245e3fc8ed95af0c1eff45938098b40b
parent 9fe644f0d99375ffd3011d8828f7dbd0fb103af0
Author: Louis Burda <quent.burda@gmail.com>
Date:   Sun, 12 Dec 2021 23:06:54 +0100

Added playback via libmpdclient

Diffstat:
M.gitignore | 2+-
MMakefile | 16+++++++++++-----
AREADME | 9+++++++++
Mhistory.c | 7+++++--
Mmain.c | 120+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mplayer.c | 219+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mplayer.h | 64++++++++++++++++++++++++++++++++++++----------------------------
Atmus | 0
Mutil.c | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mutil.h | 5++++-
10 files changed, 361 insertions(+), 158 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,4 +1,4 @@ vgcore* -main +tmus env.sh *.o diff --git a/Makefile b/Makefile @@ -1,15 +1,21 @@ CFLAGS = -I . -g -LDLIBS = -lcurses -lreadline -lportaudio -lsndfile +LDLIBS = -lcurses -lreadline -lmpdclient -.PHONY: all main +.PHONY: all tmus clean install uninstall -all: main +all: tmus clean: - rm main + rm tmus %.o: %.c %.h $(CC) -c -o $@ $< $(CFLAGS) $(LDLIBS) -main: main.c util.o history.o link.o player.o tag.o track.o +tmus: main.c util.o history.o link.o player.o tag.o track.o $(CC) -o $@ $^ $(CFLAGS) $(LDLIBS) + +install: + install -m 755 tmus /usr/bin + +uninstall: + rm /usr/bin/tmus diff --git a/README b/README @@ -0,0 +1,9 @@ +tmus +==== + +mpd backend +----------- + +To load songs by path, mpd requires the client to connect via +a unix socket. Set the environment variable MPD_HOST accordingly. + diff --git a/history.c b/history.c @@ -39,12 +39,15 @@ history_free(struct history *history) struct link *iter, *next; struct inputln *ln; - for (iter = &history->list; iter; iter = next) { + for (iter = history->list.next; iter; iter = next) { next = iter->next; ln = UPCAST(iter, struct inputln); free(ln); } - free(history->cmd); + history->list = LIST_HEAD; + free(history->query); + history->query = NULL; + history->cmd = NULL; } struct inputln * diff --git a/main.c b/main.c @@ -8,8 +8,7 @@ #include "track.h" #include "player.h" -#include "sndfile.h" -#include "portaudio.h" +#include "mpd/player.h" #include "curses.h" #include <dirent.h> @@ -71,7 +70,6 @@ struct cmd { cmd_handler func; }; - int style_attrs[STYLE_COUNT] = { 0 }; const char *datadir; @@ -89,10 +87,8 @@ struct pane *const panes[] = { struct link tags; -struct track *track_sel; struct link tracks; struct link playlist; -int track_paused; completion_generator completion; struct history search_history, command_history; @@ -104,7 +100,6 @@ struct link tags_sel; int track_index; - void init(void); void cleanup(void); @@ -142,12 +137,10 @@ void main_vis(void); int usercmd_save(const char *args); - const struct cmd cmds[] = { { "save", usercmd_save }, }; - void init(void) { @@ -155,7 +148,7 @@ init(void) win_ratio = 0.3f; initscr(); - cbreak(); + raw(); noecho(); halfdelay(1); intrflush(stdscr, FALSE); @@ -188,26 +181,21 @@ init(void) tag_index = 0; tags_sel = LIST_HEAD; - // player = player_thread(); + player_init(); data_load(); + signal(SIGINT, exit); + atexit(cleanup); } void cleanup(void) { - int status; - - // TODO stop player - data_save(); - kill(player->pid, SIGTERM); - waitpid(player->pid, &status, 0); - - // TODO free player + player_free(); history_free(&search_history); history_free(&command_history); @@ -292,7 +280,7 @@ data_save(void) void tracks_save(struct tag *tag) { - + } void @@ -488,23 +476,14 @@ track_input(wint_t c) track_index = MAX(0, track_index - 1); return 1; case KEY_DOWN: - track_index = MIN(list_len(&tracks) - 1, - track_index + 1); + track_index = MIN(list_len(&tracks) - 1, track_index + 1); return 1; case KEY_ENTER: link = link_iter(tracks.next, track_index); ASSERT(link != NULL); track = UPCAST(link, struct track); - if (track != track_sel) { - track_sel = track; - track_paused = 0; - } else { - track_paused ^= 1; - } - // if (!track_paused) - // track_play(); - // else - // track_pause(); + player->track = track; + player_play_track(track); return 1; } @@ -525,21 +504,21 @@ track_vis(struct pane *pane, int sel) for (iter = tracks.next; iter; iter = iter->next) { track = UPCAST(iter, struct track); - if (sel && index == track_index && track == track_sel) + if (sel && index == track_index && track == player->track) style_on(pane->win, STYLE_ITEM_HOVER_SEL); else if (sel && index == track_index) style_on(pane->win, STYLE_ITEM_HOVER); - else if (track == track_sel) + else if (track == player->track) style_on(pane->win, STYLE_ITEM_SEL); wmove(pane->win, 1 + index, 0); wprintw(pane->win, "%-*.*s", pane->w, pane->w, track->name); - if (sel && index == track_index && track == track_sel) + if (sel && index == track_index && track == player->track) style_off(pane->win, STYLE_ITEM_HOVER_SEL); else if (sel && index == track_index) style_off(pane->win, STYLE_ITEM_HOVER); - else if (track == track_sel) + else if (track == player->track) style_off(pane->win, STYLE_ITEM_SEL); index++; @@ -559,9 +538,11 @@ cmd_input(wint_t c) switch (c) { case KEY_ESC: - if (history->cmd == history->query) - return 0; - history->cmd = history->query; + if (history->cmd == history->query) { + pane_sel = NULL; /* TODO: save last and switch back */ + } else { + history->cmd = history->query; + } break; case KEY_LEFT: inputln_left(history->cmd); @@ -601,25 +582,43 @@ void cmd_vis(struct pane *pane, int sel) { struct inputln *cmd; + char state_char; + char *line; werase(pane->win); - if (track_sel) - pane_title(pane, track_sel->name, sel); - else - pane_title(pane, "", sel); + wmove(pane->win, 0, 0); + style_on(pane->win, STYLE_TITLE); + wprintw(pane->win, " %-*.*s\n", pane->w - 1, pane->w - 1, + player->track ? player->track->name : ""); + style_off(pane->win, STYLE_TITLE); + + if (player->time_pos) { + state_char = player->state == PLAYER_STATE_PLAYING ? '>' : '|'; + line = appendstrf(NULL, "%c ", state_char); + line = appendstrf(line, "%s / ", timestr(player->time_pos)); + line = appendstrf(line, "%s", timestr(player->time_end)); + if (player->volume >= 0) { + line = appendstrf(line, " - vol: %u%", player->volume); + } + if (player->msg) { + line = appendstrf(line, " | [PLAYER] %s", player->msg); + } - static int i = 0; - wmove(pane->win, 1, 0); - wprintw(pane->win, "%i", i++); + wmove(pane->win, 1, 0); + ATTR_ON(pane->win, A_REVERSE); + wprintw(pane->win, "%-*.*s\n", pane->w, pane->w, line); + ATTR_OFF(pane->win, A_REVERSE); + + free(line); + } if (sel || cmd_show) { wmove(pane->win, 2, 0); waddch(pane->win, cmd_mode == IMODE_SEARCH ? '/' : ':'); cmd = history->cmd; wprintw(pane->win, "%-*.*ls", pane->w - 1, pane->w - 1, cmd->buf); - // TODO: if query != cmd, highlight query substr - if (sel) { + if (sel) { /* cursor */ ATTR_ON(pane->win, A_REVERSE); wmove(pane->win, 2, 1 + cmd->cur); waddch(pane->win, cmd->cur < cmd->len ? cmd->buf[cmd->cur] : ' '); @@ -647,14 +646,37 @@ main_input(wint_t c) case KEY_RIGHT: pane_sel = &pane_right; break; + case 't': + player_toggle_pause(); + break; + case 'n': + case '>': + player_next(); + break; + case 'p': + case '<': + player_prev(); + break; + case 'b': + player_seek(0); + break; case ':': cmd_mode = IMODE_EXECUTE; pane_sel = &pane_bot; break; + case '+': + player_set_volume(MIN(100, player->volume + 5)); + break; + case '-': + player_set_volume(MAX(0, player->volume - 5)); + break; case '/': cmd_mode = IMODE_SEARCH; pane_sel = &pane_bot; break; + case 'q': + quit = 1; + break; } } @@ -693,6 +715,8 @@ main(int argc, const char **argv) c = KEY_RESIZE; do { + player_update(); + if (c == KEY_RESIZE) { resize(); } else if (c != ERR) { @@ -714,7 +738,5 @@ main(int argc, const char **argv) get_wch(&c); } while (!quit); - - cleanup(); } diff --git a/player.c b/player.c @@ -2,112 +2,221 @@ #include "portaudio.h" #include "sndfile.h" +#include "util.h" +#include <mpd/song.h> +#include <mpd/status.h> +#include <stdbool.h> #include <stdlib.h> +#include <string.h> #include <sys/mman.h> #include <unistd.h> +#define PLAYER_STATUS(lvl, ...) do { \ + player->msglvl = lvl; \ + if (player->msg) free(player->msg); \ + player->msg = aprintf(__VA_ARGS__); \ + } while (0) + static struct player player_static; struct player *player; -struct player * -player_thread() +void +player_init(void) { - struct player *player; - pid_t pid; - - player = mmap(&player_static, sizeof(struct player), - PROT_READ | PROT_WRITE, MAP_SHARED, -1, 0); - player->action = PLAYER_NONE; - player->resp = PLAYER_NOTSET; - player->alive = 1; - - pid = fork(); - if (!pid) { - player_main(); - player->alive = 0; - exit(0); - } + player = malloc(sizeof(struct player)); + ASSERT(player != NULL); + + player->conn = mpd_connection_new(NULL, 0, 0); + ASSERT(player->conn != NULL); + + player->queue = LIST_HEAD; + player->track = NULL; + player->state = PLAYER_STATE_PAUSED; + + player->volume = 0; + player->time_pos = 0; + player->time_end = 0; + + player->msg = NULL; + player->msglvl = PLAYER_MSG_INFO; - player->pid = pid; + mpd_run_stop(player->conn); + mpd_run_clear(player->conn); +} - return player; +void +player_free(void) +{ + if (!player->conn) return; + mpd_run_stop(player->conn); + mpd_run_clear(player->conn); + mpd_connection_free(player->conn); } void -player_main(void) +player_update(void) { - PaStream *stream; - int status; - - if (Pa_Initialize() != paNoError) - return; - - while (player->action != PLAYER_EXIT) { - player->resp = PLAYER_NOTSET; - switch (player->action) { - case PLAYER_PLAY: - player->resp = player_play(); - break; - case PLAYER_PAUSE: - player->resp = player_pause(); - break; - case PLAYER_SKIP: - player->resp = player_skip(); - break; - case PLAYER_PREV: - player->resp = player_prev(); - break; - } - Pa_Sleep(100); + struct mpd_status *status; + struct mpd_song *song; + const char *tmp; + + status = mpd_run_status(player->conn); + ASSERT(status != NULL); + + player->state = mpd_status_get_state(status) == MPD_STATE_PLAY + ? PLAYER_STATE_PLAYING : PLAYER_STATE_PAUSED; + player->volume = mpd_status_get_volume(status); + + song = mpd_run_current_song(player->conn); + if (song) { + player->time_pos = mpd_status_get_elapsed_time(status); + player->time_end = mpd_song_get_duration(song); + mpd_song_free(song); + } else { + player->time_pos = 0; + player->time_end = 0; } - Pa_Terminate(); + mpd_status_free(status); } -int -player_alive(void) +void +player_queue_clear(void) { - return player->alive && !kill(player->pid, 0); + struct link *iter, *next;; + + for (iter = &player->queue; iter; ) { + next = iter->next; + free(UPCAST(iter, struct track_ref)); + iter = next; + } + + player->queue = LIST_HEAD; } void -player_loadfile(const char *file) +player_queue_append(struct track *track) { - ASSERT(player_alive()); - player_action(PLAYER_STOP); + player_queue_insert(track, list_len(&player->queue)); } void -player_action(int action) +player_queue_insert(struct track *track, size_t pos) +{ + struct track_ref *new; + struct link *iter; + int i; + + new = malloc(sizeof(struct track_ref)); + new->track = track; + new->link = LINK_EMPTY; + + iter = &player->queue; + for (i = 0; i < pos && iter->next; i++) + iter = iter->next; + + link_append(iter, &new->link); +} + +int +player_play_track(struct track *track) { - ASSERT(player_alive()); - player->action = action; + player_clear_msg(); + player->track = track; + mpd_run_stop(player->conn); + + if (!mpd_run_add(player->conn, player->track->fpath) + || !mpd_run_play(player->conn)) { + PLAYER_STATUS(PLAYER_MSG_ERR, "Playback failed"); + return PLAYER_ERR; + } + + return PLAYER_OK; } int -player_play(void) +player_toggle_pause(void) { + if (!mpd_run_toggle_pause(player->conn)) { + PLAYER_STATUS(PLAYER_MSG_ERR, "Pause toggle failed"); + return PLAYER_ERR; + } + return PLAYER_OK; } int player_pause(void) { + if (!mpd_run_pause(player->conn, true)) { + PLAYER_STATUS(PLAYER_MSG_ERR, "Pausing track failed"); + return PLAYER_ERR; + } + + return PLAYER_OK; +} + +int +player_resume(void) +{ + if (!mpd_run_pause(player->conn, false)) { + PLAYER_STATUS(PLAYER_MSG_ERR, "Resuming track failed"); + return PLAYER_ERR; + } + return PLAYER_OK; } int -player_skip(void) +player_next(void) { + if (!mpd_run_next(player->conn)) { + PLAYER_STATUS(PLAYER_MSG_ERR, "Playing next track failed"); + return PLAYER_ERR; + } + return PLAYER_OK; } int player_prev(void) { + if (!mpd_run_previous(player->conn)) { + PLAYER_STATUS(PLAYER_MSG_ERR, "Playing prev track failed"); + return PLAYER_ERR; + } + return PLAYER_OK; } +int +player_seek(int sec) +{ + /* TODO */ + return PLAYER_OK; +} +int +player_set_volume(unsigned int vol) +{ + if (player->volume == -1) { + PLAYER_STATUS(PLAYER_MSG_INFO, "Setting volume not supported"); + return PLAYER_ERR; + } + + if (!mpd_run_set_volume(player->conn, vol)) { + PLAYER_STATUS(PLAYER_MSG_ERR, "Setting volume failed"); + return PLAYER_ERR; + } + return PLAYER_OK; +} + +void +player_clear_msg(void) +{ + free(player->msg); + player->msg = NULL; + player->msglvl = PLAYER_MSG_NONE; +} diff --git a/player.h b/player.h @@ -1,54 +1,62 @@ #pragma once +#include "track.h" #include "util.h" -#include "sndfile.h" +#include "mpd/client.h" #include <signal.h> enum { - PLAYER_NONE, - PLAYER_PAUSE, - PLAYER_PLAY, - PLAYER_SKIP, - PLAYER_PREV, - PLAYER_STOP, - PLAYER_LOAD, - PLAYER_EXIT + PLAYER_OK, + PLAYER_ERR }; enum { - PLAYER_NOTSET, - PLAYER_OK, - PLAYER_FAIL + PLAYER_MSG_NONE, + PLAYER_MSG_INFO, + PLAYER_MSG_ERR +}; + +enum { + PLAYER_STATE_PAUSED, + PLAYER_STATE_PLAYING }; struct player { - int action, resp; + struct mpd_connection *conn; - int reload; - char *filepath; - SNDFILE *file; - SF_INFO info; + struct link queue; + struct track *track; + int state; - int sample_index; + int volume; + unsigned int time_pos, time_end; - int alive; - pid_t pid; + char *msg; + int msglvl; }; -struct player *player_thread(void); +void player_init(void); +void player_free(void); +void player_update(void); -void player_main(void); +void player_queue_clear(void); +void player_queue_append(struct track *track); +void player_queue_insert(struct track *track, size_t pos); -int player_alive(void); - -void player_loadfile(const char *path); -void player_action(int action); +int player_play_track(struct track *track); +int player_toggle_pause(void); int player_pause(void); -int player_play(void); +int player_resume(void); int player_prev(void); -int player_skip(void); +int player_next(void); +int player_seek(int sec); + +int player_set_volume(unsigned int vol); + +void player_clear_msg(void); extern struct player *player; + diff --git a/tmus b/tmus Binary files differ. diff --git a/util.c b/util.c @@ -47,6 +47,52 @@ assert(int cond, const char *file, int line, const char *condstr) } char * +aprintf(const char *fmtstr, ...) +{ + va_list ap, cpy; + size_t size; + char *str; + + va_copy(cpy, ap); + + va_start(ap, fmtstr); + size = vsnprintf(NULL, 0, fmtstr, ap); + va_end(ap); + + str = malloc(size + 1); + ASSERT(str != NULL); + + va_start(cpy, fmtstr); + vsnprintf(str, size + 1, fmtstr, cpy); + va_end(cpy); + + return str; +} + +char * +appendstrf(char *alloc, const char *fmtstr, ...) +{ + va_list ap, cpy; + size_t size, prevlen; + + va_copy(cpy, ap); + + va_start(ap, fmtstr); + size = vsnprintf(NULL, 0, fmtstr, ap); + va_end(ap); + + prevlen = alloc ? strlen(alloc) : 0; + alloc = realloc(alloc, prevlen + size + 1); + ASSERT(alloc != NULL); + + va_start(cpy, fmtstr); + vsnprintf(alloc + prevlen, size + 1, fmtstr, cpy); + va_end(cpy); + + return alloc; +} + +char * sanitized(const char *instr) { const char *p; @@ -65,25 +111,22 @@ sanitized(const char *instr) return clean; } -char * -aprintf(const char *fmtstr, ...) +const char * +timestr(unsigned int secs) { - va_list ap, cpy; - size_t size; - char *str; + static char buf[16]; + unsigned int mins, hours; - va_copy(cpy, ap); - - va_start(ap, fmtstr); - size = vsnprintf(NULL, 0, fmtstr, ap); - va_end(ap); - - str = malloc(size + 1); - ASSERT(str != NULL); + hours = secs / 3600; + mins = secs / 60 % 60; + secs = secs % 60; - va_start(cpy, fmtstr); - vsnprintf(str, size + 1, fmtstr, cpy); - va_end(cpy); + if (hours) { + snprintf(buf, sizeof(buf), "%02u:%02u:%02u", hours, mins, secs); + } else { + snprintf(buf, sizeof(buf), "%02u:%02u", mins, secs); + } - return str; + return buf; } + diff --git a/util.h b/util.h @@ -11,6 +11,9 @@ int strnwidth(const char *s, int n); void assert(int cond, const char *file, int line, const char *condstr); +char *aprintf(const char *fmtstr, ...); +char *appendstrf(char *alloc, const char *fmtstr, ...); + char *sanitized(const char *instr); -char *aprintf(const char *fmtstr, ...); +const char *timestr(unsigned int seconds);