Fix buffer overflow when completing variable value
[cmus.git] / player.c
blob74f5b2e42ff3a1cb9c72a7eca111d400a8ffb587
1 /*
2 * Copyright 2004-2006 Timo Hirvonen
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include <player.h>
21 #include <buffer.h>
22 #include <input.h>
23 #include <output.h>
24 #include <sf.h>
25 #include <utils.h>
26 #include <xmalloc.h>
27 #include <debug.h>
28 #include <compiler.h>
30 #include <stdlib.h>
31 #include <pthread.h>
32 #include <errno.h>
33 #include <string.h>
34 #include <sys/time.h>
35 #include <stdarg.h>
37 enum producer_status {
38 PS_UNLOADED,
39 PS_STOPPED,
40 PS_PLAYING,
41 PS_PAUSED
44 enum consumer_status {
45 CS_STOPPED,
46 CS_PLAYING,
47 CS_PAUSED
50 struct player_info player_info = {
51 .mutex = CMUS_MUTEX_INITIALIZER,
52 .filename = { 0, },
53 .metadata = { 0, },
54 .status = PLAYER_STATUS_STOPPED,
55 .pos = 0,
56 .vol_left = 0,
57 .vol_right = 0,
58 .buffer_fill = 0,
59 .buffer_size = 0,
60 .error_msg = NULL,
61 .file_changed = 0,
62 .metadata_changed = 0,
63 .status_changed = 0,
64 .position_changed = 0,
65 .buffer_fill_changed = 0,
66 .vol_changed = 0,
69 /* continue playing after track is finished? */
70 int player_cont = 1;
72 static const struct player_callbacks *player_cbs = NULL;
74 static sample_format_t buffer_sf;
76 static pthread_t producer_thread;
77 static pthread_mutex_t producer_mutex = CMUS_MUTEX_INITIALIZER;
78 static int producer_running = 1;
79 static enum producer_status producer_status = PS_UNLOADED;
80 static struct input_plugin *ip = NULL;
82 static pthread_t consumer_thread;
83 static pthread_mutex_t consumer_mutex = CMUS_MUTEX_INITIALIZER;
84 static int consumer_running = 1;
85 static enum consumer_status consumer_status = CS_STOPPED;
86 static int consumer_pos = 0;
88 /* locking {{{ */
90 #define producer_lock() cmus_mutex_lock(&producer_mutex)
91 #define producer_unlock() cmus_mutex_unlock(&producer_mutex)
93 #define consumer_lock() cmus_mutex_lock(&consumer_mutex)
94 #define consumer_unlock() cmus_mutex_unlock(&consumer_mutex)
96 #define player_lock() \
97 do { \
98 consumer_lock(); \
99 producer_lock(); \
100 } while (0)
102 #define player_unlock() \
103 do { \
104 producer_unlock(); \
105 consumer_unlock(); \
106 } while (0)
108 /* locking }}} */
110 static void reset_buffer(void)
112 buffer_reset();
113 consumer_pos = 0;
116 static void set_buffer_sf(sample_format_t sf)
118 buffer_sf = sf;
120 /* ip_read converts samples to this format */
121 if (sf_get_channels(buffer_sf) <= 2 && sf_get_bits(buffer_sf) <= 16) {
122 buffer_sf &= SF_RATE_MASK;
123 buffer_sf |= sf_channels(2) | sf_bits(16) | sf_signed(1);
127 static inline int buffer_second_size(void)
129 return sf_get_second_size(buffer_sf);
132 static inline int get_next(char **filename)
134 return player_cbs->get_next(filename);
137 /* updating player status {{{ */
139 static inline void file_changed(void)
141 player_info_lock();
142 if (producer_status == PS_UNLOADED) {
143 player_info.filename[0] = 0;
144 } else {
145 strncpy(player_info.filename, ip_get_filename(ip), sizeof(player_info.filename));
146 player_info.filename[sizeof(player_info.filename) - 1] = 0;
148 d_print("%s\n", player_info.filename);
149 player_info.metadata[0] = 0;
150 player_info.file_changed = 1;
151 player_info_unlock();
154 static inline void metadata_changed(void)
156 player_info_lock();
157 d_print("metadata changed: %s\n", ip_get_metadata(ip));
158 memcpy(player_info.metadata, ip_get_metadata(ip), 255 * 16 + 1);
159 player_info.metadata_changed = 1;
160 player_info_unlock();
163 static inline void volume_update(int left, int right)
165 if (player_info.vol_left == left && player_info.vol_right == right)
166 return;
168 player_info_lock();
169 player_info.vol_left = left;
170 player_info.vol_right = right;
171 player_info.vol_changed = 1;
172 player_info_unlock();
175 static void player_error(const char *msg)
177 player_info_lock();
178 player_info.status = consumer_status;
179 player_info.pos = 0;
180 player_info.buffer_fill = buffer_get_filled_chunks();
181 player_info.buffer_size = buffer_nr_chunks;
182 player_info.status_changed = 1;
184 free(player_info.error_msg);
185 player_info.error_msg = xstrdup(msg);
186 player_info_unlock();
188 d_print("ERROR: '%s'\n", msg);
191 static void __FORMAT(2, 3) player_ip_error(int rc, const char *format, ...)
193 char buffer[1024];
194 va_list ap;
195 char *msg;
196 int save = errno;
198 va_start(ap, format);
199 vsnprintf(buffer, sizeof(buffer), format, ap);
200 va_end(ap);
202 errno = save;
203 msg = ip_get_error_msg(ip, rc, buffer);
204 player_error(msg);
205 free(msg);
208 static void __FORMAT(2, 3) player_op_error(int rc, const char *format, ...)
210 char buffer[1024];
211 va_list ap;
212 char *msg;
213 int save = errno;
215 va_start(ap, format);
216 vsnprintf(buffer, sizeof(buffer), format, ap);
217 va_end(ap);
219 errno = save;
220 msg = op_get_error_msg(rc, buffer);
221 player_error(msg);
222 free(msg);
225 /* FIXME: don't poll */
226 static void mixer_check(void)
228 static struct timeval old_st = { 0L, 0L };
229 struct timeval st;
230 int l, r;
232 gettimeofday(&st, NULL);
233 if (st.tv_sec == old_st.tv_sec) {
234 unsigned long usecs = st.tv_sec - old_st.tv_sec;
236 if (usecs < 300e6)
237 return;
239 old_st = st;
240 if (op_get_volume(&l, &r) == 0)
241 volume_update(l, r);
245 * buffer-fill changed
247 static void __producer_buffer_fill_update(void)
249 int fill;
251 player_info_lock();
252 fill = buffer_get_filled_chunks();
253 if (fill != player_info.buffer_fill) {
254 /* d_print("\n"); */
255 player_info.buffer_fill = fill;
256 player_info.buffer_fill_changed = 1;
258 player_info_unlock();
262 * playing position changed
264 static void __consumer_position_update(void)
266 static int old_pos = -1;
267 int pos;
269 pos = 0;
270 if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED)
271 pos = consumer_pos / buffer_second_size();
272 if (pos != old_pos) {
273 /* d_print("\n"); */
274 old_pos = pos;
276 player_info_lock();
277 player_info.pos = pos;
278 player_info.position_changed = 1;
279 player_info_unlock();
284 * something big happened (stopped/paused/unpaused...)
286 static void __player_status_changed(void)
288 int pos = 0;
290 /* d_print("\n"); */
291 if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED)
292 pos = consumer_pos / buffer_second_size();
294 player_info_lock();
295 player_info.status = consumer_status;
296 player_info.pos = pos;
297 player_info.buffer_fill = buffer_get_filled_chunks();
298 player_info.buffer_size = buffer_nr_chunks;
299 player_info.status_changed = 1;
300 player_info_unlock();
303 /* updating player status }}} */
305 static void __prebuffer(void)
307 int limit_chunks;
309 BUG_ON(producer_status != PS_PLAYING);
310 if (ip_is_remote(ip)) {
311 limit_chunks = buffer_nr_chunks;
312 } else {
313 int limit_ms, limit_size;
315 limit_ms = 250;
316 limit_size = limit_ms * buffer_second_size() / 1000;
317 limit_chunks = limit_size / CHUNK_SIZE;
318 if (limit_chunks < 1)
319 limit_chunks = 1;
321 while (1) {
322 int nr_read, size, filled;
323 char *wpos;
325 filled = buffer_get_filled_chunks();
326 /* d_print("PREBUF: %2d / %2d\n", filled, limit_chunks); */
328 /* not fatal */
329 //BUG_ON(filled > limit_chunks);
331 if (filled >= limit_chunks)
332 break;
334 size = buffer_get_wpos(&wpos);
335 nr_read = ip_read(ip, wpos, size);
336 if (nr_read < 0) {
337 if (nr_read == -1 && errno == EAGAIN)
338 continue;
339 player_ip_error(nr_read, "reading file %s", ip_get_filename(ip));
340 ip_delete(ip);
341 producer_status = PS_UNLOADED;
342 return;
344 if (ip_metadata_changed(ip))
345 metadata_changed();
347 /* buffer_fill with 0 count marks current chunk filled */
348 buffer_fill(nr_read);
350 __producer_buffer_fill_update();
351 if (nr_read == 0) {
352 /* EOF */
353 break;
358 /* setting producer status {{{ */
360 static void __producer_play(void)
362 if (producer_status == PS_UNLOADED) {
363 char *filename;
365 if (get_next(&filename) == 0) {
366 int rc;
368 ip = ip_new(filename);
369 rc = ip_open(ip);
370 if (rc) {
371 player_ip_error(rc, "opening file `%s'", filename);
372 ip_delete(ip);
373 } else {
374 producer_status = PS_PLAYING;
376 free(filename);
377 file_changed();
379 } else if (producer_status == PS_PLAYING) {
380 if (ip_seek(ip, 0.0) == 0) {
381 reset_buffer();
383 } else if (producer_status == PS_STOPPED) {
384 int rc;
386 rc = ip_open(ip);
387 if (rc) {
388 player_ip_error(rc, "opening file `%s'", ip_get_filename(ip));
389 ip_delete(ip);
390 producer_status = PS_UNLOADED;
391 } else {
392 producer_status = PS_PLAYING;
394 } else if (producer_status == PS_PAUSED) {
395 producer_status = PS_PLAYING;
399 static void __producer_stop(void)
401 if (producer_status == PS_PLAYING || producer_status == PS_PAUSED) {
402 ip_close(ip);
403 producer_status = PS_STOPPED;
404 reset_buffer();
408 static void __producer_unload(void)
410 __producer_stop();
411 if (producer_status == PS_STOPPED) {
412 ip_delete(ip);
413 producer_status = PS_UNLOADED;
417 static void __producer_pause(void)
419 if (producer_status == PS_PLAYING) {
420 producer_status = PS_PAUSED;
421 } else if (producer_status == PS_PAUSED) {
422 producer_status = PS_PLAYING;
426 static void __producer_set_file(const char *filename)
428 __producer_unload();
429 ip = ip_new(filename);
430 producer_status = PS_STOPPED;
431 file_changed();
434 /* setting producer status }}} */
436 /* setting consumer status {{{ */
438 static void __consumer_play(void)
440 if (consumer_status == CS_PLAYING) {
441 op_drop();
442 } else if (consumer_status == CS_STOPPED) {
443 int rc;
445 set_buffer_sf(ip_get_sf(ip));
446 rc = op_open(buffer_sf);
447 if (rc) {
448 player_op_error(rc, "opening audio device");
449 } else {
450 consumer_status = CS_PLAYING;
452 } else if (consumer_status == CS_PAUSED) {
453 op_unpause();
454 consumer_status = CS_PLAYING;
458 static void __consumer_drain_and_stop(void)
460 if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
461 op_close();
462 consumer_status = CS_STOPPED;
466 static void __consumer_stop(void)
468 if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
469 op_drop();
470 op_close();
471 consumer_status = CS_STOPPED;
475 static void __consumer_pause(void)
477 if (consumer_status == CS_PLAYING) {
478 op_pause();
479 consumer_status = CS_PAUSED;
480 } else if (consumer_status == CS_PAUSED) {
481 op_unpause();
482 consumer_status = CS_PLAYING;
486 /* setting consumer status }}} */
488 static void __consumer_handle_eof(void)
490 char *filename;
492 if (ip_is_remote(ip)) {
493 __producer_stop();
494 __consumer_drain_and_stop();
495 player_error("lost connection");
496 return;
499 if (get_next(&filename) == 0) {
500 int rc;
502 __producer_unload();
503 ip = ip_new(filename);
504 producer_status = PS_STOPPED;
505 /* PS_STOPPED, CS_PLAYING */
506 if (player_cont) {
507 __producer_play();
508 if (producer_status == PS_UNLOADED) {
509 __consumer_stop();
510 file_changed();
511 } else {
512 /* PS_PLAYING */
513 set_buffer_sf(ip_get_sf(ip));
514 rc = op_set_sf(buffer_sf);
515 if (rc < 0) {
516 __producer_stop();
517 consumer_status = CS_STOPPED;
518 player_op_error(rc, "setting sample format");
519 file_changed();
520 } else {
521 file_changed();
522 __prebuffer();
525 } else {
526 __consumer_drain_and_stop();
527 file_changed();
529 free(filename);
530 } else {
531 __producer_unload();
532 __consumer_drain_and_stop();
533 file_changed();
535 __player_status_changed();
538 static void *consumer_loop(void *arg)
540 while (1) {
541 int rc, space;
542 int size;
543 char *rpos;
545 consumer_lock();
546 if (!consumer_running)
547 break;
549 if (consumer_status == CS_PAUSED || consumer_status == CS_STOPPED) {
550 mixer_check();
551 consumer_unlock();
552 ms_sleep(50);
553 continue;
555 space = op_buffer_space();
556 if (space == -1) {
557 /* busy */
558 __consumer_position_update();
559 consumer_unlock();
560 ms_sleep(50);
561 continue;
563 /* d_print("BS: %6d %3d\n", space, space * 1000 / (44100 * 2 * 2)); */
565 while (1) {
566 /* 25 ms is 4410 B */
567 if (space < 4096) {
568 __consumer_position_update();
569 mixer_check();
570 consumer_unlock();
571 ms_sleep(25);
572 break;
574 size = buffer_get_rpos(&rpos);
575 if (size == 0) {
576 producer_lock();
577 if (producer_status != PS_PLAYING) {
578 producer_unlock();
579 consumer_unlock();
580 break;
582 /* must recheck rpos */
583 size = buffer_get_rpos(&rpos);
584 if (size == 0) {
585 /* OK. now it's safe to check if we are at EOF */
586 if (ip_eof(ip)) {
587 /* EOF */
588 __consumer_handle_eof();
589 producer_unlock();
590 consumer_unlock();
591 break;
592 } else {
593 /* possible underrun */
594 producer_unlock();
595 __consumer_position_update();
596 consumer_unlock();
597 /* d_print("possible underrun\n"); */
598 ms_sleep(10);
599 break;
603 /* player_buffer and ip.eof were inconsistent */
604 producer_unlock();
606 if (size > space)
607 size = space;
608 rc = op_write(rpos, size);
609 if (rc < 0) {
610 d_print("op_write returned %d %s\n", rc,
611 rc == -1 ? strerror(errno) : "");
613 /* try to reopen */
614 op_close();
615 consumer_status = CS_STOPPED;
616 __consumer_play();
618 consumer_unlock();
619 break;
621 buffer_consume(rc);
622 consumer_pos += rc;
623 space -= rc;
626 __consumer_stop();
627 consumer_unlock();
628 return NULL;
631 static void *producer_loop(void *arg)
633 while (1) {
634 /* number of chunks to fill
635 * too big => seeking is slow
636 * too small => underruns?
638 const int chunks = 1;
639 int size, nr_read, i;
640 char *wpos;
642 producer_lock();
643 if (!producer_running)
644 break;
646 if (producer_status == PS_UNLOADED ||
647 producer_status == PS_PAUSED ||
648 producer_status == PS_STOPPED || ip_eof(ip)) {
649 producer_unlock();
650 ms_sleep(50);
651 continue;
653 for (i = 0; ; i++) {
654 size = buffer_get_wpos(&wpos);
655 if (size == 0) {
656 /* buffer is full */
657 producer_unlock();
658 ms_sleep(50);
659 break;
661 nr_read = ip_read(ip, wpos, size);
662 if (nr_read < 0) {
663 if (nr_read != -1 || errno != EAGAIN) {
664 player_ip_error(nr_read, "reading file %s",
665 ip_get_filename(ip));
666 ip_delete(ip);
667 producer_status = PS_UNLOADED;
669 producer_unlock();
670 ms_sleep(50);
671 break;
673 if (ip_metadata_changed(ip))
674 metadata_changed();
676 /* buffer_fill with 0 count marks current chunk filled */
677 buffer_fill(nr_read);
678 if (nr_read == 0) {
679 /* consumer handles EOF */
680 producer_unlock();
681 ms_sleep(50);
682 break;
684 if (i == chunks) {
685 producer_unlock();
686 /* don't sleep! */
687 break;
690 __producer_buffer_fill_update();
692 __producer_unload();
693 producer_unlock();
694 return NULL;
697 void player_load_plugins(void)
699 ip_load_plugins();
700 op_load_plugins();
703 void player_init(const struct player_callbacks *callbacks)
705 int rc;
706 #if defined(__linux__) || defined(__FreeBSD__)
707 pthread_attr_t attr;
708 #endif
709 pthread_attr_t *attrp = NULL;
711 /* 1 s is 176400 B (0.168 MB)
712 * 10 s is 1.68 MB
714 buffer_nr_chunks = 10 * 44100 * 16 / 8 * 2 / CHUNK_SIZE;
715 buffer_init();
717 player_cbs = callbacks;
719 #if defined(__linux__) || defined(__FreeBSD__)
720 rc = pthread_attr_init(&attr);
721 BUG_ON(rc);
722 rc = pthread_attr_setschedpolicy(&attr, SCHED_RR);
723 if (rc) {
724 d_print("could not set real-time scheduling priority: %s\n", strerror(rc));
725 } else {
726 struct sched_param param;
728 d_print("using real-time scheduling\n");
729 param.sched_priority = sched_get_priority_max(SCHED_RR);
730 d_print("setting priority to %d\n", param.sched_priority);
731 rc = pthread_attr_setschedparam(&attr, &param);
732 BUG_ON(rc);
733 attrp = &attr;
735 #endif
737 rc = pthread_create(&producer_thread, NULL, producer_loop, NULL);
738 BUG_ON(rc);
740 rc = pthread_create(&consumer_thread, attrp, consumer_loop, NULL);
741 if (rc && attrp) {
742 d_print("could not create thread using real-time scheduling: %s\n", strerror(rc));
743 rc = pthread_create(&consumer_thread, NULL, consumer_loop, NULL);
745 BUG_ON(rc);
747 /* update player_info.cont etc. */
748 player_lock();
749 __player_status_changed();
750 player_unlock();
753 void player_exit(void)
755 int rc;
757 player_lock();
758 consumer_running = 0;
759 producer_running = 0;
760 player_unlock();
762 rc = pthread_join(consumer_thread, NULL);
763 BUG_ON(rc);
764 rc = pthread_join(producer_thread, NULL);
765 BUG_ON(rc);
767 op_exit_plugins();
770 void player_stop(void)
772 player_lock();
773 __consumer_stop();
774 __producer_stop();
775 __player_status_changed();
776 player_unlock();
779 void player_play(void)
781 int prebuffer;
783 player_lock();
784 if (producer_status == PS_PLAYING && ip_is_remote(ip)) {
785 /* seeking not allowed */
786 player_unlock();
787 return;
789 prebuffer = consumer_status == CS_STOPPED;
790 __producer_play();
791 if (producer_status == PS_PLAYING) {
792 __consumer_play();
793 if (consumer_status != CS_PLAYING)
794 __producer_stop();
795 } else {
796 __consumer_stop();
798 __player_status_changed();
799 if (consumer_status == CS_PLAYING && prebuffer)
800 __prebuffer();
801 player_unlock();
804 void player_pause(void)
806 player_lock();
807 if (ip && ip_is_remote(ip)) {
808 /* pausing not allowed */
809 player_unlock();
810 return;
812 __producer_pause();
813 __consumer_pause();
814 __player_status_changed();
815 player_unlock();
818 void player_set_file(const char *filename)
820 int rc;
822 player_lock();
823 __producer_set_file(filename);
824 if (producer_status == PS_UNLOADED) {
825 __consumer_stop();
826 goto out;
829 /* PS_STOPPED */
830 if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
831 __producer_play();
832 if (producer_status == PS_UNLOADED) {
833 __consumer_stop();
834 goto out;
837 /* must do op_drop() here because op_set_sf()
838 * might call op_close() which calls drain()
840 op_drop();
842 set_buffer_sf(ip_get_sf(ip));
843 rc = op_set_sf(buffer_sf);
844 if (rc == 0) {
845 /* device wasn't reopened */
846 if (consumer_status == CS_PAUSED) {
847 /* status was paused => need to unpause */
848 rc = op_unpause();
849 d_print("op_unpause: %d\n", rc);
851 consumer_status = CS_PLAYING;
852 } else if (rc == 1) {
853 /* device was reopened */
854 consumer_status = CS_PLAYING;
855 } else {
856 __producer_stop();
857 player_op_error(rc, "setting sample format");
858 consumer_status = CS_STOPPED;
861 out:
862 __player_status_changed();
863 if (producer_status == PS_PLAYING)
864 __prebuffer();
865 player_unlock();
868 void player_play_file(const char *filename)
870 int rc;
872 player_lock();
873 __producer_set_file(filename);
874 if (producer_status == PS_UNLOADED) {
875 __consumer_stop();
876 goto out;
879 /* PS_STOPPED */
880 __producer_play();
882 /* PS_UNLOADED,PS_PLAYING */
883 if (producer_status == PS_UNLOADED) {
884 __consumer_stop();
885 goto out;
888 /* PS_PLAYING */
889 if (consumer_status == CS_STOPPED) {
890 __consumer_play();
891 if (consumer_status == CS_STOPPED)
892 __producer_stop();
893 } else {
894 /* PS_PLAYING, CS_PLAYING,CS_PAUSED */
895 /* must do op_drop() here because op_set_sf()
896 * might call op_close() which calls drain()
898 op_drop();
900 set_buffer_sf(ip_get_sf(ip));
901 rc = op_set_sf(buffer_sf);
902 if (rc == 0) {
903 /* device wasn't reopened */
904 if (consumer_status == CS_PAUSED) {
905 /* status was paused => need to unpause */
906 rc = op_unpause();
907 d_print("op_unpause: %d\n", rc);
909 consumer_status = CS_PLAYING;
910 } else if (rc == 1) {
911 /* device was reopened */
912 consumer_status = CS_PLAYING;
913 } else {
914 __producer_stop();
915 player_op_error(rc, "setting sample format");
916 consumer_status = CS_STOPPED;
919 out:
920 __player_status_changed();
921 if (producer_status == PS_PLAYING)
922 __prebuffer();
923 player_unlock();
926 void player_seek(double offset, int whence)
928 player_lock();
929 if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
930 double pos, duration, new_pos;
931 int rc;
933 pos = (double)consumer_pos / (double)buffer_second_size();
934 duration = ip_duration(ip);
935 if (duration < 0) {
936 /* can't seek */
937 d_print("can't seek\n");
938 player_unlock();
939 return;
941 if (whence == SEEK_CUR) {
942 /* relative to current position */
943 new_pos = pos + offset;
944 if (new_pos < 0.0)
945 new_pos = 0.0;
946 if (offset > 0.0) {
947 /* seeking forward */
948 if (new_pos > duration - 5.0)
949 new_pos = duration - 5.0;
950 if (new_pos < 0.0)
951 new_pos = 0.0;
952 if (new_pos < pos - 0.5) {
953 /* must seek at least 0.5s */
954 d_print("must seek at least 0.5s\n");
955 player_unlock();
956 return;
959 } else {
960 /* absolute position */
961 new_pos = offset;
962 if (new_pos < 0.0) {
963 d_print("seek offset negative\n");
964 player_unlock();
965 return;
967 if (new_pos > duration) {
968 d_print("seek offset too large\n");
969 player_unlock();
970 return;
973 /* d_print("seeking %g/%g (%g from eof)\n", new_pos, duration, duration - new_pos); */
974 rc = ip_seek(ip, new_pos);
975 if (rc == 0) {
976 /* d_print("doing op_drop after seek\n"); */
977 op_drop();
978 reset_buffer();
979 consumer_pos = new_pos * buffer_second_size();
980 __consumer_position_update();
981 } else {
982 d_print("error: ip_seek returned %d\n", rc);
985 player_unlock();
989 * change output plugin without stopping playback
991 int player_set_op(const char *name)
993 int rc, l, r;
995 player_lock();
997 /* drop needed because close drains the buffer */
998 if (consumer_status == CS_PAUSED)
999 op_drop();
1001 if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED)
1002 op_close();
1004 if (name) {
1005 d_print("setting op to '%s'\n", name);
1006 rc = op_select(name);
1007 } else {
1008 /* first initialized plugin */
1009 d_print("selecting first initialized op\n");
1010 rc = op_select_any();
1012 if (rc) {
1013 consumer_status = CS_STOPPED;
1015 __producer_stop();
1016 player_op_error(rc, "selecting output plugin '%s'", name);
1017 player_unlock();
1018 return rc;
1021 if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
1022 set_buffer_sf(ip_get_sf(ip));
1023 rc = op_open(buffer_sf);
1024 if (rc) {
1025 consumer_status = CS_STOPPED;
1026 __producer_stop();
1027 player_op_error(rc, "opening audio device");
1028 player_unlock();
1029 return rc;
1031 if (consumer_status == CS_PAUSED)
1032 op_pause();
1035 if (!op_get_volume(&l, &r))
1036 volume_update(l, r);
1038 player_unlock();
1039 d_print("rc = %d\n", rc);
1040 return rc;
1043 char *player_get_op(void)
1045 return op_get_current();
1048 void player_set_buffer_chunks(unsigned int nr_chunks)
1050 if (nr_chunks < 3)
1051 nr_chunks = 3;
1052 if (nr_chunks > 30)
1053 nr_chunks = 30;
1055 player_lock();
1056 __producer_stop();
1057 __consumer_stop();
1059 buffer_nr_chunks = nr_chunks;
1060 buffer_init();
1062 __player_status_changed();
1063 player_unlock();
1066 int player_get_buffer_chunks(void)
1068 return buffer_nr_chunks;
1071 int player_get_fileinfo(const char *filename, int *duration,
1072 struct keyval **comments)
1074 struct input_plugin *plug;
1075 int rc;
1077 *comments = NULL;
1078 *duration = -1;
1079 plug = ip_new(filename);
1080 if (ip_is_remote(plug)) {
1081 *comments = xnew0(struct keyval, 1);
1082 ip_delete(plug);
1083 return 0;
1085 rc = ip_open(plug);
1086 if (rc) {
1087 int save = errno;
1089 ip_delete(plug);
1090 errno = save;
1091 if (rc != -1)
1092 rc = -PLAYER_ERROR_NOT_SUPPORTED;
1093 return rc;
1095 *duration = ip_duration(plug);
1096 rc = ip_read_comments(plug, comments);
1097 ip_delete(plug);
1098 return rc;
1101 int player_get_volume(int *left, int *right)
1103 int rc;
1105 consumer_lock();
1106 rc = op_get_volume(left, right);
1107 consumer_unlock();
1108 return rc;
1111 int player_set_volume(int left, int right)
1113 int rc;
1115 consumer_lock();
1116 rc = op_set_volume(left, right);
1117 consumer_unlock();
1118 if (rc == 0)
1119 volume_update(left, right);
1120 return rc;
1123 int player_set_op_option(unsigned int id, const char *val)
1125 int rc;
1127 player_lock();
1128 __consumer_stop();
1129 __producer_stop();
1130 rc = op_set_option(id, val);
1131 __player_status_changed();
1132 player_unlock();
1133 return rc;
1136 int player_get_op_option(unsigned int id, char **val)
1138 int rc;
1140 player_lock();
1141 rc = op_get_option(id, val);
1142 player_unlock();
1143 return rc;
1146 int player_for_each_op_option(void (*callback)(unsigned int id, const char *key))
1148 player_lock();
1149 __consumer_stop();
1150 __producer_stop();
1151 op_for_each_option(callback);
1152 __player_status_changed();
1153 player_unlock();
1154 return 0;
1157 void player_dump_plugins(void)
1159 ip_dump_plugins();
1160 op_dump_plugins();