player_mplay.c (6808B)
1#include "player.h" 2 3#include "tui.h" 4#include "data.h" 5#include "list.h" 6#include "util.h" 7#include "log.h" 8 9#include "mplay.h" 10 11#include <sys/wait.h> 12#include <sys/mman.h> 13#include <unistd.h> 14#include <errno.h> 15#include <signal.h> 16#include <stdbool.h> 17#include <stdio.h> 18#include <stdlib.h> 19#include <string.h> 20 21#define MPLAY_STATUS(line) do { \ 22 free(user_status); \ 23 user_status = aprintf("Player: %s", \ 24 line ? line : "no response"); \ 25 user_status_uptime = 20; \ 26 } while (0) 27 28struct mplay_player { 29 FILE *stdin; 30 FILE *stdout; 31 pid_t pid; 32 uint64_t update_ms; 33}; 34 35struct player player; 36struct mplay_player mplay; 37 38static void sigpipe_handler(int sig); 39 40static void mplay_kill(void); 41static bool mplay_run(struct track *track); 42static char *mplay_readline(void); 43static char *mplay_info_arg(char *line, const char *prefix); 44 45void 46sigpipe_handler(int sig) 47{ 48 mplay_kill(); 49} 50 51bool 52mplay_run(struct track *track) 53{ 54 int output[2]; 55 int input[2]; 56 char *path; 57 char *line; 58 59 ASSERT(!player.loaded); 60 61 if (pipe(input) == -1) 62 ERROR(SYSTEM, "pipe"); 63 64 if (pipe(output) == -1) 65 ERROR(SYSTEM, "pipe"); 66 67 mplay.pid = fork(); 68 if (mplay.pid < 0) ERROR(SYSTEM, "fork"); 69 70 if (mplay.pid != 0) { 71 close(output[1]); 72 mplay.stdout = fdopen(output[0], "r"); 73 if (!mplay.stdout) ERROR(SYSTEM, "fdopen"); 74 setvbuf(mplay.stdout, NULL, _IONBF, 0); 75 76 close(input[0]); 77 mplay.stdin = fdopen(input[1], "w"); 78 if (!mplay.stdin) ERROR(SYSTEM, "fdopen"); 79 setvbuf(mplay.stdin, NULL, _IONBF, 0); 80 } else { 81 dup2(input[0], 0); 82 dup2(output[1], 1); 83 dup2(output[1], 2); 84 close(input[0]); 85 close(input[1]); 86 close(output[0]); 87 close(output[1]); 88 path = aprintf("%s/%s/%s", datadir, 89 track->tag->name, track->name); 90 execlp("mplay", "mplay", path, NULL); 91 abort(); 92 } 93 94 player.loaded = true; 95 96 line = mplay_readline(); 97 if (!line || !mplay_info_arg(line, MPLAY_INFO_STR_READY)) { 98 mplay_kill(); 99 MPLAY_STATUS(line); 100 return false; 101 } 102 103 return true; 104} 105 106void 107mplay_kill(void) 108{ 109 if (!player.loaded) return; 110 kill(mplay.pid, SIGKILL); 111 waitpid(mplay.pid, NULL, 0); 112 player.loaded = false; 113} 114 115char * 116mplay_readline(void) 117{ 118 static char linebuf[256]; 119 char *tok; 120 121 /* TODO: add timeout */ 122 if (!mplay.stdout || !fgets(linebuf, sizeof(linebuf), mplay.stdout)) { 123 mplay_kill(); /* dont clear track yet */ 124 return NULL; 125 } 126 127 tok = strchr(linebuf, '\n'); 128 if (tok) *tok = '\0'; 129 130 return linebuf; 131} 132 133char * 134mplay_info_arg(char *line, const char *prefix) 135{ 136 if (strncmp(line, "mplay!", 6)) 137 return NULL; 138 line += 6; 139 140 size_t prefixlen = strlen(prefix); 141 if (strncmp(line, prefix, prefixlen)) 142 return NULL; 143 line += prefixlen; 144 145 if (line[0] == ':') 146 return line+1; 147 else if (!line[0]) 148 return line; 149 return NULL; 150} 151 152void 153player_init(void) 154{ 155 list_init(&player.playlist); 156 list_init(&player.history); 157 list_init(&player.queue); 158 159 player.track = NULL; 160 player.track_name = NULL; 161 162 player.loaded = false; 163 player.autoplay = true; 164 player.shuffle = true; 165 166 player.state = PLAYER_STATE_PAUSED; 167 player.volume = 50; 168 169 player.time_pos = 0; 170 player.time_end = 0; 171 172 signal(SIGPIPE, sigpipe_handler); 173} 174 175void 176player_deinit(void) 177{ 178 list_clear(&player.playlist); 179 list_clear(&player.queue); 180 list_clear(&player.history); 181 182 free(player.track_name); 183 184 mplay_kill(); 185} 186 187void 188player_update(void) 189{ 190 bool queue_empty; 191 char *tok, *line; 192 char *arg; 193 194 if (!player.loaded) { 195 queue_empty = list_empty(&player.queue); 196 if (player.track && player.autoplay || !queue_empty) { 197 player_next(); 198 } else if (player.track) { 199 player_clear_track(); 200 } 201 } 202 203 if (!player.loaded) return; 204 205 if (current_ms() >= mplay.update_ms + 330) { 206 mplay.update_ms = current_ms(); 207 fputc(MPLAY_ACTION_KEY_STATUS, mplay.stdin); 208 line = mplay_readline(); 209 if (!line || !(arg = mplay_info_arg(line, MPLAY_INFO_STR_STATUS))) { 210 if (line && (arg = mplay_info_arg(line, MPLAY_INFO_STR_EXIT))) { 211 player_next(); 212 return; 213 } 214 mplay_kill(); 215 MPLAY_STATUS(line); 216 return; 217 } 218 219 tok = arg; 220 while (1) { 221 if (!strncmp(tok, "volume=", 7)) { 222 player.volume = atoi(tok + 7); 223 } else if (!strncmp(tok, "pause=", 6)) { 224 player.state = atoi(tok + 6) 225 ? PLAYER_STATE_PAUSED : PLAYER_STATE_PLAYING; 226 } else if (!strncmp(tok, "pos=", 4)) { 227 player.time_pos = atoi(tok + 4); 228 } else if (!strncmp(tok, "end=", 4)) { 229 player.time_end = atoi(tok + 4); 230 } 231 tok = strchr(tok, ','); 232 if (!tok) break; 233 tok += 1; 234 } 235 } 236} 237 238int 239player_play_track(struct track *track, bool new) 240{ 241 ASSERT(track != NULL); 242 243 player_clear_track(); 244 player.track = track; 245 246 if (!mplay_run(track)) 247 return PLAYER_ERR; 248 249 /* new invocations are removed from history */ 250 if (new) list_link_pop(&track->link_hs); 251 252 player.time_pos = 0; 253 player.time_end = 0; 254 255 return PLAYER_OK; 256} 257 258int 259player_clear_track(void) 260{ 261 if (player.track) 262 player_add_history(player.track); 263 264 player.track = NULL; 265 mplay_kill(); 266 267 return PLAYER_OK; 268} 269 270int 271player_toggle_pause(void) 272{ 273 char *line, *arg; 274 275 if (!player.loaded) return PLAYER_ERR; 276 277 fputc(MPLAY_ACTION_KEY_PAUSE, mplay.stdin); 278 line = mplay_readline(); 279 if (!line || !(arg = mplay_info_arg(line, MPLAY_INFO_STR_PAUSE))) { 280 mplay_kill(); 281 MPLAY_STATUS(line); 282 return PLAYER_ERR; 283 } 284 285 if (atoi(arg)) 286 player.state = PLAYER_STATE_PAUSED; 287 else 288 player.state = PLAYER_STATE_PLAYING; 289 290 return PLAYER_OK; 291} 292 293int 294player_pause(void) 295{ 296 if (!player.loaded) return PLAYER_ERR; 297 298 if (player.state != PLAYER_STATE_PAUSED) 299 player_toggle_pause(); 300 301 return PLAYER_OK; 302} 303 304int 305player_resume(void) 306{ 307 if (!player.loaded) return PLAYER_ERR; 308 309 if (player.state != PLAYER_STATE_PLAYING) 310 player_toggle_pause(); 311 312 return PLAYER_OK; 313} 314 315int 316player_play(void) 317{ 318 return PLAYER_OK; 319} 320 321int 322player_stop(void) 323{ 324 if (!player.loaded) return PLAYER_ERR; 325 326 player_clear_track(); 327 328 return PLAYER_OK; 329} 330 331int 332player_seek(int sec) 333{ 334 char *line, *arg; 335 336 if (!player.loaded) return PLAYER_ERR; 337 338 putc(MPLAY_ACTION_KEY_SEEK, mplay.stdin); 339 fprintf(mplay.stdin, "%i\n", sec); 340 line = mplay_readline(); 341 if (!line || !(arg = mplay_info_arg(line, MPLAY_INFO_STR_SEEK))) { 342 if (line && (arg = mplay_info_arg(line, MPLAY_INFO_STR_EXIT))) { 343 return player_next(); 344 } 345 mplay_kill(); 346 MPLAY_STATUS(line); 347 return PLAYER_ERR; 348 } 349 350 player.time_pos = atoi(arg); 351 player.time_end = MAX(player.time_pos, player.time_end); 352 353 return PLAYER_OK; 354} 355 356int 357player_set_volume(unsigned int vol) 358{ 359 char *line, *arg; 360 361 if (!player.loaded) return PLAYER_ERR; 362 363 if (player.volume == -1) { 364 PLAYER_STATUS("volume control not supported"); 365 return PLAYER_ERR; 366 } 367 368 putc(MPLAY_ACTION_KEY_VOLUME, mplay.stdin); 369 fprintf(mplay.stdin, "%i\n", vol); 370 line = mplay_readline(); 371 if (!line || !(arg = mplay_info_arg(line, MPLAY_INFO_STR_VOLUME))) { 372 mplay_kill(); 373 MPLAY_STATUS(line); 374 return PLAYER_ERR; 375 } 376 377 player.volume = atoi(arg); 378 379 return PLAYER_OK; 380} 381