so://phie

rx

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_IMPL

8#include <libterm.h>

9#define LIB_STR_IMPL

10#include <libstr.h>

11

12#include "../include/rx.h"

13#define INI_IMPL

14#include "../include/ini.h"

15

16static 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}

60

61void 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}

83

84void 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}

179

180void 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}

235

236void 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}