commit 33b966e7236912d06ff5033f69b69c687a7a7803
parent 2094e88cd1f4ddf511a5810f2ab376e49f1eeba3
Author: Louis Burda <quent.burda@gmail.com>
Date: Sun, 18 Dec 2022 01:12:48 +0100
Implement player backend for mplay
Diffstat:
6 files changed, 376 insertions(+), 23 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,15 +1,19 @@
CFLAGS = -I src -g $(shell pkg-config --cflags glib-2.0 dbus-1)
CFLAGS += -I lib/liblist/include -Wunused-variable -Wmissing-prototypes
-LDLIBS = -lcurses -lmpdclient $(shell pkg-config --libs glib-2.0 dbus-1)
+LDLIBS = -lcurses $(shell pkg-config --libs glib-2.0 dbus-1)
DEPFLAGS = -MT $@ -MMD -MP -MF build/$*.d
ifeq "$(PROF)" "YES"
CFLAGS += -pg
endif
-BACKEND ?= mpd
+BACKEND ?= mplay
-SRCS = $(wildcard src/*.c)
+ifeq "$(BACKEND)" "mpd"
+ LDLIBS += -lmpdclient
+endif
+
+SRCS = $(filter-out src/player_%.c, $(wildcard src/*.c))
OBJS = $(SRCS:src/%.c=build/%.o) build/player_$(BACKEND).o
DEPS = $(OBJS:%.o=%.d)
diff --git a/src/player.c b/src/player.c
@@ -86,6 +86,40 @@ player_next_from_playlist(void)
return NULL;
}
+/* implemented by backend:
+ *
+ * void player_init(void);
+ * void player_deinit(void);
+ *
+ * void player_update(void);
+ *
+ * int player_play_track(struct track *track, bool new);
+ * int player_clear_track(void);
+ */
+
+void
+player_add_history(struct track *new)
+{
+ struct link *link;
+ struct track *track;
+
+ link = list_back(&player.history);
+ if (link) {
+ track = UPCAST(link, struct track, link_hs);
+ if (track == new) return;
+ }
+
+ link_pop(&new->link_hs);
+ list_push_back(&player.history, &new->link_hs);
+}
+
+/* implemented by backend:
+ *
+ * int player_toggle_pause(void);
+ * int player_pause(void);
+ * int player_resume(void);
+ */
+
int
player_prev(void)
{
@@ -139,4 +173,12 @@ clear:
return PLAYER_STATUS_ERR;
}
+/* implemented by backend:
+ *
+ * int player_seek(int sec);
+ * int player_play(void);
+ * int player_stop(void);
+ *
+ * int player_set_volume(unsigned int vol);
+ */
diff --git a/src/player.h b/src/player.h
@@ -68,6 +68,8 @@ void player_update(void);
int player_play_track(struct track *track, bool new);
int player_clear_track(void);
+void player_add_history(struct track *track);
+
int player_toggle_pause(void);
int player_pause(void);
int player_resume(void);
diff --git a/src/player_mpd.c b/src/player_mpd.c
@@ -41,8 +41,6 @@ static void player_clear_status(void);
static bool mpd_handle_status(int status);
static char *mpd_loaded_track_name(struct mpd_song *song);
-static void player_add_history(struct track *track);
-
void
player_clear_status(void)
{
@@ -88,22 +86,6 @@ mpd_loaded_track_name(struct mpd_song *song)
}
void
-player_add_history(struct track *new)
-{
- struct link *link;
- struct track *track;
-
- link = list_back(&player.history);
- if (link) {
- track = UPCAST(link, struct track, link_hs);
- if (track == new) return;
- }
-
- link_pop(&new->link_hs);
- list_push_back(&player.history, &new->link_hs);
-}
-
-void
player_init(void)
{
mpd.conn = NULL;
diff --git a/src/player_mplay.c b/src/player_mplay.c
@@ -0,0 +1,323 @@
+#include "player.h"
+
+#include "data.h"
+#include "list.h"
+#include "util.h"
+#include "log.h"
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.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 mplay_player {
+ FILE *stdin;
+ FILE *stdout;
+ pid_t pid;
+};
+
+struct player player;
+struct mplay_player mplay;
+
+static bool mplay_alive(void);
+static void mplay_kill(void);
+static bool mplay_run(struct track *track);
+static char *mplay_readline(void);
+static void player_clear_status(void);
+
+bool
+mplay_alive(void)
+{
+ return player.loaded && !kill(mplay.pid, 0);
+}
+
+bool
+mplay_run(struct track *track)
+{
+ int output[2];
+ int input[2];
+ char *path;
+ char *line;
+
+ ASSERT(!player.loaded);
+
+ if (pipe(input) == -1)
+ err(1, "pipe");
+
+ if (pipe(output) == -1)
+ err(1, "pipe");
+
+ mplay.pid = fork();
+ if (mplay.pid < 0) err(1, "fork");
+
+ if (mplay.pid != 0) {
+ close(output[1]);
+ mplay.stdout = fdopen(output[0], "r");
+ if (!mplay.stdout) err(1, "fdopen");
+ setvbuf(mplay.stdout, NULL, _IONBF, 0);
+
+ close(input[0]);
+ mplay.stdin = fdopen(input[1], "w");
+ if (!mplay.stdin) err(1, "fdopen");
+ setvbuf(mplay.stdin, NULL, _IONBF, 0);
+ } else {
+ close(0);
+ close(1);
+ dup2(input[0], 0);
+ dup2(output[1], 1);
+ close(output[0]);
+ close(output[1]);
+ path = aprintf("%s/%s/%s", datadir,
+ track->tag->name, track->name);
+ execl("/usr/bin/mplay", "mplay", path, NULL);
+ abort();
+ }
+
+ player.loaded = true;
+
+ line = mplay_readline();
+ if (!line || strcmp(line, "+READY")) {
+ mplay_kill();
+ PLAYER_STATUS(ERR, "mplay failed to start");
+ return false;
+ }
+
+ return true;
+}
+
+void
+mplay_kill(void)
+{
+ if (!player.loaded)
+ return;
+
+ kill(mplay.pid, SIGKILL);
+ waitpid(mplay.pid, NULL, 0);
+ player.loaded = false;
+}
+
+char *
+mplay_readline(void)
+{
+ static char linebuf[256];
+ char *tok;
+
+ /* TODO: add timeout */
+ if (!fgets(linebuf, sizeof(linebuf), mplay.stdout))
+ return NULL;
+
+ tok = strchr(linebuf, '\n');
+ if (tok) *tok = '\0';
+
+ return linebuf;
+}
+
+void
+player_clear_status(void)
+{
+ free(player.status);
+ player.status = NULL;
+ player.status_lvl = PLAYER_STATUS_MSG_NONE;
+}
+
+void
+player_init(void)
+{
+ list_init(&player.playlist);
+ list_init(&player.history);
+ list_init(&player.queue);
+
+ player.track = NULL;
+ player.track_name = NULL;
+
+ player.loaded = false;
+ player.autoplay = true;
+ player.shuffle = true;
+
+ player.state = PLAYER_STATE_PAUSED;
+ player.volume = 50;
+
+ player.time_pos = 0;
+ player.time_end = 0;
+
+ player.status = NULL;
+ player.status_lvl = PLAYER_STATUS_MSG_INFO;
+}
+
+void
+player_deinit(void)
+{
+ list_clear(&player.playlist);
+ list_clear(&player.queue);
+ list_clear(&player.history);
+
+ free(player.status);
+ free(player.track_name);
+
+ mplay_kill();
+}
+
+void
+player_update(void)
+{
+ char *tok, *line;
+
+ if (!player.loaded) return;
+
+ fprintf(mplay.stdin, "status\n");
+ line = mplay_readline();
+ if (!line || strncmp(line, "+STATUS:", 8)) {
+ PLAYER_STATUS(ERR, "PLAYER: invalid response");
+ return;
+ }
+
+ tok = line;
+ while ((tok = strchr(tok, ' '))) {
+ if (!strncmp(tok + 1, "vol:", 4)) {
+ player.volume = atoi(tok + 5);
+ } else if (!strncmp(tok + 1, "pause:", 6)) {
+ player.state = atoi(tok + 7)
+ ? PLAYER_STATE_PAUSED : PLAYER_STATE_PLAYING;
+ } else if (!strncmp(tok + 1, "pos:", 4)) {
+ player.time_pos = atoi(tok + 5);
+ player.time_end = MAX(player.time_pos, player.time_end);
+ }
+ tok += 1;
+ }
+}
+
+int
+player_play_track(struct track *track, bool new)
+{
+ ASSERT(track != NULL);
+ player_clear_track();
+
+ if (!mplay_run(track))
+ return PLAYER_STATUS_ERR;
+
+ /* add last track to history */
+ if (player.track && !link_inuse(&player.track->link_hs))
+ player_add_history(player.track);
+
+ /* new invocations result in updated history pos */
+ if (new) link_pop(&track->link_hs);
+
+ player.track = track;
+ player.time_pos = 0;
+ player.time_end = 0;
+
+ return PLAYER_STATUS_OK;
+}
+
+int
+player_clear_track(void)
+{
+ player.track = NULL;
+ mplay_kill();
+
+ return PLAYER_STATUS_OK;
+}
+
+int
+player_toggle_pause(void)
+{
+ char *line;
+
+ fprintf(mplay.stdin, "pause\n");
+ line = mplay_readline();
+ if (!line || strncmp(line, "+PAUSE:", 7)) {
+ PLAYER_STATUS(ERR, "PLAYER: invalid response");
+ return PLAYER_STATUS_ERR;
+ }
+
+ return PLAYER_STATUS_OK;
+}
+
+int
+player_pause(void)
+{
+ if (player.state != PLAYER_STATE_PAUSED)
+ player_toggle_pause();
+
+ return PLAYER_STATUS_OK;
+}
+
+int
+player_resume(void)
+{
+ if (player.state != PLAYER_STATE_PLAYING)
+ player_toggle_pause();
+
+ return PLAYER_STATUS_OK;
+}
+
+int
+player_play(void)
+{
+ return PLAYER_STATUS_OK;
+}
+
+int
+player_stop(void)
+{
+ player_clear_track();
+
+ return PLAYER_STATUS_OK;
+}
+
+int
+player_seek(int sec)
+{
+ char *line;
+
+ player_clear_status();
+
+ fprintf(mplay.stdin, "seek %i\n", sec);
+ line = mplay_readline();
+ if (!line || strncmp(line, "+SEEK:", 6)) {
+ PLAYER_STATUS(ERR, "PLAYER: Bad response");
+ return PLAYER_STATUS_ERR;
+ }
+
+ player.time_pos = atoi(line + 7);
+ player.time_end = MAX(player.time_pos, player.time_end);
+
+ return PLAYER_STATUS_OK;
+}
+
+int
+player_set_volume(unsigned int vol)
+{
+ char *line;
+
+ player_clear_status();
+
+ if (player.volume == -1) {
+ PLAYER_STATUS(ERR, "Volume control not supported");
+ return PLAYER_STATUS_ERR;
+ }
+
+ fprintf(mplay.stdin, "vol %i\n", vol);
+ line = mplay_readline();
+ if (!line || strncmp(line, "+VOLUME:", 8)) {
+ PLAYER_STATUS(ERR, "PLAYER: Bad response");
+ return PLAYER_STATUS_ERR;
+ }
+
+ player.volume = atoi(line + 9);
+
+ return PLAYER_STATUS_OK;
+}
+
diff --git a/src/tui.c b/src/tui.c
@@ -1044,11 +1044,11 @@ main_input(wint_t c)
break;
case KEY_LEFT:
if (!player.loaded) break;
- player_seek(MAX(player.time_pos - 10, 0));
+ player_seek(player.time_pos - 10);
break;
case KEY_RIGHT:
if (!player.loaded) break;
- player_seek(MIN(player.time_pos + 10, player.time_end));
+ player_seek(player.time_pos + 10);
break;
case L'y':
queue_hover();