diff options
Diffstat (limited to 'mpdws.c')
| -rw-r--r-- | mpdws.c | 244 |
1 files changed, 244 insertions, 0 deletions
@@ -0,0 +1,244 @@ +#include "mpdws.h" +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/select.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +static struct mpd_ws_server *g_server = NULL; + +static void signal_handler(int sig) { + (void)sig; + if (g_server) + g_server->running = 0; +} + +static void broadcast_current_song(struct mpd_ws_server *server) { + struct client_session *client = server->clients; + size_t len = strlen(server->current_song); + + while (client) { + unsigned char buf[LWS_PRE + MAX_MESSAGE_SIZE]; + memcpy(&buf[LWS_PRE], server->current_song, len); + lws_write(client->wsi, &buf[LWS_PRE], len, LWS_WRITE_TEXT); + client = client->next; + } +} + +static int ws_callback(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) { + (void)user; + (void)in; + (void)len; + + struct mpd_ws_server *server = + (struct mpd_ws_server *)lws_context_user(lws_get_context(wsi)); + + switch (reason) { + case LWS_CALLBACK_ESTABLISHED: { + struct client_session *client = malloc(sizeof(*client)); + if (client) { + client->wsi = wsi; + client->next = server->clients; + server->clients = client; + syslog(LOG_INFO, "Client connected"); + if (strlen(server->current_song) > 0) { + lws_callback_on_writable(wsi); + } + } + break; + } + case LWS_CALLBACK_CLOSED: { + struct client_session **current = &server->clients; + while (*current) { + if ((*current)->wsi == wsi) { + struct client_session *to_remove = *current; + *current = (*current)->next; + free(to_remove); + syslog(LOG_INFO, "Client disconnected"); + break; + } + current = &(*current)->next; + } + break; + } + case LWS_CALLBACK_SERVER_WRITEABLE: + if (strlen(server->current_song) > 0) { + unsigned char buf[LWS_PRE + MAX_MESSAGE_SIZE]; + size_t msg_len = strlen(server->current_song); + memcpy(&buf[LWS_PRE], server->current_song, msg_len); + lws_write(wsi, &buf[LWS_PRE], msg_len, LWS_WRITE_TEXT); + } + break; + } + return 0; +} + +static struct lws_protocols protocols[] = { + {"mpd-protocol", ws_callback, 0, MAX_MESSAGE_SIZE, 0, NULL, 0}, + {NULL, NULL, 0, 0, 0, NULL, 0}}; + +static int connect_mpd(struct mpd_ws_server *server) { + if (server->mpd_conn) { + if (server->mpd_idle_active) { + mpd_send_noidle(server->mpd_conn); + mpd_response_finish(server->mpd_conn); + } + mpd_connection_free(server->mpd_conn); + } + + server->mpd_conn = mpd_connection_new(MPD_HOST, MPD_PORT, 0); + server->mpd_idle_active = 0; + + if (mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { + syslog(LOG_ERR, "MPD connection failed: %s", + mpd_connection_get_error_message(server->mpd_conn)); + mpd_connection_free(server->mpd_conn); + server->mpd_conn = NULL; + return -1; + } + + syslog(LOG_INFO, "Connected to MPD at %s:%d", MPD_HOST, MPD_PORT); + return 0; +} + +static void update_current_song(struct mpd_ws_server *server) { + if (!server->mpd_conn || + mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { + return; + } + + mpd_send_current_song(server->mpd_conn); + struct mpd_song *song = mpd_recv_song(server->mpd_conn); + + if (mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { + mpd_response_finish(server->mpd_conn); + return; + } + + char new_song[MAX_MESSAGE_SIZE]; + if (!song) { + snprintf(new_song, sizeof(new_song), "Now Playing: No song"); + } else { + const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); + const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); + + snprintf(new_song, sizeof(new_song), "Now Playing: %s - %s", artist, title); + mpd_song_free(song); + } + + // Only send song if it's changed + if (strcmp(new_song, server->current_song) != 0) { + strlcpy(server->current_song, new_song, sizeof(server->current_song)); + broadcast_current_song(server); + syslog(LOG_DEBUG, "Broadcasting: %s", server->current_song); + } + + mpd_response_finish(server->mpd_conn); +} + +int mpd_ws_init(struct mpd_ws_server *server) { + memset(server, 0, sizeof(*server)); + + g_server = server; + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + openlog("mpd_ws", LOG_PID | LOG_NDELAY, LOG_DAEMON); + + struct lws_context_creation_info info = {0}; + info.port = WEBSOCKET_PORT; + info.protocols = protocols; + info.gid = info.uid = -1; + info.user = server; + + server->ws_context = lws_create_context(&info); + if (!server->ws_context) { + syslog(LOG_ERR, "Failed to create WebSocket context"); + return -1; + } + + syslog(LOG_INFO, "WebSocket server listening on port %d", WEBSOCKET_PORT); + connect_mpd(server); + server->running = 1; + return 0; +} + +void mpd_ws_run(struct mpd_ws_server *server) { + time_t last_reconnect = 0; + + while (server->running) { + lws_service(server->ws_context, 10); + + // Handle MPD connection + if (!server->mpd_conn || + mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { + time_t now = time(NULL); + if (now - last_reconnect >= RECONNECT_INTERVAL_SEC) { + if (connect_mpd(server) == 0) { + update_current_song(server); + } + last_reconnect = now; + } + sleep(SELECT_TIMEOUT_SEC); + continue; + } + + // Start idle if needed + if (!server->mpd_idle_active) { + if (mpd_send_idle_mask(server->mpd_conn, MPD_IDLE_PLAYER)) { + server->mpd_idle_active = 1; + } else { + connect_mpd(server); + continue; + } + } + + // Check for MPD events + fd_set readfds; + int mpd_fd = mpd_connection_get_fd(server->mpd_conn); + struct timeval timeout = {0, SELECT_TIMEOUT_SEC}; + + FD_ZERO(&readfds); + FD_SET(mpd_fd, &readfds); + + if (select(mpd_fd + 1, &readfds, NULL, NULL, &timeout) > 0 && + FD_ISSET(mpd_fd, &readfds)) { + + enum mpd_idle events = mpd_recv_idle(server->mpd_conn, false); + server->mpd_idle_active = 0; + + if (mpd_connection_get_error(server->mpd_conn) == MPD_ERROR_SUCCESS) { + if (events & MPD_IDLE_PLAYER) { + update_current_song(server); + } + } + } + } +} + +void mpd_ws_cleanup(struct mpd_ws_server *server) { + syslog(LOG_INFO, "Shutting down"); + + if (server->mpd_conn) { + if (server->mpd_idle_active) { + mpd_send_noidle(server->mpd_conn); + mpd_response_finish(server->mpd_conn); + } + mpd_connection_free(server->mpd_conn); + } + + if (server->ws_context) { + lws_context_destroy(server->ws_context); + } + + while (server->clients) { + struct client_session *next = server->clients->next; + free(server->clients); + server->clients = next; + } + + closelog(); +} |
