tmus

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

commit 3dad446ab7a6e207229b56af552dd3304a9ab11b
parent 321b23c68aa8e0c2445fdddee632693a06fd86e1
Author: Louis Burda <quent.burda@gmail.com>
Date:   Fri, 25 Feb 2022 01:31:57 +0100

Refactor player interface, improve history navigation and playlist shuffle

Diffstat:
M.gitmodules | 2+-
MMakefile | 19++++++++++++-------
Dlib/clist | 1-
Alib/liblist | 1+
Msrc/cmd.c | 2+-
Msrc/player.c | 412-------------------------------------------------------------------------------
Msrc/player.h | 64++++++++++++++++++++++++++--------------------------------------
Dsrc/player_backend.c | 4----
Dsrc/player_backend.h | 6------
Asrc/player_mpd.c | 513+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/tui.c | 80++++++++++++++++++++++++++++++++++++++++++-------------------------------------
11 files changed, 597 insertions(+), 507 deletions(-)

diff --git a/.gitmodules b/.gitmodules @@ -1,3 +1,3 @@ [submodule "lib/clist"] - path = lib/clist + path = lib/liblist url = git@sinitax.com:sinitax/clist diff --git a/Makefile b/Makefile @@ -1,5 +1,5 @@ CFLAGS = -I src -g $(shell pkg-config --cflags glib-2.0 dbus-1) -CFLAGS += -I lib/clist/include +CFLAGS += -I lib/liblist/include LDLIBS = -lcurses -lmpdclient $(shell pkg-config --libs glib-2.0 dbus-1) DEPFLAGS = -MT $@ -MMD -MP -MF build/$*.d @@ -7,23 +7,28 @@ ifeq "$(PROF)" "YES" CFLAGS += -pg endif +BACKEND ?= mpd + SRCS = $(wildcard src/*.c) -OBJS = $(SRCS:src/%.c=build/%.o) +OBJS = $(SRCS:src/%.c=build/%.o) build/player_$(BACKEND).o DEPS = $(OBJS:%.o=%.d) -LIBLIST_A = lib/clist/build/liblist.a +LIBLIST_A = lib/liblist/build/liblist.a -.PHONY: all tmus clean install uninstall +.PHONY: all tmus clean cleanlibs install uninstall all: tmus clean: rm -rf build +cleanlibs: + rm -rf lib/liblist/build + build: mkdir build -build/%.o: src/%.c build/%.d +build/%.o: src/%.c build/%.d | build $(CC) -c -o $@ $(DEPFLAGS) $(CFLAGS) $< build/%.d: | build; @@ -31,7 +36,7 @@ build/%.d: | build; include $(DEPS) $(LIBLIST_A): - make -C lib/clist build/liblist.a + make -C lib/liblist build/liblist.a tmus: $(OBJS) $(LIBLIST_A) $(CC) -o tmus $^ $(CFLAGS) $(LDLIBS) @@ -40,5 +45,5 @@ install: install -m 755 tmus /usr/bin uninstall: - rm /usr/bin/tmus + rm -f /usr/bin/tmus diff --git a/lib/clist b/lib/clist @@ -1 +0,0 @@ -Subproject commit a1eba38afd00c5c8be0a6c66684c7ace10afb3e5 diff --git a/lib/liblist b/lib/liblist @@ -0,0 +1 @@ +Subproject commit 2f54fc46a72f532feb3f98a4cc2cae88bbd0169f diff --git a/src/cmd.c b/src/cmd.c @@ -80,7 +80,7 @@ cmd_add(const wchar_t *name) tag = tag_find(name); if (!tag) return 0; - link = list_at(&player->playlist, track_nav.sel); + link = list_at(&player.playlist, track_nav.sel); if (!link) return 0; track = UPCAST(link, struct ref)->data; diff --git a/src/player.c b/src/player.c @@ -1,414 +1,2 @@ #include "player.h" -#include "ref.h" - -#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; - -static void player_clear_msg(void); -static int handle_mpd_status(int status); -static void player_play_next(struct track *track); - -static void -player_clear_msg(void) -{ - free(player->msg); - player->msg = NULL; - player->msglvl = PLAYER_MSG_NONE; -} - -static int -handle_mpd_status(int status) -{ - player_clear_msg(); - switch (status) { - case MPD_ERROR_SERVER: - case MPD_ERROR_ARGUMENT: - if (!mpd_connection_clear_error(player->conn)) - PANIC("PLAYER: Failed to recover from argument error"); - case MPD_ERROR_SYSTEM: - PLAYER_STATUS(PLAYER_MSG_ERR, "%s", - mpd_connection_get_error_message(player->conn)); - return 1; - case MPD_ERROR_CLOSED: - PANIC("PLAYER: Connection abruptly closed"); - } - return 0; -} - -void -player_play_next(struct track *prev) -{ - struct link *iter; - struct track *track; - int index; - - if (list_empty(&player->playlist)) - return; - - iter = NULL; - if (player->shuffle) { - /* TODO better algorithm for random sequence */ - index = rand() % list_len(&player->playlist); - iter = list_at(&player->playlist, index); - ASSERT(iter != NULL); - } else if (player->loaded) { - for (LIST_ITER(&player->playlist, iter)) { - track = UPCAST(iter, struct ref)->data; - if (track == prev) - break; - } - if (iter) iter = iter->next; - } - - if (!iter) iter = list_at(&player->playlist, 0); - track = UPCAST(iter, struct ref)->data; - - player_play_track(track); -} - -void -player_init(void) -{ - player = malloc(sizeof(struct player)); - OOM_CHECK(player); - - player->conn = mpd_connection_new(NULL, 0, 0); - OOM_CHECK(player->conn); - - list_init(&player->queue); - list_init(&player->history); - - list_init(&player->playlist); - - player->track = NULL; - player->loaded = 0; - player->state = PLAYER_STATE_PAUSED; - - player->autoplay = 0; - - player->shuffle = 0; - - player->action = PLAYER_ACTION_NONE; - - player->seek_delay = 0; - - player->volume = 0; - player->time_pos = 0; - player->time_end = 0; - - player->msg = NULL; - player->msglvl = PLAYER_MSG_INFO; -} - -void -player_deinit(void) -{ - struct link *iter; - - if (!player->conn) return; - - refs_free(&player->queue); - refs_free(&player->history); - - refs_free(&player->playlist); - - player_clear_msg(); - - //mpd_run_clear(player->conn); - mpd_connection_free(player->conn); -} - -void -player_update(void) -{ - struct mpd_status *status; - struct mpd_song *song; - struct ref *ref; - const char *tmp; - - status = mpd_run_status(player->conn); - if (status == NULL) - PANIC("MPD Fatal Error: %s", - mpd_connection_get_error_message(player->conn)); - - song = mpd_run_current_song(player->conn); - if (!song) { - /* if autoplay and another track just finished, - * or there are tracks in queue to be played */ - if (player->track && player->autoplay - || !list_empty(&player->queue)) { - player->action = PLAYER_ACTION_PLAY_NEXT; - } - } else { - mpd_song_free(song); - } - - mpd_status_free(status); - - if (player->action != PLAYER_ACTION_NONE) { - handle_mpd_status(mpd_run_clear(player->conn)); - - ref = NULL; - switch (player->action) { - case PLAYER_ACTION_PLAY_PREV: - if (list_empty(&player->history)) - break; - ref = UPCAST(list_pop_front(&player->history), - struct ref); - - /* TODO keep index instead until new track is played */ - /* TODO create slimmer player_backend interface */ - - /* dont add current song to history */ - player->track = NULL; - - player_play_track(ref->data); - ref_free(ref); - break; - case PLAYER_ACTION_PLAY_NEXT: - if (!list_empty(&player->queue)) { - ref = UPCAST(list_pop_front(&player->queue), - struct ref); - player_play_track(ref->data); - ref_free(ref); - } else { - player_play_next(player->track); - } - break; - default: - PANIC(); - } - player->action = PLAYER_ACTION_NONE; - } - - /* TODO move prev / next handling to own functions */ - - status = mpd_run_status(player->conn); - if (status == NULL) - PANIC("MPD Fatal Error: %s", - mpd_connection_get_error_message(player->conn)); - - song = mpd_run_current_song(player->conn); - if (song) { - player->loaded = true; - player->time_pos = mpd_status_get_elapsed_time(status); - player->time_end = mpd_song_get_duration(song); - mpd_song_free(song); - } else { - player->loaded = false; - player->track = NULL; - player->time_pos = 0; - player->time_end = 0; - } - - switch (mpd_status_get_state(status)) { - case MPD_STATE_PAUSE: - player->state = PLAYER_STATE_PAUSED; - break; - case MPD_STATE_PLAY: - player->state = PLAYER_STATE_PLAYING; - break; - case MPD_STATE_STOP: - player->state = PLAYER_STATE_STOPPED; - break; - default: - PANIC(); - } - player->volume = mpd_status_get_volume(status); - - if (player->seek_delay) { - player->seek_delay--; - if (!player->seek_delay) - player_play(); - } - - - mpd_status_free(status); -} - -void -player_queue_clear(void) -{ - refs_free(&player->queue); -} - -void -player_queue_append(struct track *track) -{ - struct ref *ref; - struct link *link; - - ref = ref_init(track); - list_push_back(&player->queue, LINK(ref)); -} - -void -player_queue_insert(struct track *track, size_t pos) -{ - struct ref *ref; - struct link *link; - - ref = ref_init(track); - link = list_at(&player->queue, pos); - link_prepend(link, LINK(ref)); -} - -int -player_play_track(struct track *track) -{ - struct link *link; - int status; - - if (player->track && player->track != track) { - list_push_front(&player->history, - LINK(ref_init(player->track))); - } - - handle_mpd_status(mpd_run_clear(player->conn)); - - status = mpd_run_add(player->conn, track->fpath); - if (handle_mpd_status(status)) - return PLAYER_ERR; - - status = mpd_run_play(player->conn); - if (handle_mpd_status(status)) - return PLAYER_ERR; - - player->track = track; - - return PLAYER_OK; -} - -int -player_toggle_pause(void) -{ - int status; - - status = mpd_run_toggle_pause(player->conn); - if (handle_mpd_status(status)) - return PLAYER_ERR; - - return PLAYER_OK; -} - -int -player_pause(void) -{ - int status; - - status = mpd_run_pause(player->conn, true); - if (handle_mpd_status(status)) - return PLAYER_ERR; - - return PLAYER_OK; -} - -int -player_resume(void) -{ - int status; - - status = mpd_run_pause(player->conn, false); - if (handle_mpd_status(status)) - return PLAYER_ERR; - - return PLAYER_OK; -} - -int -player_next(void) -{ - player->action = PLAYER_ACTION_PLAY_NEXT; - - return PLAYER_OK; -} - -int -player_prev(void) -{ - player->action = PLAYER_ACTION_PLAY_PREV; - - return PLAYER_OK; -} - -int -player_play(void) -{ - int status; - - status = mpd_run_play(player->conn); - if (handle_mpd_status(status)) - return PLAYER_ERR; - - return PLAYER_OK; -} - -int -player_stop(void) -{ - int status; - - status = mpd_run_stop(player->conn); - if (handle_mpd_status(status)) - return PLAYER_ERR; - - return PLAYER_OK; -} - -int -player_seek(int sec) -{ - int status; - - player_clear_msg(); - if (!player->loaded || player->state == PLAYER_STATE_STOPPED) { - PLAYER_STATUS(PLAYER_MSG_INFO, "No track loaded"); - return PLAYER_ERR; - } - - status = mpd_run_seek_current(player->conn, sec, false); - if (handle_mpd_status(status)) - return PLAYER_ERR; - - player->seek_delay = 7; - player_pause(); - - return PLAYER_OK; -} - -int -player_set_volume(unsigned int vol) -{ - int status; - - player_clear_msg(); - if (player->volume == -1) { - PLAYER_STATUS(PLAYER_MSG_INFO, "Volume control not supported"); - return PLAYER_ERR; - } - - status = mpd_run_set_volume(player->conn, vol); - if (handle_mpd_status(status)) - return PLAYER_ERR; - - return PLAYER_OK; -} diff --git a/src/player.h b/src/player.h @@ -4,19 +4,15 @@ #include "list.h" #include "util.h" -#include "mpd/client.h" - -#include <signal.h> - enum { - PLAYER_OK, - PLAYER_ERR + PLAYER_STATUS_OK, + PLAYER_STATUS_ERR }; enum { - PLAYER_MSG_NONE, - PLAYER_MSG_INFO, - PLAYER_MSG_ERR + PLAYER_STATUS_MSG_NONE, + PLAYER_STATUS_MSG_INFO, + PLAYER_STATUS_MSG_ERR }; enum { @@ -33,45 +29,41 @@ enum { }; struct player { - /* TODO move implementation details to source file */ - struct mpd_connection *conn; - - /* TODO combine with index */ - /* for navigating forward and backwards in time */ - struct list queue; - struct list history; + /* played track history */ + struct list history; /* struct ref -> struct track */ + struct link *history_sel; /* position in history */ - /* list of track refs to choose from on prev / next */ - struct list playlist; + /* queued tracks */ + struct list queue; /* struct ref -> struct track */ - /* last player track */ + /* selected track, not (yet) part of history or queue */ struct track *track; - /* player has a track loaded, - * not necessarily player->track */ - int loaded; + /* list of tracks to choose from on prev / next */ + struct list playlist; /* struct ref -> struct track */ + struct link *playlist_sel; /* position in playlist */ - /* stopped, paused or playing */ - int state; + /* a track is loaded, not necessarily player.track */ + bool loaded; /* automatically select new tracks when queue empty */ - int autoplay; + bool autoplay; - int shuffle; + /* randomize which track is chosen when queue empty */ + bool shuffle; - int action; - - /* number of frames to wait before unpausing after - * seek to prevent pause-play cycle noises */ - int seek_delay; + /* stopped, paused or playing */ + int state; + /* volume adjustment when possible */ int volume; + /* track position and duration */ unsigned int time_pos, time_end; /* status messaging */ - char *msg; - int msglvl; + char *status; + int status_lvl; }; void player_init(void); @@ -79,10 +71,6 @@ void player_deinit(void); void player_update(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_play_track(struct track *track); int player_toggle_pause(void); @@ -96,5 +84,5 @@ int player_stop(void); int player_set_volume(unsigned int vol); -extern struct player *player; +extern struct player player; diff --git a/src/player_backend.c b/src/player_backend.c @@ -1,4 +0,0 @@ -#include "player_backend.h" - -void foo(void); - diff --git a/src/player_backend.h b/src/player_backend.h @@ -1,6 +0,0 @@ -#pragma once - -struct player_data { - int time_pos, time_end; -}; - diff --git a/src/player_mpd.c b/src/player_mpd.c @@ -0,0 +1,513 @@ +#include "player.h" +#include "ref.h" + +#include "portaudio.h" +#include "sndfile.h" +#include "util.h" + +#include <mpd/client.h> +#include <mpd/song.h> +#include <mpd/status.h> + +#include <signal.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> + +#define PLAYER_STATUS(lvl, ...) do { \ + player.status_lvl = PLAYER_STATUS_MSG_ ## lvl; \ + if (player.status) free(player.status); \ + player.status = aprintf(__VA_ARGS__); \ + } while (0) + +struct mpd_player { + struct mpd_connection *conn; + + /* number of frames to wait before unpausing after + * seek to prevent pause-play cycle noises */ + int seek_delay; + + /* action to perform on next update */ + int action; +}; + +struct player player; +struct mpd_player mpd; + +static void player_clear_status(void); + +static int mpd_handle_status(int status); + +static bool history_contains(struct track *track, int depth); + +static struct track *playlist_track_lru(int skip); + +static void player_play_prev(void); +static void player_play_next(void); + +static void player_add_history(struct track *track); + +void +player_clear_status(void) +{ + free(player.status); + player.status = NULL; + player.status_lvl = PLAYER_STATUS_MSG_NONE; +} + +int +mpd_handle_status(int status) +{ + const char *errstr; + + player_clear_status(); + + switch (status) { + case MPD_ERROR_SERVER: + case MPD_ERROR_ARGUMENT: + if (!mpd_connection_clear_error(mpd.conn)) + ERROR("PLAYER: Failed to recover from argument error"); + case MPD_ERROR_SYSTEM: + errstr = mpd_connection_get_error_message(mpd.conn); + PLAYER_STATUS(ERR, "ERR - %s", errstr); + return 1; + case MPD_ERROR_CLOSED: + ERROR("PLAYER: Connection abruptly closed"); + } + + return 0; +} + +bool +history_contains(struct track *track, int depth) +{ + struct link *link; + struct ref *ref; + + link = list_back(&player.history); + while (LIST_INNER(link) && depth-- > 0) { + ref = UPCAST(link, struct ref); + if (track == ref->data) + return true; + link = link->prev; + } + + return false; +} + +struct track * +playlist_track_lru(int skip) +{ + struct track *track; + struct link *link; + struct ref *ref; + int len; + + track = NULL; + + len = list_len(&player.playlist); + link = list_front(&player.playlist); + while (skip >= 0 && LIST_INNER(link)) { + ref = UPCAST(link, struct ref); + track = ref->data; + + if (!history_contains(track, len - 1)) + skip -= 1; + + if (skip <= 0) break; + + link = link->next; + if (!LIST_INNER(link)) + link = list_front(&player.playlist); + } + + PLAYER_STATUS(INFO, "%i, %i", len - 1, skip); + + return track; +} + +void +player_play_prev(void) +{ + struct link *link, *next; + struct track *track; + struct ref *ref; + + if (list_empty(&player.history)) + return; + + if (!player.history_sel) { + next = list_back(&player.history); + } else if (LIST_INNER(player.history_sel->prev)) { + next = player.history_sel->prev; + } else { + return; + } + + ref = UPCAST(next, struct ref); + player_play_track(ref->data); + + player.history_sel = next; +} + +void +player_play_next(void) +{ + struct link *link; + struct track *track, *next_track; + struct ref *ref; + int index, len; + int status; + + next_track = NULL; + + link = player.history_sel; + if (link && LIST_INNER(link->next)) { + player.history_sel = link->next; + ref = UPCAST(link->next, struct ref); + next_track = ref->data; + } else { + if (!list_empty(&player.queue)) { + link = list_pop_front(&player.queue); + ref = UPCAST(link, struct ref); + next_track = ref->data; + ref_free(ref); + } else { + if (list_empty(&player.playlist)) + return; + + if (!player.history_sel) { + player_add_history(player.track); + player.track = NULL; + } + player.history_sel = NULL; + + if (player.shuffle) { + index = rand() % list_len(&player.playlist); + next_track = playlist_track_lru(index + 1); + ASSERT(next_track != NULL); + } else { + link = player.playlist_sel; + if (link && LIST_INNER(link->next)) { + ref = UPCAST(link->next, struct ref); + next_track = ref->data; + } else { + status = mpd_run_clear(mpd.conn); + mpd_handle_status(status); + return; + } + } + } + } + + player_play_track(next_track); +} + +void +player_add_history(struct track *track) +{ + struct link *link; + struct ref *ref; + + link = list_back(&player.history); + if (link) { + ref = UPCAST(link, struct ref); + if (ref->data == track) return; + } + + ref = ref_init(track); + list_push_back(&player.history, LINK(ref)); +} + +void +player_init(void) +{ + mpd.conn = NULL; + mpd.action = PLAYER_ACTION_NONE; + mpd.seek_delay = 0; + + list_init(&player.history); + player.history_sel = NULL; + list_init(&player.queue); + player.track = NULL; + list_init(&player.playlist); + player.playlist_sel = NULL; + player.loaded = 0; + player.autoplay = true; + player.shuffle = true; + player.state = PLAYER_STATE_PAUSED; + player.volume = 0; + player.time_pos = 0; + player.time_end = 0; + player.status = NULL; + player.status_lvl = PLAYER_STATUS_MSG_INFO; +} + +void +player_deinit(void) +{ + struct link *iter; + + if (!mpd.conn) return; + + refs_free(&player.queue); + refs_free(&player.history); + refs_free(&player.playlist); + if (player.status) free(player.status); + + if (mpd.conn) mpd_connection_free(mpd.conn); +} + +void +player_update(void) +{ + struct mpd_status *status; + struct mpd_song *current_song; + struct ref *ref; + bool queue_empty; + + if (!mpd.conn) { + mpd.conn = mpd_connection_new(NULL, 0, 0); + if (!mpd.conn) ERROR("MPD: Connect to server failed\n"); + } + + status = mpd_run_status(mpd.conn); + if (!status) { + PLAYER_STATUS(ERR, "Resetting MPD server connection"); + mpd_connection_free(mpd.conn); + mpd.conn = NULL; + return; + } + + current_song = mpd_run_current_song(mpd.conn); + if (!current_song) { + if (player.track && !player.history_sel) { + player_add_history(player.track); + player.track = NULL; + } + + queue_empty = list_empty(&player.queue); + if (player.loaded && player.autoplay || !queue_empty) + mpd.action = PLAYER_ACTION_PLAY_NEXT; + } else { + mpd_song_free(current_song); + } + + mpd_status_free(status); + + if (mpd.action != PLAYER_ACTION_NONE) { + switch (mpd.action) { + case PLAYER_ACTION_PLAY_PREV: + player_play_prev(); + break; + case PLAYER_ACTION_PLAY_NEXT: + player_play_next(); + break; + default: + PANIC(); + } + + mpd.action = PLAYER_ACTION_NONE; + } + + status = mpd_run_status(mpd.conn); + if (!status) { + PLAYER_STATUS(ERR, "Resetting MPD server connection"); + mpd_connection_free(mpd.conn); + mpd.conn = NULL; + return; + } + + current_song = mpd_run_current_song(mpd.conn); + if (current_song) { + player.loaded = true; + player.time_pos = mpd_status_get_elapsed_time(status); + player.time_end = mpd_song_get_duration(current_song); + mpd_song_free(current_song); + } else { + player.loaded = false; + player.time_pos = 0; + player.time_end = 0; + } + + switch (mpd_status_get_state(status)) { + case MPD_STATE_PAUSE: + player.state = PLAYER_STATE_PAUSED; + break; + case MPD_STATE_PLAY: + player.state = PLAYER_STATE_PLAYING; + break; + case MPD_STATE_STOP: + player.state = PLAYER_STATE_STOPPED; + break; + default: + PANIC(); + } + + player.volume = mpd_status_get_volume(status); + + if (mpd.seek_delay) { + mpd.seek_delay -= 1; + if (!mpd.seek_delay) player_play(); + } + + mpd_status_free(status); +} + +int +player_play_track(struct track *track) +{ + struct link *link; + struct ref *ref; + int status; + + status = mpd_run_clear(mpd.conn); + mpd_handle_status(status); + + status = mpd_run_add(mpd.conn, track->fpath); + if (mpd_handle_status(status)) + return PLAYER_STATUS_ERR; + + status = mpd_run_play(mpd.conn); + if (mpd_handle_status(status)) + return PLAYER_STATUS_ERR; + + if (player.track && !player.history_sel) { + player_add_history(player.track); + player.track = NULL; + } + + player.history_sel = NULL; + /* re-assigning history_sel done in calling code */ + + player.playlist_sel = NULL; + for (LIST_ITER(&player.playlist, link)) { + ref = UPCAST(link, struct ref); + if (ref->data == track) { + player.playlist_sel = link; + break; + } + } + + player.track = track; + + return PLAYER_STATUS_OK; +} + +int +player_toggle_pause(void) +{ + int status; + + status = mpd_run_toggle_pause(mpd.conn); + if (mpd_handle_status(status)) + return PLAYER_STATUS_ERR; + + return PLAYER_STATUS_OK; +} + +int +player_pause(void) +{ + int status; + + status = mpd_run_pause(mpd.conn, true); + if (mpd_handle_status(status)) + return PLAYER_STATUS_ERR; + + return PLAYER_STATUS_OK; +} + +int +player_resume(void) +{ + int status; + + status = mpd_run_pause(mpd.conn, false); + if (mpd_handle_status(status)) + return PLAYER_STATUS_ERR; + + return PLAYER_STATUS_OK; +} + +int +player_next(void) +{ + mpd.action = PLAYER_ACTION_PLAY_NEXT; + + return PLAYER_STATUS_OK; +} + +int +player_prev(void) +{ + mpd.action = PLAYER_ACTION_PLAY_PREV; + + return PLAYER_STATUS_OK; +} + +int +player_play(void) +{ + int status; + + status = mpd_run_play(mpd.conn); + if (mpd_handle_status(status)) + return PLAYER_STATUS_ERR; + + return PLAYER_STATUS_OK; +} + +int +player_stop(void) +{ + int status; + + status = mpd_run_stop(mpd.conn); + if (mpd_handle_status(status)) + return PLAYER_STATUS_ERR; + + return PLAYER_STATUS_OK; +} + +int +player_seek(int sec) +{ + int status; + + player_clear_status(); + if (!player.loaded || player.state == PLAYER_STATE_STOPPED) { + PLAYER_STATUS(ERR, "No track loaded"); + return PLAYER_STATUS_ERR; + } + + status = mpd_run_seek_current(mpd.conn, sec, false); + if (mpd_handle_status(status)) + return PLAYER_STATUS_ERR; + + mpd.seek_delay = 7; + player_pause(); + + return PLAYER_STATUS_OK; +} + +int +player_set_volume(unsigned int vol) +{ + int status; + + player_clear_status(); + if (player.volume == -1) { + PLAYER_STATUS(ERR, "Volume control not supported"); + return PLAYER_STATUS_ERR; + } + + status = mpd_run_set_volume(mpd.conn, vol); + if (mpd_handle_status(status)) + return PLAYER_STATUS_ERR; + + return PLAYER_STATUS_OK; +} + diff --git a/src/tui.c b/src/tui.c @@ -225,13 +225,13 @@ toggle_current_tag(void) } /* rebuild the full playlist */ - refs_free(&player->playlist); + refs_free(&player.playlist); for (LIST_ITER(&tags_sel, link)) { tag = UPCAST(link, struct ref)->data; for (LIST_ITER(&tag->tracks, iter)) { ref = ref_init(UPCAST(iter, struct ref)->data); ASSERT(ref != NULL); - list_push_back(&player->playlist, LINK(ref)); + list_push_back(&player.playlist, LINK(ref)); } } } @@ -375,11 +375,11 @@ track_pane_vis(struct pane *pane, int sel) if (index < track_nav.wmin) continue; if (index >= track_nav.wmax) break; - if (sel && index == track_nav.sel && track == player->track) + if (sel && index == track_nav.sel && track == player.track) style_on(pane->win, STYLE_ITEM_HOVER_SEL); else if (sel && index == track_nav.sel) style_on(pane->win, STYLE_ITEM_HOVER); - else if (track == player->track) + else if (track == player.track) style_on(pane->win, STYLE_ITEM_SEL); else if (index == track_nav.sel) style_on(pane->win, STYLE_PREV); @@ -387,11 +387,11 @@ track_pane_vis(struct pane *pane, int sel) wmove(pane->win, 1 + index - track_nav.wmin, 0); wprintw(pane->win, "%-*.*ls", pane->w, pane->w, track->name); - if (sel && index == track_nav.sel && track == player->track) + if (sel && index == track_nav.sel && track == player.track) style_off(pane->win, STYLE_ITEM_HOVER_SEL); else if (sel && index == track_nav.sel) style_off(pane->win, STYLE_ITEM_HOVER); - else if (track == player->track) + else if (track == player.track) style_off(pane->win, STYLE_ITEM_SEL); else if (index == track_nav.sel) style_off(pane->win, STYLE_PREV); @@ -581,46 +581,46 @@ cmd_pane_vis(struct pane *pane, int sel) /* track name */ style_on(pane->win, STYLE_TITLE); pane_clearln(pane, 0); - if (player->track) { - swprintf(linebuf, linecap, L"%ls", player->track->name); + if (player.loaded && player.track) { + swprintf(linebuf, linecap, L"%ls", player.track->name); mvwaddwstr(pane->win, 0, 1, linebuf); } style_off(pane->win, STYLE_TITLE); - if (player->loaded) { + if (player.loaded) { /* status line */ line = linebuf; line += swprintf(line, end - line, L"%c ", - player_state_chars[player->state]); + player_state_chars[player.state]); line += swprintf(line, end - line, L"%s / ", - timestr(player->time_pos)); + timestr(player.time_pos)); line += swprintf(line, end - line, L"%s", - timestr(player->time_end)); + timestr(player.time_end)); - if (player->volume >= 0) { + if (player.volume >= 0) { line += swprintf(line, end - line, L" - vol: %u%%", - player->volume); + player.volume); } - if (player->msg) { + if (player.status) { line += swprintf(line, end - line, L" | [PLAYER] %s", - player->msg); + player.status); } - if (!list_empty(&player->queue)) { + if (list_len(&player.queue)) { line += swprintf(line, end - line, L" | [QUEUE] %i tracks", - list_len(&player->queue)); + list_len(&player.queue)); } ATTR_ON(pane->win, A_REVERSE); pane_clearln(pane, 1); mvwaddwstr(pane->win, 1, 0, linebuf); ATTR_OFF(pane->win, A_REVERSE); - } else if (player->msg) { + } else if (player.status) { /* player message */ line = linebuf; - line += swprintf(line, linecap, L"[PLAYER] %s", player->msg); + line += swprintf(line, linecap, L"[PLAYER] %s", player.status); line += swprintf(line, end - line, L"%*.*s", pane->w, pane->w, L" "); @@ -629,12 +629,12 @@ cmd_pane_vis(struct pane *pane, int sel) } /* status bits on right of status line */ - if (player->loaded) ATTR_ON(pane->win, A_REVERSE); + if (player.loaded) ATTR_ON(pane->win, A_REVERSE); mvwaddstr(pane->win, 1, pane->w - 5, "[ ]"); if (track_show_playlist) mvwaddstr(pane->win, 1, pane->w - 4, "P"); - if (player->autoplay) mvwaddstr(pane->win, 1, pane->w - 3, "A"); - if (player->shuffle) mvwaddstr(pane->win, 1, pane->w - 2, "S"); - if (player->loaded) ATTR_OFF(pane->win, A_REVERSE); + if (player.autoplay) mvwaddstr(pane->win, 1, pane->w - 3, "A"); + if (player.shuffle) mvwaddstr(pane->win, 1, pane->w - 2, "S"); + if (player.loaded) ATTR_OFF(pane->win, A_REVERSE); if (sel || cmd_show) { /* cmd and search input */ @@ -681,10 +681,13 @@ void queue_hover(void) { struct link *link; + struct ref *ref; - link = list_at(&player->playlist, track_nav.sel); + link = list_at(&player.playlist, track_nav.sel); if (!link) return; - player_queue_append(UPCAST(link, struct ref)->data); + + ref = UPCAST(link, struct ref); + list_push_back(&player.queue, ref->data); } void @@ -694,7 +697,7 @@ update_track_playlist(void) struct tag *tag; if (track_show_playlist) { - tracks_vis = &player->playlist; + tracks_vis = &player.playlist; } else { link = list_at(&tags, tag_nav.sel); if (!link) return; @@ -719,18 +722,21 @@ main_input(wint_t c) pane_sel = pane_after_cmd; break; case KEY_LEFT: - if (!player->loaded) break; - player_seek(MAX(player->time_pos - 10, 0)); + if (!player.loaded) break; + player_seek(MAX(player.time_pos - 10, 0)); break; case KEY_RIGHT: - if (!player->loaded) break; - player_seek(MIN(player->time_pos + 10, player->time_end)); + if (!player.loaded) break; + player_seek(MIN(player.time_pos + 10, player.time_end)); break; case L'y': queue_hover(); break; case L'o': - player_queue_clear(); + refs_free(&player.queue); + break; + case L'h': + refs_free(&player.history); break; case L'c': player_toggle_pause(); @@ -753,16 +759,16 @@ main_input(wint_t c) } break; case L'A': - player->autoplay ^= 1; + player.autoplay ^= 1; break; case L'S': - player->shuffle ^= 1; + player.shuffle ^= 1; break; case L'b': player_seek(0); break; case L'x': - if (player->state == PLAYER_STATE_PLAYING) { + if (player.state == PLAYER_STATE_PLAYING) { player_stop(); } else { player_play(); @@ -804,10 +810,10 @@ main_input(wint_t c) completion = tag_name_gen; break; case L'+': - player_set_volume(MIN(100, player->volume + 5)); + player_set_volume(MIN(100, player.volume + 5)); break; case L'-': - player_set_volume(MAX(0, player->volume - 5)); + player_set_volume(MAX(0, player.volume - 5)); break; case L'q': quit = 1;