A terminal-based radio player
git clone git://soophie.de/rx
blob: 155ba2dd4c9693e1068133142f3baa4dd3fe1c08 (7368 bytes)[doc][blame][raw]
1
#include <stdio.h>2
#include <stdlib.h>3
#include <string.h>4
#include <dlfcn.h>5
#include <mpv/client.h>6
#define LIB_TERM_IMPL7
#define LIB_TERM_POSIX8
#include <so/term.h>9
#define LIB_STR_IMPL10
#include <libstr.h>12
#include "../include/rx.h"13
#define INI_IMPL14
#include "../include/ini.h"16
static int rx_ini_load(void *user, const char *section, const char *name, const char *value) {17
rx_t *self = (rx_t *) user;18
if (strcmp(section, "stations") == 0) {19
if (strcmp(name, "uid") == 0) {20
self->stations = realloc(self->stations, sizeof(rx_station_t) * (self->stations_len + 1));21
self->stations[self->stations_len] = (rx_station_t) {22
.addr = NULL,23
.url = NULL,24
.resolver = NULL,25
};26
self->stations_len++;27
}28
rx_station_t *station = &self->stations[self->stations_len - 1];29
if (strcmp(name, "uid") == 0) {30
size_t len = strlen(value);31
memcpy(station->uid, value, len);32
station->uid[len] = '\0';33
}34
if (strcmp(name, "name") == 0) {35
size_t len = strlen(value);36
memcpy(station->name, value, len);37
station->name[len] = '\0';38
}39
if (strcmp(name, "addr") == 0) {40
size_t len = strlen(value);41
station->addr = malloc(sizeof(char) * (len + 1));42
memcpy(station->addr, value, len);43
station->addr[len] = '\0';44
}45
if (strcmp(name, "url") == 0) {46
size_t len = strlen(value);47
station->url = malloc(sizeof(char) * (len + 1));48
memcpy(station->url, value, len);49
station->url[len] = '\0';50
}51
if (strcmp(name, "resolver") == 0) {52
size_t len = strlen(value);53
station->resolver = malloc(sizeof(char) * (len + 1));54
memcpy(station->resolver, value, len);55
station->resolver[len] = '\0';56
}57
}58
return 1;59
}61
void rx_init(rx_t *self) {62
self->stations = NULL;63
self->stations_len = 0;64
char *config_path = NULL;65
char *home_path = getenv("HOME");66
if (home_path != NULL) {67
str_appendf(&config_path, "%s/.config/rx/", home_path);68
}69
str_append(&config_path, "lst.ini");70
ini_parse(config_path, rx_ini_load, self);71
free(config_path);72
self->curr = NULL;73
self->ctx = mpv_create();74
mpv_initialize(self->ctx);75
self->idx = 0;76
self->count = 0;77
self->title = NULL;78
self->loading = 0;79
self->error = 0;80
self->term = term_init();81
term_rawmode_enable(&self->term);82
term_write(&self->term, "\x1b[?25l");83
}85
void rx_update(rx_t *self) {86
int key = term_poll_key(&self->term, 100);87
switch (key) {88
case 'q': {89
self->quit = true;90
break;91
}92
case 'j': {93
if ((size_t) self->idx < self->stations_len - 1) {94
self->idx++;95
}96
break;97
}98
case 'k': {99
if (self->idx > 0) {100
self->idx--;101
}102
break;103
}104
case ' ': {105
const rx_station_t *station = &self->stations[self->idx];106
char *resolver = station->resolver;107
char *url = NULL;108
if (resolver != NULL) {109
char *resolver_path = NULL;110
char *home_path = getenv("HOME");111
if (home_path != NULL) {112
str_appendf(&resolver_path, "%s/.config/rx/mods/", home_path);113
}114
str_append(&resolver_path, resolver);115
void *handle = dlopen(resolver_path, RTLD_LAZY);116
free(resolver_path);117
if (handle == NULL) {118
fprintf(stderr, "%s\n", dlerror());119
break;120
}121
char *(*resolve_fn)(void) = (char *(*)(void)) dlsym(handle, "rx_resolve");122
const char *err = dlerror();123
if (err != 0) {124
fprintf(stderr, "%s\n", err);125
break;126
}127
url = resolve_fn();128
dlclose(handle);129
}130
else {131
size_t len = strlen(station->url);132
url = malloc(sizeof(char) * (len + 1));133
memcpy(url, station->url, len);134
url[len] = '\0';135
}136
if (url != NULL) {137
const char *cmd[] = { "loadfile", url, NULL };138
mpv_command(self->ctx, cmd);139
free(url);140
}141
self->curr = station;142
self->title = NULL;143
self->loading = 1;144
self->error = 0;145
break;146
}147
case KEY_BACKSPACE: {148
const char *cmd[] = { "stop", NULL };149
mpv_command(self->ctx, cmd);150
self->curr = NULL;151
self->loading = 0;152
self->error = 0;153
break;154
}155
default:156
break;157
}158
if (self->count == RX_UPDATE_COUNT) {159
if (self->title != NULL) {160
mpv_free(self->title);161
self->title = NULL;162
}163
self->title = mpv_get_property_string(self->ctx, "metadata/icy-title");164
self->count = 0;165
}166
self->count++;167
mpv_get_property(self->ctx, "core-idle", MPV_FORMAT_FLAG, &self->loading);168
mpv_event *event = mpv_wait_event(self->ctx, 0);169
if (event->event_id == MPV_EVENT_END_FILE) {170
mpv_event_end_file *end_file = (mpv_event_end_file *) event->data;171
if (end_file->reason == MPV_END_FILE_REASON_ERROR) {172
self->error = 1;173
const char* err = mpv_error_string(end_file->error);174
size_t len = strlen(err);175
memcpy(self->message, err, len);176
self->message[len] = '\0';177
}178
}179
}181
void rx_draw(rx_t *self) {182
term_write(&self->term, "\x1b[2J");183
term_write(&self->term, "\x1b[H");184
term_write(&self->term, "\r\n");185
size_t rows = 0;186
size_t cols = 0;187
term_read_window(&self->term, &rows, &cols);188
size_t len = rows - 5;189
for (size_t i = 0; i < len; i++) {190
if (i + self->idx >= self->stations_len) {191
term_write(&self->term, "\r\n");192
continue;193
}194
const rx_station_t *station = &self->stations[i + self->idx];195
if (i + self->idx == (size_t) self->idx) {196
term_write(&self->term, "> ");197
}198
else {199
term_write(&self->term, " ");200
}201
int pad = 3; // floor(log10(self->stations_len) + 1);202
term_writef(&self->term, "%0*d ", pad, i + self->idx + 1);203
term_writef(&self->term, "[%s] %s", station->uid, station->name);204
if (station->addr != NULL) {205
term_writef(&self->term, " (%s)", station->addr);206
}207
term_write(&self->term, "\r\n");208
}209
term_write(&self->term, "\r\n");210
const rx_station_t *station = self->curr;211
if (station != NULL) {212
term_writef(&self->term, "[%s] %s\r\n", station->uid, station->name);213
if (self->error) {214
term_writef(&self->term, "error: %s\r\n", self->message);215
}216
else {217
if (self->loading) {218
term_write(&self->term, "loading...\r\n");219
}220
else {221
if (self->title != NULL && strlen(self->title) > 0) {222
term_writef(&self->term, "title: %s\r\n", self->title);223
}224
else {225
term_write(&self->term, "title: ???\r\n");226
}227
}228
}229
}230
else {231
term_write(&self->term, "[???] no station\r\n");232
term_write(&self->term, "...\r\n");233
}234
term_flush(&self->term);235
}237
void rx_free(rx_t *self) {238
mpv_terminate_destroy(self->ctx);239
term_write(&self->term, "\x1b[2J");240
term_write(&self->term, "\x1b[H");241
term_write(&self->term, "\x1b[?25h");242
term_flush(&self->term);243
term_rawmode_disable(&self->term);244
term_cleanup(&self->term);245
for (size_t i = 0; i < self->stations_len; i++) {246
rx_station_t *station = &self->stations[i];247
if (station->addr != NULL) {248
free(station->addr);249
}250
if (station->url != NULL) {251
free(station->url);252
}253
if (station->resolver != NULL) {254
free(station->resolver);255
}256
}257
free(self->stations);258
self->stations_len = 0;259
}