mpdws.c (6728B)
1 #include "mpdws.h" 2 #include <signal.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <sys/select.h> 7 #include <syslog.h> 8 #include <time.h> 9 #include <unistd.h> 10 11 static struct mpd_ws_server *g_server = NULL; 12 13 static void signal_handler(int sig) { 14 (void)sig; 15 if (g_server) 16 g_server->running = 0; 17 } 18 19 static void broadcast_current_song(struct mpd_ws_server *server) { 20 struct client_session *client = server->clients; 21 size_t len = strlen(server->current_song); 22 23 while (client) { 24 unsigned char buf[LWS_PRE + MAX_MESSAGE_SIZE]; 25 memcpy(&buf[LWS_PRE], server->current_song, len); 26 lws_write(client->wsi, &buf[LWS_PRE], len, LWS_WRITE_TEXT); 27 client = client->next; 28 } 29 } 30 31 static int ws_callback(struct lws *wsi, enum lws_callback_reasons reason, 32 void *user, void *in, size_t len) { 33 (void)user; 34 (void)in; 35 (void)len; 36 37 struct mpd_ws_server *server = 38 (struct mpd_ws_server *)lws_context_user(lws_get_context(wsi)); 39 40 switch (reason) { 41 case LWS_CALLBACK_ESTABLISHED: { 42 struct client_session *client = malloc(sizeof(*client)); 43 if (client) { 44 client->wsi = wsi; 45 client->next = server->clients; 46 server->clients = client; 47 syslog(LOG_INFO, "Client connected"); 48 if (strlen(server->current_song) > 0) { 49 lws_callback_on_writable(wsi); 50 } 51 } 52 break; 53 } 54 case LWS_CALLBACK_CLOSED: { 55 struct client_session **current = &server->clients; 56 while (*current) { 57 if ((*current)->wsi == wsi) { 58 struct client_session *to_remove = *current; 59 *current = (*current)->next; 60 free(to_remove); 61 syslog(LOG_INFO, "Client disconnected"); 62 break; 63 } 64 current = &(*current)->next; 65 } 66 break; 67 } 68 case LWS_CALLBACK_SERVER_WRITEABLE: 69 if (strlen(server->current_song) > 0) { 70 unsigned char buf[LWS_PRE + MAX_MESSAGE_SIZE]; 71 size_t msg_len = strlen(server->current_song); 72 memcpy(&buf[LWS_PRE], server->current_song, msg_len); 73 lws_write(wsi, &buf[LWS_PRE], msg_len, LWS_WRITE_TEXT); 74 } 75 break; 76 default: 77 break; 78 } 79 return 0; 80 } 81 82 static struct lws_protocols protocols[] = { 83 {"mpd-protocol", ws_callback, 0, MAX_MESSAGE_SIZE, 0, NULL, 0}, 84 {NULL, NULL, 0, 0, 0, NULL, 0}}; 85 86 static int connect_mpd(struct mpd_ws_server *server) { 87 if (server->mpd_conn) { 88 if (server->mpd_idle_active) { 89 mpd_send_noidle(server->mpd_conn); 90 mpd_response_finish(server->mpd_conn); 91 } 92 mpd_connection_free(server->mpd_conn); 93 } 94 95 server->mpd_conn = mpd_connection_new(MPD_HOST, MPD_PORT, 0); 96 server->mpd_idle_active = 0; 97 98 if (mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { 99 syslog(LOG_ERR, "MPD connection failed: %s", 100 mpd_connection_get_error_message(server->mpd_conn)); 101 mpd_connection_free(server->mpd_conn); 102 server->mpd_conn = NULL; 103 return -1; 104 } 105 106 syslog(LOG_INFO, "Connected to MPD at %s:%d", MPD_HOST, MPD_PORT); 107 return 0; 108 } 109 110 static void update_current_song(struct mpd_ws_server *server) { 111 if (!server->mpd_conn || 112 mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { 113 return; 114 } 115 116 mpd_send_current_song(server->mpd_conn); 117 struct mpd_song *song = mpd_recv_song(server->mpd_conn); 118 119 if (mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { 120 mpd_response_finish(server->mpd_conn); 121 return; 122 } 123 124 char new_song[MAX_MESSAGE_SIZE]; 125 if (!song) { 126 snprintf(new_song, sizeof(new_song), "Now Playing: No song"); 127 } else { 128 const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); 129 const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); 130 131 snprintf(new_song, sizeof(new_song), "Now Playing: %s - %s", artist, title); 132 mpd_song_free(song); 133 } 134 135 // Only send song if it's changed 136 if (strcmp(new_song, server->current_song) != 0) { 137 strlcpy(server->current_song, new_song, sizeof(server->current_song)); 138 broadcast_current_song(server); 139 syslog(LOG_DEBUG, "Broadcasting: %s", server->current_song); 140 } 141 142 mpd_response_finish(server->mpd_conn); 143 } 144 145 int mpd_ws_init(struct mpd_ws_server *server) { 146 memset(server, 0, sizeof(*server)); 147 148 g_server = server; 149 signal(SIGINT, signal_handler); 150 signal(SIGTERM, signal_handler); 151 openlog("mpd_ws", LOG_PID | LOG_NDELAY, LOG_DAEMON); 152 153 struct lws_context_creation_info info = {0}; 154 info.port = WEBSOCKET_PORT; 155 info.protocols = protocols; 156 info.gid = info.uid = -1; 157 info.user = server; 158 159 server->ws_context = lws_create_context(&info); 160 if (!server->ws_context) { 161 syslog(LOG_ERR, "Failed to create WebSocket context"); 162 return -1; 163 } 164 165 syslog(LOG_INFO, "WebSocket server listening on port %d", WEBSOCKET_PORT); 166 connect_mpd(server); 167 server->running = 1; 168 return 0; 169 } 170 171 void mpd_ws_run(struct mpd_ws_server *server) { 172 time_t last_reconnect = 0; 173 174 while (server->running) { 175 lws_service(server->ws_context, 10); 176 177 // Handle MPD connection 178 if (!server->mpd_conn || 179 mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { 180 time_t now = time(NULL); 181 if (now - last_reconnect >= RECONNECT_INTERVAL_SEC) { 182 if (connect_mpd(server) == 0) { 183 update_current_song(server); 184 } 185 last_reconnect = now; 186 } 187 sleep(SELECT_TIMEOUT_SEC); 188 continue; 189 } 190 191 // Start idle if needed 192 if (!server->mpd_idle_active) { 193 if (mpd_send_idle_mask(server->mpd_conn, MPD_IDLE_PLAYER)) { 194 server->mpd_idle_active = 1; 195 } else { 196 connect_mpd(server); 197 continue; 198 } 199 } 200 201 // Check for MPD events 202 fd_set readfds; 203 int mpd_fd = mpd_connection_get_fd(server->mpd_conn); 204 struct timeval timeout = {0, SELECT_TIMEOUT_SEC}; 205 206 FD_ZERO(&readfds); 207 FD_SET(mpd_fd, &readfds); 208 209 if (select(mpd_fd + 1, &readfds, NULL, NULL, &timeout) > 0 && 210 FD_ISSET(mpd_fd, &readfds)) { 211 212 enum mpd_idle events = mpd_recv_idle(server->mpd_conn, false); 213 server->mpd_idle_active = 0; 214 215 if (mpd_connection_get_error(server->mpd_conn) == MPD_ERROR_SUCCESS) { 216 if (events & MPD_IDLE_PLAYER) { 217 update_current_song(server); 218 } 219 } 220 } 221 } 222 } 223 224 void mpd_ws_cleanup(struct mpd_ws_server *server) { 225 syslog(LOG_INFO, "Shutting down"); 226 227 if (server->mpd_conn) { 228 if (server->mpd_idle_active) { 229 mpd_send_noidle(server->mpd_conn); 230 mpd_response_finish(server->mpd_conn); 231 } 232 mpd_connection_free(server->mpd_conn); 233 } 234 235 if (server->ws_context) { 236 lws_context_destroy(server->ws_context); 237 } 238 239 while (server->clients) { 240 struct client_session *next = server->clients->next; 241 free(server->clients); 242 server->clients = next; 243 } 244 245 closelog(); 246 }