A terminal-based radio player
git clone git://soophie.de/rx
blob: 10170ec988fea18e3e5cabb4b4e8714089479b55 (6956 bytes)[doc][blame][raw]
1
#include <stdio.h>2
#include <stdlib.h>3
#include <string.h>4
#include <dlfcn.h>5
#include <math.h>6
#include <mpv/client.h>7
#define LIB_TERM_IMPL8
#include <libterm.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, "rx.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
term_enable_raw_mode();81
term_write("\x1b[?25l");82
}84
void rx_update(rx_t *self) {85
int key = term_poll_key(100);86
switch (key) {87
case 'q': {88
self->quit = true;89
break;90
}91
case 'j': {92
if ((size_t) self->idx < self->stations_len - 1) {93
self->idx++;94
}95
break;96
}97
case 'k': {98
if (self->idx > 0) {99
self->idx--;100
}101
break;102
}103
case KEY_ENTER: {104
const rx_station_t *station = &self->stations[self->idx];105
char *resolver = station->resolver;106
char *url = NULL;107
if (resolver != NULL) {108
char *resolver_path = NULL;109
char *home_path = getenv("HOME");110
if (home_path != NULL) {111
str_appendf(&resolver_path, "%s/.config/rx/mods/", home_path);112
}113
str_append(&resolver_path, resolver);114
void *handle = dlopen(resolver_path, RTLD_LAZY);115
free(resolver_path);116
if (handle == NULL) {117
fprintf(stderr, "%s\n", dlerror());118
break;119
}120
char *(*resolve_fn)(void) = (char *(*)(void)) dlsym(handle, "rx_resolve");121
const char *err = dlerror();122
if (err != 0) {123
fprintf(stderr, "%s\n", err);124
break;125
}126
url = resolve_fn();127
dlclose(handle);128
}129
else {130
size_t len = strlen(station->url);131
url = malloc(sizeof(char) * (len + 1));132
memcpy(url, station->url, len);133
url[len] = '\0';134
}135
if (url != NULL) {136
const char *cmd[] = { "loadfile", url, NULL };137
mpv_command(self->ctx, cmd);138
free(url);139
}140
self->curr = station;141
self->title = NULL;142
self->loading = 1;143
self->error = 0;144
break;145
}146
case KEY_BACKSPACE: {147
const char *cmd[] = { "stop", NULL };148
mpv_command(self->ctx, cmd);149
self->curr = NULL;150
self->loading = 0;151
self->error = 0;152
break;153
}154
default:155
break;156
}157
if (self->count == RX_UPDATE_COUNT) {158
if (self->title != NULL) {159
mpv_free(self->title);160
self->title = NULL;161
}162
self->title = mpv_get_property_string(self->ctx, "metadata/icy-title");163
self->count = 0;164
}165
self->count++;166
mpv_get_property(self->ctx, "core-idle", MPV_FORMAT_FLAG, &self->loading);167
mpv_event *event = mpv_wait_event(self->ctx, 0);168
if (event->event_id == MPV_EVENT_END_FILE) {169
mpv_event_end_file *end_file = (mpv_event_end_file *) event->data;170
if (end_file->reason == MPV_END_FILE_REASON_ERROR) {171
self->error = 1;172
const char* err = mpv_error_string(end_file->error);173
size_t len = strlen(err);174
memcpy(self->message, err, len);175
self->message[len] = '\0';176
}177
}178
}180
void rx_draw(rx_t *self) {181
term_write("\x1b[2J");182
term_write("\x1b[H");183
term_write("\r\n");184
int rows = 0;185
int cols = 0;186
term_read_window_size(&rows, &cols);187
size_t len = rows - 5;188
for (size_t i = 0; i < len; i++) {189
if (i + self->idx >= self->stations_len) {190
term_write("\r\n");191
continue;192
}193
const rx_station_t *station = &self->stations[i + self->idx];194
if (i + self->idx == (size_t) self->idx) {195
term_write("> ");196
}197
else {198
term_write(" ");199
}200
int pad = 3; // floor(log10(self->stations_len) + 1);201
term_writef("%0*d ", pad, i + self->idx + 1);202
term_writef("[%s] %s", station->uid, station->name);203
if (station->addr != NULL) {204
term_writef(" (%s)", station->addr);205
}206
term_write("\r\n");207
}208
term_write("\r\n");209
const rx_station_t *station = self->curr;210
if (station != NULL) {211
term_writef("[%s] %s\r\n", station->uid, station->name);212
if (self->error) {213
term_writef("error: %s\r\n", self->message);214
}215
else {216
if (self->loading) {217
term_write("loading...\r\n");218
}219
else {220
if (self->title != NULL && strlen(self->title) > 0) {221
term_writef("title: %s\r\n", self->title);222
}223
else {224
term_write("title: ???\r\n");225
}226
}227
}228
}229
else {230
term_write("[???] no station\r\n");231
term_write("...\r\n");232
}233
term_flush();234
}236
void rx_free(rx_t *self) {237
mpv_terminate_destroy(self->ctx);238
term_write("\x1b[2J");239
term_write("\x1b[H");240
term_write("\x1b[?25h");241
term_flush();242
term_disable_raw_mode();243
for (size_t i = 0; i < self->stations_len; i++) {244
rx_station_t *station = &self->stations[i];245
if (station->addr != NULL) {246
free(station->addr);247
}248
if (station->url != NULL) {249
free(station->url);250
}251
if (station->resolver != NULL) {252
free(station->resolver);253
}254
}255
free(self->stations);256
self->stations_len = 0;257
}