1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | #include "mplayer.h" |
18 | #include "mplayer_conf.h" |
19 | |
20 | |
21 | void (*mplayer_callback_playnext)(void) = NULL((void *)0); |
22 | void (*mplayer_callback_notice)(char *, ...) = NULL((void *)0); |
23 | void (*mplayer_callback_error)(char *, ...) = NULL((void *)0); |
24 | void (*mplayer_callback_fatal)(char *, ...) = NULL((void *)0); |
25 | |
26 | |
27 | |
28 | static struct { |
29 | |
30 | float position; |
31 | float volume; |
32 | bool_Bool playing; |
33 | bool_Bool paused; |
34 | |
35 | |
36 | pid_t pid; |
37 | int pipe_read; |
38 | int pipe_write; |
39 | const char *current_song; |
40 | } mplayer_state; |
41 | |
42 | bool_Bool restarting = false0; |
43 | |
44 | |
45 | void mplayer_volume_set(float); |
46 | void mplayer_volume_query(); |
47 | |
48 | static void |
49 | mplayer_send_cmd(const char *cmd) |
50 | { |
51 | write(mplayer_state.pipe_write, cmd, strlen(cmd)); |
52 | } |
53 | |
54 | void |
55 | mplayer_start() |
56 | { |
57 | int pwrite[2]; |
58 | int pread[2]; |
59 | int flags; |
60 | |
61 | if (!exe_in_path(MPLAYER_PATH)) |
| |
62 | errx(1, "it appears '%s' does not exist in your $PATH", MPLAYER_PATH); |
63 | |
64 | if (pipe(pwrite) == -1 || pipe(pread) == -1) |
65 | err(1, "%s: pipe() failed", __FUNCTION__); |
66 | |
67 | switch (mplayer_state.pid = fork()) { |
| 6 | | Control jumps to 'case 0:' at line 72 | |
|
68 | case -1: |
69 | err(1, "%s: fork() failed", __FUNCTION__); |
70 | break; |
71 | |
72 | case 0: |
73 | if (close(0) == -1 || close(1) == -1 || close(2) == -1) |
74 | err(1, "%s: child close()'s failed(1)", __FUNCTION__); |
75 | |
76 | if (close(pwrite[1]) == -1 || close(pread[0]) == -1) |
| 7 | | Function call argument is an uninitialized value |
|
77 | err(1, "%s: child close()'s failed(2)", __FUNCTION__); |
78 | |
79 | if (dup(pwrite[0]) == -1 || dup(pread[1]) == -1) |
80 | err(1, "%s: child dup()'s failed", __FUNCTION__); |
81 | |
82 | if (execvp(MPLAYER_PATH, MPLAYER_ARGS) == -1) |
83 | kill(getppid(), SIGCHLD20); |
84 | |
85 | exit(1); |
86 | break; |
87 | } |
88 | |
89 | |
90 | |
91 | |
92 | if (close(pwrite[0]) == -1 || close(pread[1]) == -1) |
93 | err(1, "%s: parent close()'s failed", __FUNCTION__); |
94 | |
95 | |
96 | mplayer_state.pipe_read = pread[0]; |
97 | mplayer_state.pipe_write = pwrite[1]; |
98 | |
99 | |
100 | if ((flags = fcntl(mplayer_state.pipe_read, F_GETFL3, 0)) == -1) |
101 | err(1, "%s: fcntl() failed to get current flags", __FUNCTION__); |
102 | |
103 | if (fcntl(mplayer_state.pipe_read, F_SETFL4, flags | O_NONBLOCK0x0004) == -1) |
104 | err(1, "%s: fcntl() failed to set pipe non-blocking", __FUNCTION__); |
105 | |
106 | if (!restarting) { |
107 | mplayer_state.playing = false0; |
108 | mplayer_state.paused = false0; |
109 | mplayer_state.volume = -1; |
110 | mplayer_state.position = 0; |
111 | mplayer_state.current_song = NULL((void *)0); |
112 | } |
113 | restarting = true1; |
114 | } |
115 | |
116 | void |
117 | mplayer_finish() |
118 | { |
119 | mplayer_send_cmd("\nquit\n"); |
120 | |
121 | close(mplayer_state.pipe_read); |
122 | close(mplayer_state.pipe_write); |
123 | |
124 | waitpid(mplayer_state.pid, NULL((void *)0), 0); |
125 | } |
126 | |
127 | void |
128 | mplayer_restart() |
129 | { |
130 | int status; |
131 | |
132 | close(mplayer_state.pipe_read); |
133 | close(mplayer_state.pipe_write); |
134 | wait(&status); |
135 | |
136 | restarting = true1; |
137 | mplayer_start(); |
| |
138 | |
139 | if (mplayer_state.playing && !mplayer_state.paused) { |
140 | int previous_position = mplayer_state.position; |
141 | mplayer_play(mplayer_state.current_song); |
142 | mplayer_seek(previous_position); |
143 | } |
144 | } |
145 | |
146 | void |
147 | mplayer_sigchld_message() |
148 | { |
149 | |
150 | mplayer_callback_fatal("%s is crashing too often. Possible causes are:\n\ |
151 | 1. %s is not in your $PATH\n\ |
152 | 2. The installed %s is older, and not supported by vitunes\n", |
153 | MPLAYER_PATH, MPLAYER_PATH, MPLAYER_PATH); |
154 | } |
155 | |
156 | void |
157 | mplayer_sigchld() |
158 | { |
159 | static time_t last_sigchld = -1; |
160 | |
161 | if (kill(mplayer_state.pid, 0) != 0) { |
| |
162 | if (time(0) - last_sigchld <= 1) { |
| |
163 | if (mplayer_callback_fatal != NULL((void *)0)) |
164 | mplayer_sigchld_message(); |
165 | } else { |
166 | mplayer_restart(); |
| 3 | | Calling 'mplayer_restart' | |
|
167 | if (mplayer_callback_error != NULL((void *)0)) |
168 | mplayer_callback_error("%s died. Restarting it.", MPLAYER_PATH); |
169 | } |
170 | } |
171 | |
172 | last_sigchld = time(0); |
173 | } |
174 | |
175 | void |
176 | mplayer_play(const char *file) |
177 | { |
178 | static const char *cmd_fmt = "\nloadfile \"%s\" 0\nget_property time_pos\n"; |
179 | char *cmd; |
180 | |
181 | if (asprintf(&cmd, cmd_fmt, file) == -1) |
182 | err(1, "%s: asprintf failed", __FUNCTION__); |
183 | |
184 | mplayer_send_cmd(cmd); |
185 | free(cmd); |
186 | |
187 | mplayer_state.position = 0; |
188 | mplayer_state.playing = true1; |
189 | mplayer_state.paused = false0; |
190 | mplayer_state.current_song = file; |
191 | |
192 | |
193 | if (mplayer_state.volume > -1) |
194 | mplayer_volume_set(mplayer_state.volume); |
195 | } |
196 | |
197 | void |
198 | mplayer_stop() |
199 | { |
200 | mplayer_send_cmd("\nstop\n"); |
201 | |
202 | mplayer_state.playing = false0; |
203 | mplayer_state.paused = false0; |
204 | } |
205 | |
206 | void |
207 | mplayer_pause() |
208 | { |
209 | if (!mplayer_state.playing) |
210 | return; |
211 | |
212 | mplayer_send_cmd("\npause\n"); |
213 | mplayer_state.paused = !mplayer_state.paused; |
214 | } |
215 | |
216 | void |
217 | mplayer_seek(int seconds) |
218 | { |
219 | static const char *cmd_fmt = "\nseek %i 0\nget_property time_pos\n"; |
220 | char *cmd; |
221 | |
222 | if (!mplayer_state.playing) |
223 | return; |
224 | |
225 | if (asprintf(&cmd, cmd_fmt, seconds) == -1) |
226 | err(1, "%s: asprintf failed", __FUNCTION__); |
227 | |
228 | mplayer_send_cmd(cmd); |
229 | free(cmd); |
230 | |
231 | if (mplayer_state.paused) |
232 | mplayer_state.paused = false0; |
233 | } |
234 | |
235 | void |
236 | mplayer_volume_step(float percent) |
237 | { |
238 | static const char *cmd_fmt = "\npausing_keep volume %f\n"; |
239 | char *cmd; |
240 | |
241 | if (!mplayer_state.playing) |
242 | return; |
243 | |
244 | |
245 | |
246 | |
247 | if (mplayer_state.volume > -1) { |
248 | percent += mplayer_state.volume; |
249 | mplayer_volume_set(percent); |
250 | return; |
251 | } |
252 | |
253 | if (asprintf(&cmd, cmd_fmt, percent) == -1) |
254 | err(1, "%s: asprintf failed", __FUNCTION__); |
255 | |
256 | mplayer_send_cmd(cmd); |
257 | free(cmd); |
258 | |
259 | mplayer_volume_query(); |
260 | } |
261 | |
262 | void |
263 | mplayer_volume_set(float percent) |
264 | { |
265 | static const char *cmd_fmt = "\npausing_keep set_property volume %f\n"; |
266 | char *cmd; |
267 | |
268 | if (!mplayer_state.playing) |
269 | return; |
270 | |
271 | if (percent > 100) percent = 100; |
272 | if (percent < 0) percent = 0; |
273 | |
274 | if (asprintf(&cmd, cmd_fmt, percent) == -1) |
275 | err(1, "%s: asprintf failed", __FUNCTION__); |
276 | |
277 | |
278 | mplayer_send_cmd(cmd); |
279 | free(cmd); |
280 | |
281 | mplayer_volume_query(); |
282 | } |
283 | |
284 | void |
285 | mplayer_volume_query() |
286 | { |
287 | static const char *cmd = "\npausing_keep get_property volume\n"; |
288 | |
289 | if (!mplayer_state.playing) |
290 | return; |
291 | |
292 | mplayer_send_cmd(cmd); |
293 | } |
294 | |
295 | |
296 | float mplayer_get_position() { return mplayer_state.position; } |
297 | float mplayer_get_volume() { return mplayer_state.volume; } |
298 | bool_Bool mplayer_is_playing() { return mplayer_state.playing; } |
299 | bool_Bool mplayer_is_paused() { return mplayer_state.paused; } |
300 | |
301 | |
302 | void |
303 | mplayer_set_callback_playnext(void (*f)(void)) |
304 | { |
305 | mplayer_callback_playnext = f; |
306 | } |
307 | |
308 | void |
309 | mplayer_set_callback_notice(void (*f)(char *, ...)) |
310 | { |
311 | mplayer_callback_notice = f; |
312 | } |
313 | |
314 | void |
315 | mplayer_set_callback_error(void (*f)(char *, ...)) |
316 | { |
317 | mplayer_callback_error = f; |
318 | } |
319 | |
320 | void |
321 | mplayer_set_callback_fatal(void (*f)(char *, ...)) |
322 | { |
323 | mplayer_callback_fatal = f; |
324 | } |
325 | |
326 | |
327 | |
328 | |
329 | |
330 | |
331 | |
332 | |
333 | |
334 | |
335 | |
336 | |
337 | |
338 | void |
339 | mplayer_monitor() |
340 | { |
341 | static const char *query_cmd = "\nget_property time_pos\n"; |
342 | static const char *answer_fail = "ANS_ERROR=PROPERTY_UNAVAILABLE"; |
343 | static const char *answer_good = "ANS_time_pos"; |
344 | static char response[1000]; |
345 | char *s; |
346 | int nbytes; |
347 | |
348 | |
349 | if (!mplayer_state.playing || mplayer_state.paused) |
350 | return; |
351 | |
352 | |
353 | bzero(response, sizeof(response)); |
354 | nbytes = read(mplayer_state.pipe_read, &response, sizeof(response)); |
355 | |
356 | if (nbytes == -1 && errno(*__errno()) == EAGAIN35) |
357 | return; |
358 | |
359 | response[nbytes + 1] = '\0'; |
360 | |
361 | |
362 | if (strstr(response, answer_fail) != NULL((void *)0)) { |
363 | if (mplayer_callback_playnext != NULL((void *)0)) mplayer_callback_playnext(); |
364 | return; |
365 | } |
366 | |
367 | |
368 | if ((s = strstr(response, answer_good)) != NULL((void *)0)) { |
369 | while (strstr(s + 1, answer_good) != NULL((void *)0)) |
370 | s = strstr(s + 1, answer_good); |
371 | |
372 | if (sscanf(s, "ANS_time_pos=%20f", &mplayer_state.position) != 1) |
373 | errx(1, "player_monitor: player child is misbehaving."); |
374 | } |
375 | |
376 | mplayer_send_cmd(query_cmd); |
377 | |
378 | |
379 | static const char *volume_good = "ANS_volume"; |
380 | if ((s = strstr(response, volume_good)) != NULL((void *)0)) { |
381 | while (strstr(s + 1, volume_good) != NULL((void *)0)) |
382 | s = strstr(s + 1, volume_good); |
383 | |
384 | if (sscanf(s, "ANS_volume=%20f", &mplayer_state.volume) != 1) |
385 | errx(1, "player_monitor: player child is misbehaving."); |
386 | } |
387 | } |
388 | |