diff options
486 files changed, 10211 insertions, 2102 deletions
diff --git a/.gitignore b/.gitignore index 977ae6a12..7aa22de09 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,5 @@ test/dump_playlist test/run_normalize test/tmp test/run_inotify +test/test_queue_priority +test/run_ntp_server @@ -119,6 +119,9 @@ WAVE, AIFF, and many others. libwavpack - http://www.wavpack.com/ For WavPack playback. +despotify - https://github.com/SimonKagstrom/despotify +For Spotify playback. + Optional Miscellaneous Dependencies ----------------------------------- @@ -141,6 +144,9 @@ For the sticker database. libcue - http://libcue.sourceforge.net/ For CUE sheet support. +libcdio - http://www.gnu.org/software/libcdio/ +For playing audio CDs. + pkg-config ---------- diff --git a/Makefile.am b/Makefile.am index b3620bdeb..df9be5aaf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -101,6 +101,7 @@ mpd_headers = \ src/decoder/flac_pcm.h \ src/decoder/_flac_common.h \ src/decoder/_ogg_common.h \ + src/decoder/pcm_decoder_plugin.h \ src/input_init.h \ src/input_plugin.h \ src/input_registry.h \ @@ -110,6 +111,9 @@ mpd_headers = \ src/input/curl_input_plugin.h \ src/input/rewind_input_plugin.h \ src/input/mms_input_plugin.h \ + src/input/despotify_input_plugin.h \ + src/input/cdio_paranoia_input_plugin.h \ + src/despotify_utils.h \ src/text_file.h \ src/text_input_stream.h \ src/icy_server.h \ @@ -142,6 +146,7 @@ mpd_headers = \ src/output/httpd_client.h \ src/output/httpd_internal.h \ src/output/pulse_output_plugin.h \ + src/output/roar_output_plugin.h \ src/output/winmm_output_plugin.h \ src/page.h \ src/pcm_buffer.h \ @@ -180,6 +185,7 @@ mpd_headers = \ src/playlist/asx_playlist_plugin.h \ src/playlist/rss_playlist_plugin.h \ src/playlist/lastfm_playlist_plugin.h \ + src/playlist/despotify_playlist_plugin.h \ src/playlist/cue_playlist_plugin.h \ src/playlist/flac_playlist_plugin.h \ src/poison.h \ @@ -216,6 +222,7 @@ mpd_headers = \ src/strset.h \ src/uri.h \ src/utils.h \ + src/string_util.h \ src/volume.h \ src/zeroconf.h src/zeroconf-internal.h \ src/locate.h \ @@ -278,16 +285,24 @@ src_mpd_SOURCES = \ src/client_event.c \ src/client_expire.c \ src/client_global.c \ + src/client_idle.h \ src/client_idle.c \ src/client_list.c \ src/client_new.c \ src/client_process.c \ src/client_read.c \ src/client_write.c \ + src/client_message.h \ + src/client_message.c \ + src/client_subscribe.h \ + src/client_subscribe.c \ + src/tcp_socket.c src/tcp_socket.h \ + src/udp_server.c src/udp_server.h \ src/server_socket.c \ src/listen.c \ src/log.c \ src/ls.c \ + src/io_thread.c src/io_thread.h \ src/main.c \ src/main_win32.c \ src/event_pipe.c \ @@ -349,11 +364,17 @@ src_mpd_SOURCES = \ src/strset.c \ src/uri.c \ src/utils.c \ + src/string_util.c \ src/volume.c \ src/locate.c \ src/stored_playlist.c \ src/timer.c +if ENABLE_DESPOTIFY +src_mpd_SOURCES += \ + src/despotify_utils.c +endif + if ENABLE_INOTIFY src_mpd_SOURCES += \ src/inotify_source.c \ @@ -463,6 +484,7 @@ DECODER_LIBS = \ $(CUE_LIBS) DECODER_SRC = \ + src/decoder/pcm_decoder_plugin.c \ src/decoder_buffer.c \ src/decoder_plugin.c \ src/decoder_list.c @@ -618,11 +640,15 @@ endif INPUT_CFLAGS = \ $(CURL_CFLAGS) \ + $(SOUP_CFLAGS) \ + $(CDIO_PARANOIA_CFLAGS) \ $(FFMPEG_CFLAGS) \ $(MMS_CFLAGS) INPUT_LIBS = \ $(CURL_LIBS) \ + $(SOUP_LIBS) \ + $(CDIO_PARANOIA_LIBS) \ $(FFMPEG_LIBS) \ $(MMS_LIBS) @@ -638,6 +664,16 @@ INPUT_SRC += src/input/curl_input_plugin.c \ src/icy_metadata.c endif +if ENABLE_SOUP +INPUT_SRC += \ + src/input/soup_input_plugin.c \ + src/input/soup_input_plugin.h +endif + +if ENABLE_CDIO_PARANOIA +INPUT_SRC += src/input/cdio_paranoia_input_plugin.c +endif + if HAVE_FFMPEG INPUT_SRC += src/input/ffmpeg_input_plugin.c endif @@ -646,6 +682,10 @@ if ENABLE_MMS INPUT_SRC += src/input/mms_input_plugin.c endif +if ENABLE_DESPOTIFY +INPUT_SRC += src/input/despotify_input_plugin.c +endif + OUTPUT_CFLAGS = \ $(AO_CFLAGS) \ @@ -653,6 +693,7 @@ OUTPUT_CFLAGS = \ $(FFADO_CFLAGS) \ $(JACK_CFLAGS) \ $(OPENAL_CFLAGS) \ + $(OPENSSL_CFLAGS) \ $(PULSE_CFLAGS) \ $(SHOUT_CFLAGS) @@ -660,6 +701,7 @@ OUTPUT_LIBS = \ $(LIBWRAP_LDFLAGS) \ $(AO_LIBS) \ $(ALSA_LIBS) \ + $(ROAR_LIBS) \ $(FFADO_LIBS) \ $(JACK_LIBS) \ $(OPENAL_LIBS) \ @@ -674,6 +716,7 @@ OUTPUT_API_SRC = \ src/output_state.c \ src/output_print.c \ src/output_command.c \ + src/output_finish.c \ src/output_init.c OUTPUT_SRC = \ @@ -693,6 +736,11 @@ OUTPUT_SRC += src/output/alsa_plugin.c MIXER_SRC += src/mixer/alsa_mixer_plugin.c endif +if HAVE_ROAR +OUTPUT_SRC += src/output/roar_plugin.c +MIXER_SRC += src/mixer/roar_mixer_plugin.c +endif + if ENABLE_FFADO_OUTPUT OUTPUT_SRC += src/output/ffado_output_plugin.c endif @@ -730,6 +778,15 @@ if HAVE_OSX OUTPUT_SRC += src/output/osx_plugin.c endif +if ENABLE_RAOP_OUTPUT +OUTPUT_SRC += \ + src/ntp_server.c src/ntp_server.h \ + src/rtsp_client.c src/rtsp_client.h \ + src/output/raop_output_plugin.c +MIXER_SRC += src/mixer/raop_mixer_plugin.c +OUTPUT_LIBS += $(OPENSSL_LIBS) +endif + if HAVE_PULSE OUTPUT_SRC += src/output/pulse_output_plugin.c MIXER_SRC += src/mixer/pulse_mixer_plugin.c @@ -777,6 +834,10 @@ if ENABLE_LASTFM PLAYLIST_SRC += src/playlist/lastfm_playlist_plugin.c endif +if ENABLE_DESPOTIFY +PLAYLIST_SRC += src/playlist/despotify_playlist_plugin.c +endif + if HAVE_CUE PLAYLIST_SRC += src/playlist/cue_playlist_plugin.c endif @@ -827,14 +888,19 @@ sparse-check: if ENABLE_TEST -TESTS = +C_TESTS = \ + test/test_queue_priority + +TESTS = $(C_TESTS) noinst_PROGRAMS = \ + $(C_TESTS) \ test/read_conf \ test/run_input \ test/dump_playlist \ test/run_decoder \ test/read_tags \ + test/run_ntp_server \ test/run_filter \ test/run_output \ test/run_convert \ @@ -851,7 +917,7 @@ test_read_conf_CPPFLAGS = $(AM_CPPFLAGS) \ test_read_conf_LDADD = $(MPD_LIBS) \ $(GLIB_LIBS) test_read_conf_SOURCES = test/read_conf.c \ - src/conf.c src/tokenizer.c src/utils.c + src/conf.c src/tokenizer.c src/utils.c src/string_util.c test_run_input_CPPFLAGS = $(AM_CPPFLAGS) \ $(ARCHIVE_CFLAGS) \ @@ -862,7 +928,8 @@ test_run_input_LDADD = $(MPD_LIBS) \ $(GLIB_LIBS) test_run_input_SOURCES = test/run_input.c \ test/stdbin.h \ - src/conf.c src/tokenizer.c src/utils.c \ + src/io_thread.c src/io_thread.h \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ src/tag.c src/tag_pool.c src/tag_save.c \ src/fd_util.c \ $(ARCHIVE_SRC) \ @@ -880,7 +947,8 @@ test_dump_playlist_LDADD = $(MPD_LIBS) \ $(INPUT_LIBS) \ $(GLIB_LIBS) test_dump_playlist_SOURCES = test/dump_playlist.c \ - src/conf.c src/tokenizer.c src/utils.c \ + src/io_thread.c src/io_thread.h \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ src/uri.c \ src/song.c src/tag.c src/tag_pool.c src/tag_save.c \ src/text_input_stream.c src/fifo_buffer.c \ @@ -910,7 +978,8 @@ test_run_decoder_LDADD = $(MPD_LIBS) \ $(GLIB_LIBS) test_run_decoder_SOURCES = test/run_decoder.c \ test/stdbin.h \ - src/conf.c src/tokenizer.c src/utils.c src/log.c \ + src/io_thread.c src/io_thread.h \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ src/tag.c src/tag_pool.c \ src/replay_gain_info.c \ src/uri.c \ @@ -933,7 +1002,8 @@ test_read_tags_LDADD = $(MPD_LIBS) \ $(INPUT_LIBS) $(DECODER_LIBS) \ $(GLIB_LIBS) test_read_tags_SOURCES = test/read_tags.c \ - src/conf.c src/tokenizer.c src/utils.c src/log.c \ + src/io_thread.c src/io_thread.h \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ src/tag.c src/tag_pool.c \ src/replay_gain_info.c \ src/uri.c \ @@ -945,6 +1015,15 @@ test_read_tags_SOURCES = test/read_tags.c \ $(TAG_SRC) \ $(DECODER_SRC) +test_run_ntp_server_CPPFLAGS = $(AM_CPPFLAGS) +test_run_ntp_server_LDADD = $(MPD_LIBS) \ + $(GLIB_LIBS) +test_run_ntp_server_SOURCES = test/run_ntp_server.c \ + test/signals.c test/signals.h \ + src/io_thread.c src/io_thread.h \ + src/udp_server.c src/udp_server.h \ + src/ntp_server.c src/ntp_server.h + test_run_filter_CPPFLAGS = $(AM_CPPFLAGS) test_run_filter_LDADD = $(MPD_LIBS) \ $(SAMPLERATE_LIBS) \ @@ -953,7 +1032,7 @@ test_run_filter_SOURCES = test/run_filter.c \ test/stdbin.h \ src/filter_plugin.c \ src/filter_registry.c \ - src/conf.c src/tokenizer.c src/utils.c \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c \ src/pcm_volume.c src/pcm_convert.c src/pcm_byteswap.c \ src/pcm_format.c src/pcm_channels.c src/pcm_dither.c \ src/pcm_pack.c \ @@ -970,12 +1049,23 @@ if HAVE_LIBSAMPLERATE test_run_filter_SOURCES += src/pcm_resample_libsamplerate.c endif +if ENABLE_DESPOTIFY +test_read_tags_SOURCES += \ + src/despotify_utils.c +test_run_input_SOURCES += \ + src/despotify_utils.c +test_dump_playlist_SOURCES += \ + src/despotify_utils.c +test_run_decoder_SOURCES += \ + src/despotify_utils.c +endif + if ENABLE_ENCODER noinst_PROGRAMS += test/run_encoder test_run_encoder_SOURCES = test/run_encoder.c \ test/stdbin.h \ src/conf.c src/tokenizer.c \ - src/utils.c \ + src/utils.c src/string_util.c \ src/tag.c src/tag_pool.c \ src/audio_check.c \ src/audio_format.c \ @@ -1036,7 +1126,10 @@ test_run_output_LDADD = $(MPD_LIBS) \ $(GLIB_LIBS) test_run_output_SOURCES = test/run_output.c \ test/stdbin.h \ - src/conf.c src/tokenizer.c src/utils.c src/log.c \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ + src/io_thread.c src/io_thread.h \ + src/udp_server.c src/udp_server.h \ + src/tcp_socket.c src/tcp_socket.h \ src/audio_check.c \ src/audio_format.c \ src/audio_parser.c \ @@ -1045,7 +1138,7 @@ test_run_output_SOURCES = test/run_output.c \ src/fifo_buffer.c \ src/page.c \ src/socket_util.c \ - src/output_init.c src/output_list.c \ + src/output_init.c src/output_finish.c src/output_list.c \ $(ENCODER_SRC) \ src/mixer_api.c \ src/mixer_control.c \ @@ -1072,7 +1165,7 @@ test_read_mixer_LDADD = $(MPD_LIBS) \ $(OUTPUT_LIBS) \ $(GLIB_LIBS) test_read_mixer_SOURCES = test/read_mixer.c \ - src/conf.c src/tokenizer.c src/utils.c src/log.c \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ src/mixer_control.c src/mixer_api.c \ src/filter_plugin.c \ src/filter/volume_filter_plugin.c \ @@ -1100,6 +1193,12 @@ test_run_inotify_SOURCES = test/run_inotify.c \ test_run_inotify_LDADD = $(GLIB_LIBS) endif +test_test_queue_priority_SOURCES = \ + src/queue.c \ + test/test_queue_priority.c +test_test_queue_priority_LDADD = \ + $(GLIB_LIBS) + endif @@ -1137,8 +1236,7 @@ endif doc/api/html/index.html: doc/doxygen.conf @mkdir -p $(@D) - [ "$(srcdir)" = "." ] || sed '/INPUT *=/ s/\([^ ]\+\/\)/$(subst /,\/,$(srcdir))\/\1/g' $(srcdir)/doc/doxygen.conf >doc/doxygen.conf - $(DOXYGEN) doc/doxygen.conf + $(DOXYGEN) $< all-local: $(DOCBOOK_HTML) doc/api/html/index.html @@ -1,3 +1,25 @@ +ver 0.17 (2011/??/??) +* protocol: + - support client-to-client communication + - "update" and "rescan" need only "CONTROL" permission +* input: + - cdio_paranoia: new input plugin to play audio CDs + - curl: enable CURLOPT_NETRC + - soup: new input plugin based on libsoup + - ffmpeg: support libavformat 0.7 +* decoder: + - mpg123: implement seeking + - ffmpeg: drop support for pre-0.5 ffmpeg + - ffmpeg: support libavformat 0.7 +* output: + - osx: allow user to specify other audio devices + - raop: new output plugin + - shout: add possibility to set url + - roar: new output plugin for RoarAudio +* state_file: add option "restore_paused" +* cue: show CUE track numbers + + ver 0.16.4 (2011/??/??) * don't abort configure when avahi is not found * auto-detect libmad without pkg-config diff --git a/configure.ac b/configure.ac index 502560110..0a749842b 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,11 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.16.4~git, musicpd-dev-team@lists.sourceforge.net) +AC_INIT(mpd, 0.17~git, musicpd-dev-team@lists.sourceforge.net) AC_CONFIG_SRCDIR([src/main.c]) AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2 subdir-objects]) AM_CONFIG_HEADER(config.h) AC_CONFIG_MACRO_DIR([m4]) -AC_DEFINE(PROTOCOL_VERSION, "0.16.0", [The MPD protocol version]) +AC_DEFINE(PROTOCOL_VERSION, "0.17.0", [The MPD protocol version]) dnl --------------------------------------------------------------------------- @@ -121,6 +121,11 @@ AC_ARG_ENABLE(alsa, AS_HELP_STRING([--enable-alsa], [enable ALSA support]),, [enable_alsa=auto]) +AC_ARG_ENABLE(roar, + AS_HELP_STRING([--enable-roar], + [enable support for RoarAudio]),, + [enable_roar=auto]) + AC_ARG_ENABLE(ao, AS_HELP_STRING([--enable-ao], [enable support for libao]),, @@ -136,6 +141,11 @@ AC_ARG_ENABLE(bzip2, [enable bzip2 archive support (default: disabled)]),, enable_bzip2=no) +AC_ARG_ENABLE(cdio-paranoia, + AS_HELP_STRING([--enable-cdio-paranoia], + [enable support for audio CD support]),, + enable_cdio_paranoia=auto) + AC_ARG_ENABLE(cue, AS_HELP_STRING([--enable-cue], [enable support for libcue support]),, @@ -146,6 +156,11 @@ AC_ARG_ENABLE(curl, [enable support for libcurl HTTP streaming (default: auto)]),, [enable_curl=auto]) +AC_ARG_ENABLE(soup, + AS_HELP_STRING([--enable-soup], + [enable support for libsoup HTTP streaming (default: auto)]),, + [enable_soup=auto]) + AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [enable debugging (default: disabled)]),, @@ -195,6 +210,11 @@ AC_ARG_ENABLE(httpd-output, [enables the HTTP server output]),, [enable_httpd_output=auto]) +AC_ARG_ENABLE(raop-output, + AS_HELP_STRING([--enable-raop-output], + [enables the RAOP output]),, + [enable_raop_output=auto]) + AC_ARG_ENABLE(id3, AS_HELP_STRING([--enable-id3], [disable id3 support]),, @@ -227,6 +247,11 @@ AC_ARG_ENABLE(lastfm, [enable support for last.fm radio (default: disable)]),, [enable_lastfm=no]) +AC_ARG_ENABLE(despotify, + AS_HELP_STRING([--enable-despotify], + [enable support for despotify (default: disable)]),, + [enable_despotify=no]) + AC_ARG_ENABLE(lame-encoder, AS_HELP_STRING([--enable-lame-encoder], [enable the LAME mp3 encoder]),, @@ -621,6 +646,14 @@ if test x$enable_curl = xyes; then fi AM_CONDITIONAL(ENABLE_CURL, test x$enable_curl = xyes) +dnl ----------------------------------- SOUP ---------------------------------- +MPD_AUTO_PKG(soup, SOUP, [libsoup-2.4], + [libsoup HTTP streaming], [libsoup not found]) +if test x$enable_soup = xyes; then + AC_DEFINE(ENABLE_SOUP, 1, [Define when libsoup is used for HTTP streaming]) +fi +AM_CONDITIONAL(ENABLE_SOUP, test x$enable_soup = xyes) + dnl --------------------------------- Last.FM --------------------------------- if test x$enable_lastfm = xyes; then if test x$enable_curl != xyes; then @@ -631,6 +664,25 @@ if test x$enable_lastfm = xyes; then fi AM_CONDITIONAL(ENABLE_LASTFM, test x$enable_lastfm = xyes) +dnl --------------------------------- Despotify --------------------------------- +MPD_AUTO_PKG(despotify, DESPOTIFY, [despotify], + [Despotify support], [despotify not found]) +if test x$enable_despotify = xyes; then + AC_DEFINE(ENABLE_DESPOTIFY, 1, [Define when despotify is enabled]) + MPD_LIBS="$MPD_LIBS $DESPOTIFY_LIBS" +fi +AM_CONDITIONAL(ENABLE_DESPOTIFY, test x$enable_despotify = xyes) + +dnl ---------------------------------- libcue --------------------------------- +MPD_AUTO_PKG(cdio_paranoia, CDIO_PARANOIA, [libcdio_paranoia], + [libcdio_paranoia audio CD library], [libcdio_paranoia not found]) +if test x$enable_cdio_paranoia = xyes; then + AC_DEFINE([ENABLE_CDIO_PARANOIA], 1, + [Define to enable libcdio_paranoia support]) +fi + +AM_CONDITIONAL(ENABLE_CDIO_PARANOIA, test x$enable_cdio_paranoia = xyes) + dnl ---------------------------------- libmms --------------------------------- MPD_AUTO_PKG(mms, MMS, [libmms >= 0.4], [libmms mms:// protocol support], [libmms not found]) @@ -724,21 +776,10 @@ AM_CONDITIONAL(HAVE_FAAD, test x$enable_aac = xyes) AM_CONDITIONAL(HAVE_MP4, test x$enable_mp4 = xyes) dnl ---------------------------------- ffmpeg --------------------------------- -MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52 libavcodec >= 51 libavutil >= 49], +MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52.31 libavcodec >= 52.20 libavutil >= 49.15], [ffmpeg decoder library], [libavformat+libavcodec+libavutil not found]) if test x$enable_ffmpeg = xyes; then - # prior to ffmpeg svn12865, you had to specify include files - # without path prefix - old_CPPCFLAGS=$CPPFLAGS - CPPFLAGS="$CPPFLAGS $FFMPEG_CFLAGS" - AC_CHECK_HEADER(libavcodec/avcodec.h,, - AC_DEFINE(OLD_FFMPEG_INCLUDES, 1, - [Define if avcodec.h instead of libavcodec/avcodec.h should be included])) - CPPCFLAGS=$old_CPPFLAGS -fi - -if test x$enable_ffmpeg = xyes; then AC_DEFINE(HAVE_FFMPEG, 1, [Define for FFMPEG support]) fi @@ -1200,6 +1241,16 @@ fi AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes) +dnl ----------------------------------- ROAR ---------------------------------- +MPD_AUTO_PKG(roar, ROAR, [libroar >= 0.4.0], + [ROAR output plugin], [libroar not found]) + +if test x$enable_roar = xyes; then + AC_DEFINE(HAVE_ROAR, 1, [Define to enable ROAR support]) +fi + +AM_CONDITIONAL(HAVE_ROAR, test x$enable_roar = xyes) + dnl ----------------------------------- FFADO --------------------------------- MPD_AUTO_PKG(ffado, FFADO, [libffado], @@ -1310,7 +1361,7 @@ enable_osx=no case "$host_os" in darwin*) AC_DEFINE(HAVE_OSX, 1, [Define for compiling OS X support]) - MPD_LIBS="$MPD_LIBS -framework AudioUnit -framework CoreServices" + MPD_LIBS="$MPD_LIBS -framework AudioUnit -framework CoreAudio -framework CoreServices" enable_osx=yes ;; esac @@ -1381,6 +1432,17 @@ esac AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes) +dnl --------------------------------- RAOP ------------------------------------ + +MPD_AUTO_PKG(raop_output, OPENSSL, [openssl], + [RAOP output], [OpenSSL not found]) + +if test x$enable_raop_output = xyes; then + AC_DEFINE(ENABLE_RAOP_OUTPUT, 1, [Define for compiling RAOP support]) +fi + +AM_CONDITIONAL(ENABLE_RAOP_OUTPUT, test x$enable_raop_output = xyes) + dnl --------------------------------- WinMM --------------------------------- case "$host_os" in @@ -1400,6 +1462,7 @@ AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes) dnl --------------------- Post Audio Output Plugins Tests --------------------- if test x$enable_alsa = xno && + test x$enable_roar = xno && test x$enable_ao = xno && test x$enable_ffado = xno && test x$enable_fifo = xno && @@ -1409,6 +1472,7 @@ if test x$enable_openal = xno && test x$enable_oss = xno && test x$enable_osx = xno && + test x$enable_raop_output = xno && test x$enable_pipe_output = xno && test x$enable_pulse = xno && test x$enable_recorder_output = xno && @@ -1459,7 +1523,6 @@ if test x$GCC = xyes then MPD_CHECK_FLAG([-Wall]) MPD_CHECK_FLAG([-Wextra]) - MPD_CHECK_FLAG([-Wno-deprecated-declarations]) MPD_CHECK_FLAG([-Wmissing-prototypes]) MPD_CHECK_FLAG([-Wshadow]) MPD_CHECK_FLAG([-Wpointer-arith]) @@ -1536,10 +1599,12 @@ results(id3,[ID3]) printf '\nPlayback support:\n\t' results(alsa,ALSA) +results(roar,ROAR) results(ffado,FFADO) results(fifo,FIFO) results(recorder_output,[File Recorder]) results(httpd_output,[HTTP Daemon]) +results(raop_output, [RAOP]) results(jack,[JACK]) results(ao,[libao]) results(oss,[OSS]) @@ -1568,8 +1633,11 @@ fi printf '\nStreaming support:\n\t' results(curl,[CURL]) +results(soup, [SOUP]) results(lastfm,[Last.FM]) results(mms,[MMS]) +results(cdio_paranoia, [CDIO_PARANOIA]) +results(despotify,[Despotify]) printf '\n\n##########################################\n\n' @@ -1579,5 +1647,6 @@ dnl --------------------------------------------------------------------------- dnl Generate files dnl --------------------------------------------------------------------------- AC_OUTPUT(Makefile) +AC_OUTPUT(doc/doxygen.conf) echo 'MPD is ready for compilation, type "make" to begin.' diff --git a/doc/doxygen.conf b/doc/doxygen.conf.in index ddece77e8..0657c5ca2 100644 --- a/doc/doxygen.conf +++ b/doc/doxygen.conf.in @@ -31,7 +31,7 @@ PROJECT_NAME = MPD # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = @VERSION@ # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. @@ -534,7 +534,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = src/ +INPUT = @abs_top_srcdir@/src/ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5 index 4cddd7ba9..af65e1146 100644 --- a/doc/mpd.conf.5 +++ b/doc/mpd.conf.5 @@ -69,6 +69,9 @@ mpd will be saved to this file when mpd is terminated by a TERM signal or by the "kill" command. When mpd is restarted, it will read the state file and restore the state of mpd (including the playlist). .TP +.B restore_paused <yes or no> +Put MPD into pause mode instead of starting playback after startup. +.TP .B user <username> This specifies the user that MPD will run as, if set. MPD should never run as root, and you may use this option to make MPD change its @@ -259,6 +262,17 @@ of database. .B auto_update_depth <N> Limit the depth of the directories being watched, 0 means only watch the music directory itself. There is no limit by default. +.TP +.B despotify_user <name> +This specifies the user to use when logging in to Spotify using the despotify plugins. +.TP +.B despotify_password <name> +This specifies the password to use when logging in to Spotify using the despotify plugins. +.TP +.B despotify_high_bitrate <yes or no> +This specifies if the requested bitrate for Spotify should be high or not. Higher sounds +better but requires more processing and higher bandwidth. Default is yes. +.TP .SH REQUIRED AUDIO OUTPUT PARAMETERS .TP .B type <type> @@ -464,6 +478,9 @@ connect to the icecast server. The default is 2 seconds. .B description <description> This specifies a description of the stream. .TP +.B url <url> +This specifies a URL associated with the stream. +.TP .B genre <genre> This specifies the genre(s) of the stream. .SH FILES diff --git a/doc/mpdconf.example b/doc/mpdconf.example index b14337c76..1aa9cf1dc 100644 --- a/doc/mpdconf.example +++ b/doc/mpdconf.example @@ -103,6 +103,11 @@ # #gapless_mp3_playback "yes" # +# Setting "restore_paused" to "yes" puts MPD into pause mode instead +# of starting playback after startup. +# +#restore_paused "no" +# # This setting enables MPD to create playlists in a format usable by other # music players. # @@ -235,6 +240,7 @@ input { ## protocol "icecast2" # optional ## user "source" # optional ## description "My Stream Description" # optional +## url "http://example.com" # optional ## genre "jazz" # optional ## public "no" # optional ## timeout "2" # optional diff --git a/doc/protocol.xml b/doc/protocol.xml index 0b4f0d175..aba080a6a 100644 --- a/doc/protocol.xml +++ b/doc/protocol.xml @@ -204,6 +204,47 @@ </chapter> <chapter> + <title>Recipes</title> + + <section> + <title>Queuing</title> + + <para> + Often, users run MPD with "<link + linkend="command_random">random</link>" enabled, but want to + be able to insert songs "before" the rest of the playlist. + That is commonly called "queuing". + </para> + + <para> + MPD implements this by allowing the client to specify a + "priority" for each song in the playlist (commands <link + linkend="command_prio"><command>prio</command></link> and + <link + linkend="command_prioid"><command>prioid</command></link>). A + higher priority means that the song is going to be played + before the other songs. + </para> + + <para> + In "random" mode, MPD maintains an internal randomized + sequence of songs. In this sequence, songs with a higher + priority come first, and all songs with the same priority are + shuffled (by default, all songs are shuffled, because all have + the same priority "0"). When you increase the priority of a + song, it is moved to the front of the sequence according to + its new priority, but always after the current one. A song + that has been played already (it's "before" the current song + in that sequence) will only be scheduled for repeated playback + if its priority has become bigger than the priority of the + current song. Decreasing the priority of a song will moved it + farther to the end of the sequence. Changing the priority of + the current song has no effect on the sequence. + </para> + </section> + </chapter> + + <chapter> <title>Command reference</title> <note> @@ -318,6 +359,25 @@ <option>crossfade</option>, replay gain </para> </listitem> + <listitem> + <para> + <returnvalue>sticker</returnvalue>: the sticker database + has been modified. + </para> + </listitem> + <listitem> + <para> + <returnvalue>subscription</returnvalue>: a client + has subscribed or unsubscribed to a channel + </para> + </listitem> + <listitem> + <para> + <returnvalue>message</returnvalue>: a message was + received on a channel this client is subscribed to; + this event is only emitted when the queue is empty + </para> + </listitem> </itemizedlist> <para> While a client is waiting for <command>idle</command> @@ -1073,6 +1133,46 @@ OK </para> </listitem> </varlistentry> + + <varlistentry id="command_prio"> + <term> + <cmdsynopsis> + <command>prio</command> + <arg choice="req"><replaceable>PRIORITY</replaceable></arg> + <arg choice="req" rep="repeat"><replaceable>START:END</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Set the priority of the specified songs. A higher + priority means that it will be played first when + "random" mode is enabled. + </para> + + <para> + A priority is an integer between 0 and 255. The default + priority of new songs is 0. + </para> + </listitem> + </varlistentry> + + <varlistentry id="command_prioid"> + <term> + <cmdsynopsis> + <command>prioid</command> + <arg choice="req"><replaceable>PRIORITY</replaceable></arg> + <arg choice="req" rep="repeat"><replaceable>ID</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Same as <link + linkend="command_prio"><command>prio</command></link>, + but address the songs with their id. + </para> + </listitem> + </varlistentry> + <varlistentry id="command_shuffle"> <term> <cmdsynopsis> @@ -1790,5 +1890,105 @@ suffix: mpc</programlisting> </varlistentry> </variablelist> </section> + + <section> + <title>Client to client</title> + + <para> + Clients can communicate with each others over "channels". A + channel is created by a client subscribing to it. More than + one client can be subscribed to a channel at a time; all of + them will receive the messages which get sent to it. + </para> + + <para> + Each time a client subscribes or unsubscribes, the global idle + event <varname>subscription</varname> is generated. In + conjunction with the <command>channels</command> command, this + may be used to auto-detect clients providing additional + services. + </para> + + <para> + A new messages is indicated by the <varname>message</varname> + idle event. + </para> + + <variablelist> + <varlistentry id="command_subscribe"> + <term> + <cmdsynopsis> + <command>subscribe</command> + <arg choice="req"><replaceable>NAME</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Subscribe to a channel. The channel is created if it + does not exist already. The name may consist of + alphanumeric ASCII characters plus underscore, dash, dot + and colon. + </para> + </listitem> + </varlistentry> + + <varlistentry id="command_unsubscribe"> + <term> + <cmdsynopsis> + <command>unsubscribe</command> + <arg choice="req"><replaceable>NAME</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Unsubscribe from a channel. + </para> + </listitem> + </varlistentry> + + <varlistentry id="command_channels"> + <term> + <cmdsynopsis> + <command>channels</command> + </cmdsynopsis> + </term> + <listitem> + <para> + Obtain a list of all channels. The response is a list + of "channel:" lines. + </para> + </listitem> + </varlistentry> + + <varlistentry id="command_readmessages"> + <term> + <cmdsynopsis> + <command>readmessages</command> + </cmdsynopsis> + </term> + <listitem> + <para> + Reads messages for this client. The response is a list + of "channel:" and "message:" lines. + </para> + </listitem> + </varlistentry> + + <varlistentry id="command_sendmessage"> + <term> + <cmdsynopsis> + <command>sendmessage</command> + <arg choice="req"><replaceable>CHANNEL</replaceable></arg> + <arg choice="req"><replaceable>TEXT</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Send a message to the specified channel. + </para> + </listitem> + </varlistentry> + </variablelist> + </section> </chapter> </book> diff --git a/doc/user.xml b/doc/user.xml index 6a9871007..4ca91da33 100644 --- a/doc/user.xml +++ b/doc/user.xml @@ -236,6 +236,16 @@ cd mpd-version</programlisting> </section> <section> + <title>Configuring encoder plugins</title> + + <para> + Encoders are used by some of the output plugins (such as + <varname>shout</varname>). The encoder settings are included + in the <varname>audio_output</varname> section. + </para> + </section> + + <section> <title>Configuring audio outputs</title> <para> @@ -346,7 +356,7 @@ cd mpd-version</programlisting> If set to "yes", then MPD attempts to keep this audio output always open. This may be useful for streaming servers, when you don't want to disconnect all - listeners even when playback is accidently stopped. + listeners even when playback is accidentally stopped. </entry> </row> <row> @@ -621,6 +631,106 @@ cd mpd-version</programlisting> Plays streams with the MMS protocol. </para> </section> + + <section> + <title><varname>cdio_paranoia</varname></title> + + <para> + Plays audio CDs. The URI has the form: + "<filename>cdda://[DEVICE][/TRACK]</filename>". The + simplest form <filename>cdda://</filename> plays the whole + disc in the default drive. + </para> + </section> + + <section> + <title><varname>despotify</varname></title> + + <para> + Plays <ulink url="http://www.spotify.com">Spotify</ulink> tracks using the despotify + library. The despotify plugin uses a <filename>spt://</filename> URI and a Spotify + URL. So for example, you can add a song with: + </para> + + <para> + <filename>mpc add spt://spotify:track:5qENVY0YEdZ7fiuOax70x1</filename> + </para> + + <para> + You need a Spotify premium account to use this plugin, and you need + to setup username and password in the configuration file. The + configuration settings are global since the despotify playlist plugin + use the same settings. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>despotify_user</varname> + </entry> + <entry> + Sets up the Spotify username (required) + </entry> + </row> + <row> + <entry> + <varname>despotify_password</varname> + </entry> + <entry> + Sets up the Spotify password (required) + </entry> + </row> + <row> + <entry> + <varname>despotify_high_bitrate</varname> + </entry> + <entry> + Set up if high bitrate should be used for Spotify tunes. + High bitrate sounds better but slow systems can have problems + with playback (default yes). + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>soup</varname></title> + + <para> + Opens remote files or streams over HTTP. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>proxy</varname> + </entry> + <entry> + Sets the address of the HTTP proxy server. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> </section> <section> @@ -658,6 +768,178 @@ cd mpd-version</programlisting> </section> <section> + <title>Encoder plugins</title> + + <section> + <title><varname>flac</varname></title> + + <para> + Encodes into FLAC (lossless). + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>compression</varname> + </entry> + <entry> + Sets the <filename>libFLAC</filename> compression + level. The levels range from 0 (fastest, least + compression) to 8 (slowest, most compression). + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>lame</varname></title> + + <para> + Encodes into MP3 using the LAME library. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>quality</varname> + </entry> + <entry> + Sets the quality for VBR. 0 is the highest quality, + 9 is the lowest quality. Cannot be used with + <varname>bitrate</varname>. + </entry> + </row> + <row> + <entry> + <varname>bitrate</varname> + </entry> + <entry> + Sets the bit rate in kilobit per second. Cannot be + used with <varname>quality</varname>. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>null</varname></title> + + <para> + Does not encode anything, passes the input PCM data as-is. + </para> + </section> + + <section> + <title><varname>twolame</varname></title> + + <para> + Encodes into MP2 using the <filename>twolame</filename> + library. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>quality</varname> + </entry> + <entry> + Sets the quality for VBR. 0 is the highest quality, + 9 is the lowest quality. Cannot be used with + <varname>bitrate</varname>. + </entry> + </row> + <row> + <entry> + <varname>bitrate</varname> + </entry> + <entry> + Sets the bit rate in kilobit per second. Cannot be + used with <varname>quality</varname>. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>vorbis</varname></title> + + <para> + Encodes into Ogg Vorbis. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>quality</varname> + </entry> + <entry> + Sets the quality for VBR. -1 is the lowest quality, + 10 is the highest quality. Cannot be used with + <varname>bitrate</varname>. + </entry> + </row> + <row> + <entry> + <varname>bitrate</varname> + </entry> + <entry> + Sets the bit rate in kilobit per second. Cannot be + used with <varname>quality</varname>. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>wave</varname></title> + + <para> + Encodes into WAV (lossless). + </para> + </section> + </section> + + <section> <title>Output plugins</title> <section> @@ -1366,6 +1648,15 @@ cd mpd-version</programlisting> </row> <row> <entry> + <varname>url</varname> + <parameter>URL</parameter> + </entry> + <entry> + Sets a URL associated with the stream (optional). + </entry> + </row> + <row> + <entry> <varname>public</varname> <parameter>yes|no</parameter> </entry> @@ -1498,6 +1789,27 @@ cd mpd-version</programlisting> playlist files. </para> </section> + + <section> + <title><varname>despotify</varname></title> + + <para> + Adds <ulink url="http://www.spotify.com/">Spotify</ulink> + playlists. Spotify playlists use the <filename>spt://</filename> URI, + and a Spotify playlist URL. So for example, you can load a playlist + with + </para> + + <para> + <filename>mpc load spt://spotify:user:simon.kagstrom:playlist:3SUwkOe5VbVHysZcidEZtH</filename> + </para> + + <para> + See the despotify input plugin for configuration options (username + and password needs to be setup) + </para> + </section> + </section> </chapter> </book> @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/aiff.c b/src/aiff.c index e2ca0dfe4..35b716eef 100644 --- a/src/aiff.c +++ b/src/aiff.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/aiff.h b/src/aiff.h index 52c0a73ec..a0ae2d41a 100644 --- a/src/aiff.h +++ b/src/aiff.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive/bz2_archive_plugin.c b/src/archive/bz2_archive_plugin.c index 2414eb519..7a3202015 100644 --- a/src/archive/bz2_archive_plugin.c +++ b/src/archive/bz2_archive_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive/bz2_archive_plugin.h b/src/archive/bz2_archive_plugin.h index 199049008..46c69a66c 100644 --- a/src/archive/bz2_archive_plugin.h +++ b/src/archive/bz2_archive_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive/iso9660_archive_plugin.c b/src/archive/iso9660_archive_plugin.c index 142fa10e0..78f184262 100644 --- a/src/archive/iso9660_archive_plugin.c +++ b/src/archive/iso9660_archive_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive/iso9660_archive_plugin.h b/src/archive/iso9660_archive_plugin.h index 2a3864cee..47dc6e474 100644 --- a/src/archive/iso9660_archive_plugin.h +++ b/src/archive/iso9660_archive_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive/zzip_archive_plugin.c b/src/archive/zzip_archive_plugin.c index 3c2b80318..62af6e221 100644 --- a/src/archive/zzip_archive_plugin.c +++ b/src/archive/zzip_archive_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive/zzip_archive_plugin.h b/src/archive/zzip_archive_plugin.h index 6d5037eef..2b2c01e5a 100644 --- a/src/archive/zzip_archive_plugin.h +++ b/src/archive/zzip_archive_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive_api.c b/src/archive_api.c index b15810f1b..be3c35f7e 100644 --- a/src/archive_api.c +++ b/src/archive_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive_api.h b/src/archive_api.h index f08960c72..4e0f603f5 100644 --- a/src/archive_api.h +++ b/src/archive_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive_internal.h b/src/archive_internal.h index 03439e826..0d885e91c 100644 --- a/src/archive_internal.h +++ b/src/archive_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive_list.c b/src/archive_list.c index 2656726b5..24aa060c9 100644 --- a/src/archive_list.c +++ b/src/archive_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ #include "config.h" #include "archive_list.h" #include "archive_plugin.h" -#include "utils.h" +#include "string_util.h" #include "archive/bz2_archive_plugin.h" #include "archive/iso9660_archive_plugin.h" #include "archive/zzip_archive_plugin.h" diff --git a/src/archive_list.h b/src/archive_list.h index b65245ce9..24e4063bf 100644 --- a/src/archive_list.h +++ b/src/archive_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive_plugin.c b/src/archive_plugin.c index 60da4d283..e73035053 100644 --- a/src/archive_plugin.c +++ b/src/archive_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive_plugin.h b/src/archive_plugin.h index b08c93389..7f038486b 100644 --- a/src/archive_plugin.h +++ b/src/archive_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -68,7 +68,7 @@ struct archive_plugin { * Opens an input_stream of a file within the archive. * * @param path the path within the archive - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, or * NULL to ignore errors */ struct input_stream *(*open_stream)(struct archive_file *af, diff --git a/src/audio.c b/src/audio.c index f9894cf3c..22879fc79 100644 --- a/src/audio.c +++ b/src/audio.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/audio.h b/src/audio.h index cb3ab7bbe..61835d4d7 100644 --- a/src/audio.h +++ b/src/audio.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/audio_check.c b/src/audio_check.c index 61d2c5833..a9aa2dd82 100644 --- a/src/audio_check.c +++ b/src/audio_check.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/audio_check.h b/src/audio_check.h index 4862e7f15..1d0bc1ded 100644 --- a/src/audio_check.h +++ b/src/audio_check.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/audio_format.c b/src/audio_format.c index 13403fbc1..799e0dd89 100644 --- a/src/audio_format.c +++ b/src/audio_format.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/audio_format.h b/src/audio_format.h index a4450ad71..1a54a092a 100644 --- a/src/audio_format.h +++ b/src/audio_format.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/audio_parser.c b/src/audio_parser.c index 139cf1c04..80bf9a5d7 100644 --- a/src/audio_parser.c +++ b/src/audio_parser.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/audio_parser.h b/src/audio_parser.h index 214ec5eb1..a963eb467 100644 --- a/src/audio_parser.h +++ b/src/audio_parser.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -38,7 +38,7 @@ struct audio_format; * @param dest the destination #audio_format struct * @param src the input string * @param mask if true, then "*" is allowed for any number of items - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success */ diff --git a/src/buffer.c b/src/buffer.c index bee871700..559f39a9a 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/buffer.h b/src/buffer.h index 75e5bc6e6..f860231e7 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/check.h b/src/check.h index 56061621f..0642a4b91 100644 --- a/src/check.h +++ b/src/check.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/chunk.c b/src/chunk.c index 79597506d..1eb96f4b9 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/chunk.h b/src/chunk.h index 02e7b3650..a06a203eb 100644 --- a/src/chunk.h +++ b/src/chunk.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/client.c b/src/client.c index 9668c9249..3fa2c9be4 100644 --- a/src/client.c +++ b/src/client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/client.h b/src/client.h index d46747b4f..5647e5eae 100644 --- a/src/client.h +++ b/src/client.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,11 +27,13 @@ struct client; struct sockaddr; +struct player_control; void client_manager_init(void); void client_manager_deinit(void); -void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid); +void client_new(struct player_control *player_control, + int fd, const struct sockaddr *sa, size_t sa_length, int uid); bool client_is_expired(const struct client *client); @@ -60,17 +62,4 @@ void client_vprintf(struct client *client, const char *fmt, va_list args); */ G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...); -/** - * Adds the specified idle flags to all clients and immediately sends - * notifications to all waiting clients. - */ -void client_manager_idle_add(unsigned flags); - -/** - * Checks whether the client has pending idle flags. If yes, they are - * sent immediately and "true" is returned". If no, it puts the - * client into waiting mode and returns false. - */ -bool client_idle_wait(struct client *client, unsigned flags); - #endif diff --git a/src/client_event.c b/src/client_event.c index 93f5a9df7..4f54ae0a7 100644 --- a/src/client_event.c +++ b/src/client_event.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/client_expire.c b/src/client_expire.c index a5b0be047..1ca32ebcc 100644 --- a/src/client_expire.c +++ b/src/client_expire.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/client_global.c b/src/client_global.c index fc5adedba..adf3b2f9e 100644 --- a/src/client_global.c +++ b/src/client_global.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/client_idle.c b/src/client_idle.c index 10be4d430..930911d6e 100644 --- a/src/client_idle.c +++ b/src/client_idle.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "client_idle.h" #include "client_internal.h" #include "idle.h" @@ -50,12 +51,9 @@ client_idle_notify(struct client *client) g_timer_start(client->last_activity); } -static void -client_idle_callback(gpointer data, gpointer user_data) +void +client_idle_add(struct client *client, unsigned flags) { - struct client *client = data; - unsigned flags = GPOINTER_TO_UINT(user_data); - if (client_is_expired(client)) return; @@ -67,6 +65,15 @@ client_idle_callback(gpointer data, gpointer user_data) } } +static void +client_idle_callback(gpointer data, gpointer user_data) +{ + struct client *client = data; + unsigned flags = GPOINTER_TO_UINT(user_data); + + client_idle_add(client, flags); +} + void client_manager_idle_add(unsigned flags) { assert(flags != 0); diff --git a/src/client_idle.h b/src/client_idle.h new file mode 100644 index 000000000..c56fd014c --- /dev/null +++ b/src/client_idle.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_IDLE_H +#define MPD_CLIENT_IDLE_H + +#include <stdbool.h> + +struct client; + +void +client_idle_add(struct client *client, unsigned flags); + +/** + * Adds the specified idle flags to all clients and immediately sends + * notifications to all waiting clients. + */ +void +client_manager_idle_add(unsigned flags); + +/** + * Checks whether the client has pending idle flags. If yes, they are + * sent immediately and "true" is returned". If no, it puts the + * client into waiting mode and returns false. + */ +bool +client_idle_wait(struct client *client, unsigned flags); + +#endif diff --git a/src/client_internal.h b/src/client_internal.h index 2b1b92433..ba97e4b8f 100644 --- a/src/client_internal.h +++ b/src/client_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,17 +21,25 @@ #define MPD_CLIENT_INTERNAL_H #include "client.h" +#include "client_message.h" #include "command.h" #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "client" +enum { + CLIENT_MAX_SUBSCRIPTIONS = 16, + CLIENT_MAX_MESSAGES = 64, +}; + struct deferred_buffer { size_t size; char data[sizeof(long)]; }; struct client { + struct player_control *player_control; + GIOChannel *channel; guint source_id; @@ -67,6 +75,28 @@ struct client { /** idle flags that the client wants to receive */ unsigned idle_subscriptions; + + /** + * A list of channel names this client is subscribed to. + */ + GSList *subscriptions; + + /** + * The number of subscriptions in #subscriptions. Used to + * limit the number of subscriptions. + */ + unsigned num_subscriptions; + + /** + * A list of messages this client has received in reverse + * order (latest first). + */ + GSList *messages; + + /** + * The number of messages in #messages. + */ + unsigned num_messages; }; extern unsigned int client_max_connections; diff --git a/src/client_list.c b/src/client_list.c index 5332ed65f..2c7f37aff 100644 --- a/src/client_list.c +++ b/src/client_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/client_message.c b/src/client_message.c new file mode 100644 index 000000000..b681b4e7f --- /dev/null +++ b/src/client_message.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "client_message.h" + +#include <assert.h> +#include <glib.h> + +G_GNUC_PURE +static bool +valid_channel_char(const char ch) +{ + return g_ascii_isalnum(ch) || + ch == '_' || ch == '-' || ch == '.' || ch == ':'; +} + +bool +client_message_valid_channel_name(const char *name) +{ + do { + if (!valid_channel_char(*name)) + return false; + } while (*++name != 0); + + return true; +} + +void +client_message_init_null(struct client_message *msg) +{ + assert(msg != NULL); + + msg->channel = NULL; + msg->message = NULL; +} + +void +client_message_init(struct client_message *msg, + const char *channel, const char *message) +{ + assert(msg != NULL); + + msg->channel = g_strdup(channel); + msg->message = g_strdup(message); +} + +void +client_message_copy(struct client_message *dest, + const struct client_message *src) +{ + assert(dest != NULL); + assert(src != NULL); + assert(client_message_defined(src)); + + client_message_init(dest, src->channel, src->message); +} + +struct client_message * +client_message_dup(const struct client_message *src) +{ + struct client_message *dest = g_slice_new(struct client_message); + client_message_copy(dest, src); + return dest; +} + +void +client_message_deinit(struct client_message *msg) +{ + assert(msg != NULL); + + g_free(msg->channel); + g_free(msg->message); +} + +void +client_message_free(struct client_message *msg) +{ + client_message_deinit(msg); + g_slice_free(struct client_message, msg); +} diff --git a/src/client_message.h b/src/client_message.h new file mode 100644 index 000000000..5c7e86c15 --- /dev/null +++ b/src/client_message.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_MESSAGE_H +#define MPD_CLIENT_MESSAGE_H + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <glib.h> + +/** + * A client-to-client message. + */ +struct client_message { + char *channel; + + char *message; +}; + +G_GNUC_PURE +bool +client_message_valid_channel_name(const char *name); + +G_GNUC_PURE +static inline bool +client_message_defined(const struct client_message *msg) +{ + assert(msg != NULL); + assert((msg->channel == NULL) == (msg->message == NULL)); + + return msg->channel != NULL; +} + +void +client_message_init_null(struct client_message *msg); + +void +client_message_init(struct client_message *msg, + const char *channel, const char *message); + +void +client_message_copy(struct client_message *dest, + const struct client_message *src); + +G_GNUC_MALLOC G_GNUC_PURE +struct client_message * +client_message_dup(const struct client_message *src); + +void +client_message_deinit(struct client_message *msg); + +void +client_message_free(struct client_message *msg); + +#endif diff --git a/src/client_new.c b/src/client_new.c index 781a36524..5b2dfde65 100644 --- a/src/client_new.c +++ b/src/client_new.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -41,12 +41,15 @@ static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; -void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid) +void +client_new(struct player_control *player_control, + int fd, const struct sockaddr *sa, size_t sa_length, int uid) { static unsigned int next_client_num; struct client *client; char *remote; + assert(player_control != NULL); assert(fd >= 0); #ifdef HAVE_LIBWRAP @@ -81,6 +84,7 @@ void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid) } client = g_new0(struct client, 1); + client->player_control = player_control; #ifndef G_OS_WIN32 client->channel = g_io_channel_unix_new(fd); @@ -117,6 +121,10 @@ void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid) client->send_buf_used = 0; + client->subscriptions = NULL; + client->messages = NULL; + client->num_messages = 0; + (void)send(fd, GREETING, sizeof(GREETING) - 1, 0); client_list_add(client); diff --git a/src/client_process.c b/src/client_process.c index aeb75bb57..57a8a7824 100644 --- a/src/client_process.c +++ b/src/client_process.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/client_read.c b/src/client_read.c index 7a6bd3d5e..26ade264e 100644 --- a/src/client_read.c +++ b/src/client_read.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/client_subscribe.c b/src/client_subscribe.c new file mode 100644 index 000000000..c65a7ed31 --- /dev/null +++ b/src/client_subscribe.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "client_subscribe.h" +#include "client_internal.h" +#include "client_idle.h" +#include "idle.h" + +#include <string.h> + +G_GNUC_PURE +static GSList * +client_find_subscription(const struct client *client, const char *channel) +{ + for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) + if (strcmp((const char *)i->data, channel) == 0) + return i; + + return NULL; +} + +enum client_subscribe_result +client_subscribe(struct client *client, const char *channel) +{ + assert(client != NULL); + assert(channel != NULL); + + if (!client_message_valid_channel_name(channel)) + return CLIENT_SUBSCRIBE_INVALID; + + if (client_find_subscription(client, channel) != NULL) + return CLIENT_SUBSCRIBE_ALREADY; + + if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) + return CLIENT_SUBSCRIBE_FULL; + + client->subscriptions = g_slist_prepend(client->subscriptions, + g_strdup(channel)); + ++client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + return CLIENT_SUBSCRIBE_OK; +} + +bool +client_unsubscribe(struct client *client, const char *channel) +{ + GSList *i = client_find_subscription(client, channel); + if (i == NULL) + return false; + + assert(client->num_subscriptions > 0); + + client->subscriptions = g_slist_remove(client->subscriptions, i->data); + --client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + assert((client->num_subscriptions == 0) == + (client->subscriptions == NULL)); + + return true; +} + +void +client_unsubscribe_all(struct client *client) +{ + for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) + g_free(i->data); + + g_slist_free(client->subscriptions); + client->subscriptions = NULL; + client->num_subscriptions = 0; +} + +bool +client_push_message(struct client *client, const struct client_message *msg) +{ + assert(client != NULL); + assert(msg != NULL); + assert(client_message_defined(msg)); + + if (client->num_messages >= CLIENT_MAX_MESSAGES || + client_find_subscription(client, msg->channel) == NULL) + return false; + + if (client->messages == NULL) + client_idle_add(client, IDLE_MESSAGE); + + client->messages = g_slist_prepend(client->messages, + client_message_dup(msg)); + ++client->num_messages; + + return true; +} + +GSList * +client_read_messages(struct client *client) +{ + GSList *messages = g_slist_reverse(client->messages); + + client->messages = NULL; + client->num_messages = 0; + + return messages; +} diff --git a/src/client_subscribe.h b/src/client_subscribe.h new file mode 100644 index 000000000..09f864417 --- /dev/null +++ b/src/client_subscribe.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_SUBSCRIBE_H +#define MPD_CLIENT_SUBSCRIBE_H + +#include <stdbool.h> +#include <glib.h> + +struct client; +struct client_message; + +enum client_subscribe_result { + /** success */ + CLIENT_SUBSCRIBE_OK, + + /** invalid channel name */ + CLIENT_SUBSCRIBE_INVALID, + + /** already subscribed to this channel */ + CLIENT_SUBSCRIBE_ALREADY, + + /** too many subscriptions */ + CLIENT_SUBSCRIBE_FULL, +}; + +enum client_subscribe_result +client_subscribe(struct client *client, const char *channel); + +bool +client_unsubscribe(struct client *client, const char *channel); + +void +client_unsubscribe_all(struct client *client); + +bool +client_push_message(struct client *client, const struct client_message *msg); + +G_GNUC_MALLOC +GSList * +client_read_messages(struct client *client); + +#endif diff --git a/src/client_write.c b/src/client_write.c index 543cdbb6c..78cfca8a1 100644 --- a/src/client_write.c +++ b/src/client_write.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/cmdline.c b/src/cmdline.c index 2c1db890b..45e361e71 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -79,7 +79,7 @@ static void version(void) puts(PACKAGE " (MPD: Music Player Daemon) " VERSION " \n" "\n" "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n" - "Copyright (C) 2008-2010 Max Kellermann <max@duempel.org>\n" + "Copyright (C) 2008-2011 Max Kellermann <max@duempel.org>\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" "\n" diff --git a/src/cmdline.h b/src/cmdline.h index b7af63c5a..68f625a6c 100644 --- a/src/cmdline.h +++ b/src/cmdline.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command.c b/src/command.c index 64f161805..3e60e0ac3 100644 --- a/src/command.c +++ b/src/command.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -44,6 +44,9 @@ #include "dbUtils.h" #include "tag.h" #include "client.h" +#include "client_idle.h" +#include "client_internal.h" +#include "client_subscribe.h" #include "tag_print.h" #include "path.h" #include "replay_gain_config.h" @@ -434,7 +437,7 @@ handle_play(struct client *client, int argc, char *argv[]) if (argc == 2 && !check_int(client, &song, argv[1], need_positive)) return COMMAND_RETURN_ERROR; - result = playlist_play(&g_playlist, song); + result = playlist_play(&g_playlist, client->player_control, song); return print_playlist_result(client, result); } @@ -447,7 +450,7 @@ handle_playid(struct client *client, int argc, char *argv[]) if (argc == 2 && !check_int(client, &id, argv[1], need_positive)) return COMMAND_RETURN_ERROR; - result = playlist_play_id(&g_playlist, id); + result = playlist_play_id(&g_playlist, client->player_control, id); return print_playlist_result(client, result); } @@ -455,7 +458,7 @@ static enum command_return handle_stop(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - playlist_stop(&g_playlist); + playlist_stop(&g_playlist, client->player_control); return COMMAND_RETURN_OK; } @@ -476,9 +479,9 @@ handle_pause(struct client *client, if (!check_bool(client, &pause_flag, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_pause(pause_flag); + pc_set_pause(client->player_control, pause_flag); } else - pc_pause(); + pc_pause(client->player_control); return COMMAND_RETURN_OK; } @@ -493,7 +496,7 @@ handle_status(struct client *client, char *error; int song; - pc_get_status(&player_status); + pc_get_status(client->player_control, &player_status); switch (player_status.state) { case PLAYER_STATE_STOP: @@ -526,9 +529,9 @@ handle_status(struct client *client, playlist_get_consume(&g_playlist), playlist_get_version(&g_playlist), playlist_get_length(&g_playlist), - (int)(pc_get_cross_fade() + 0.5), - pc_get_mixramp_db(), - pc_get_mixramp_delay(), + (int)(pc_get_cross_fade(client->player_control) + 0.5), + pc_get_mixramp_db(client->player_control), + pc_get_mixramp_delay(client->player_control), state); song = playlist_get_current_song(&g_playlist); @@ -561,7 +564,7 @@ handle_status(struct client *client, updateJobId); } - error = pc_get_error_message(); + error = pc_get_error_message(client->player_control); if (error != NULL) { client_printf(client, COMMAND_STATUS_ERROR ": %s\n", @@ -605,6 +608,7 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) result = PLAYLIST_RESULT_DENIED; #else result = playlist_append_file(&g_playlist, + client->player_control, uri + 7, client_get_uid(client), NULL); #endif @@ -618,11 +622,13 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - result = playlist_append_uri(&g_playlist, uri, NULL); + result = playlist_append_uri(&g_playlist, + client->player_control, + uri, NULL); return print_playlist_result(client, result); } - result = addAllIn(uri); + result = addAllIn(client->player_control, uri); if (result == (enum playlist_result)-1) { command_error(client, ACK_ERROR_NO_EXIST, "directory or file not found"); @@ -643,7 +649,9 @@ handle_addid(struct client *client, int argc, char *argv[]) #ifdef WIN32 result = PLAYLIST_RESULT_DENIED; #else - result = playlist_append_file(&g_playlist, uri + 7, + result = playlist_append_file(&g_playlist, + client->player_control, + uri + 7, client_get_uid(client), &added_id); #endif @@ -654,7 +662,9 @@ handle_addid(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - result = playlist_append_uri(&g_playlist, uri, &added_id); + result = playlist_append_uri(&g_playlist, + client->player_control, + uri, &added_id); } if (result != PLAYLIST_RESULT_SUCCESS) @@ -664,11 +674,13 @@ handle_addid(struct client *client, int argc, char *argv[]) int to; if (!check_int(client, &to, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, added_id, to); + result = playlist_move_id(&g_playlist, client->player_control, + added_id, to); if (result != PLAYLIST_RESULT_SUCCESS) { enum command_return ret = print_playlist_result(client, result); - playlist_delete_id(&g_playlist, added_id); + playlist_delete_id(&g_playlist, client->player_control, + added_id); return ret; } } @@ -686,7 +698,8 @@ handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_range(client, &start, &end, argv[1], need_range)) return COMMAND_RETURN_ERROR; - result = playlist_delete_range(&g_playlist, start, end); + result = playlist_delete_range(&g_playlist, client->player_control, + start, end); return print_playlist_result(client, result); } @@ -699,7 +712,7 @@ handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &id, argv[1], need_positive)) return COMMAND_RETURN_ERROR; - result = playlist_delete_id(&g_playlist, id); + result = playlist_delete_id(&g_playlist, client->player_control, id); return print_playlist_result(client, result); } @@ -720,7 +733,7 @@ handle_shuffle(G_GNUC_UNUSED struct client *client, argv[1], need_range)) return COMMAND_RETURN_ERROR; - playlist_shuffle(&g_playlist, start, end); + playlist_shuffle(&g_playlist, client->player_control, start, end); return COMMAND_RETURN_OK; } @@ -728,7 +741,7 @@ static enum command_return handle_clear(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - playlist_clear(&g_playlist); + playlist_clear(&g_playlist, client->player_control); return COMMAND_RETURN_OK; } @@ -747,11 +760,13 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { enum playlist_result result; - result = playlist_open_into_queue(argv[1], &g_playlist); + result = playlist_open_into_queue(argv[1], &g_playlist, + client->player_control, true); if (result != PLAYLIST_RESULT_NO_SUCH_LIST) return print_playlist_result(client, result); - result = playlist_load_spl(&g_playlist, argv[1]); + result = playlist_load_spl(&g_playlist, client->player_control, + argv[1]); return print_playlist_result(client, result); } @@ -1141,7 +1156,7 @@ handle_next(G_GNUC_UNUSED struct client *client, int single = g_playlist.queue.single; g_playlist.queue.single = false; - playlist_next(&g_playlist); + playlist_next(&g_playlist, client->player_control); g_playlist.queue.single = single; return COMMAND_RETURN_OK; @@ -1151,7 +1166,69 @@ static enum command_return handle_previous(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - playlist_previous(&g_playlist); + playlist_previous(&g_playlist, client->player_control); + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_prio(struct client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned start_position, end_position; + if (!check_range(client, &start_position, &end_position, + argv[i], need_range)) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + playlist_set_priority(&g_playlist, + client->player_control, + start_position, end_position, + priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_prioid(struct client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + playlist_set_priority_id(&g_playlist, + client->player_control, + song_id, priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + return COMMAND_RETURN_OK; } @@ -1210,7 +1287,7 @@ handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - playlist_set_repeat(&g_playlist, status); + playlist_set_repeat(&g_playlist, client->player_control, status); return COMMAND_RETURN_OK; } @@ -1228,7 +1305,7 @@ handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - playlist_set_single(&g_playlist, status); + playlist_set_single(&g_playlist, client->player_control, status); return COMMAND_RETURN_OK; } @@ -1264,7 +1341,7 @@ handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - playlist_set_random(&g_playlist, status); + playlist_set_random(&g_playlist, client->player_control, status); return COMMAND_RETURN_OK; } @@ -1279,7 +1356,7 @@ static enum command_return handle_clearerror(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - pc_clear_error(); + pc_clear_error(client->player_control); return COMMAND_RETURN_OK; } @@ -1348,7 +1425,8 @@ handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; if (!check_int(client, &to, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_move_range(&g_playlist, start, end, to); + result = playlist_move_range(&g_playlist, client->player_control, + start, end, to); return print_playlist_result(client, result); } @@ -1362,7 +1440,8 @@ handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; if (!check_int(client, &to, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, id, to); + result = playlist_move_id(&g_playlist, client->player_control, + id, to); return print_playlist_result(client, result); } @@ -1376,7 +1455,8 @@ handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; if (!check_int(client, &song2, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_swap_songs(&g_playlist, song1, song2); + result = playlist_swap_songs(&g_playlist, client->player_control, + song1, song2); return print_playlist_result(client, result); } @@ -1390,7 +1470,8 @@ handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; if (!check_int(client, &id2, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_swap_songs_id(&g_playlist, id1, id2); + result = playlist_swap_songs_id(&g_playlist, client->player_control, + id1, id2); return print_playlist_result(client, result); } @@ -1405,7 +1486,8 @@ handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &seek_time, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_seek_song(&g_playlist, song, seek_time); + result = playlist_seek_song(&g_playlist, client->player_control, + song, seek_time); return print_playlist_result(client, result); } @@ -1420,7 +1502,8 @@ handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &seek_time, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_seek_song_id(&g_playlist, id, seek_time); + result = playlist_seek_song_id(&g_playlist, client->player_control, + id, seek_time); return print_playlist_result(client, result); } @@ -1470,7 +1553,7 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_unsigned(client, &xfade_time, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_cross_fade(xfade_time); + pc_set_cross_fade(client->player_control, xfade_time); return COMMAND_RETURN_OK; } @@ -1482,7 +1565,7 @@ handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_float(client, &db, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_mixramp_db(db); + pc_set_mixramp_db(client->player_control, db); return COMMAND_RETURN_OK; } @@ -1494,7 +1577,7 @@ handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_float(client, &delay_secs, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_mixramp_delay(delay_secs); + pc_set_mixramp_delay(client->player_control, delay_secs); return COMMAND_RETURN_OK; } @@ -1817,6 +1900,172 @@ handle_sticker(struct client *client, int argc, char *argv[]) } #endif +static enum command_return +handle_subscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + switch (client_subscribe(client, argv[1])) { + case CLIENT_SUBSCRIBE_OK: + return COMMAND_RETURN_OK; + + case CLIENT_SUBSCRIBE_INVALID: + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_ALREADY: + command_error(client, ACK_ERROR_EXIST, + "already subscribed to this channel"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_FULL: + command_error(client, ACK_ERROR_EXIST, + "subscription list is full"); + return COMMAND_RETURN_ERROR; + } + + /* unreachable */ + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_unsubscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + if (client_unsubscribe(client, argv[1])) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "not subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} + +struct channels_context { + GStringChunk *chunk; + + GHashTable *channels; +}; + +static void +collect_channels(gpointer data, gpointer user_data) +{ + struct channels_context *context = user_data; + const struct client *client = data; + + for (GSList *i = client->subscriptions; i != NULL; + i = g_slist_next(i)) { + const char *channel = i->data; + + if (g_hash_table_lookup(context->channels, channel) == NULL) { + char *channel2 = g_string_chunk_insert(context->chunk, + channel); + g_hash_table_insert(context->channels, channel2, + context); + } + } +} + +static void +print_channel(gpointer key, G_GNUC_UNUSED gpointer value, gpointer user_data) +{ + struct client *client = user_data; + const char *channel = key; + + client_printf(client, "channel: %s\n", channel); +} + +static enum command_return +handle_channels(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + struct channels_context context = { + .chunk = g_string_chunk_new(1024), + .channels = g_hash_table_new(g_str_hash, g_str_equal), + }; + + client_list_foreach(collect_channels, &context); + + g_hash_table_foreach(context.channels, print_channel, client); + + g_hash_table_destroy(context.channels); + g_string_chunk_free(context.chunk); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_read_messages(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + GSList *messages = client_read_messages(client); + + for (GSList *i = messages; i != NULL; i = g_slist_next(i)) { + struct client_message *msg = i->data; + + client_printf(client, "channel: %s\nmessage: %s\n", + msg->channel, msg->message); + client_message_free(msg); + } + + g_slist_free(messages); + + return COMMAND_RETURN_OK; +} + +struct send_message_context { + struct client_message msg; + + bool sent; +}; + +static void +send_message(gpointer data, gpointer user_data) +{ + struct send_message_context *context = user_data; + struct client *client = data; + + if (client_push_message(client, &context->msg)) + context->sent = true; +} + +static enum command_return +handle_send_message(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 3); + + if (!client_message_valid_channel_name(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + } + + struct send_message_context context = { + .sent = false, + }; + + client_message_init(&context.msg, argv[1], argv[2]); + + client_list_foreach(send_message, &context); + + client_message_deinit(&context.msg); + + if (context.sent) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "nobody is subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} + /** * The command registry. * @@ -1825,6 +2074,7 @@ handle_sticker(struct client *client, int argc, char *argv[]) static const struct command commands[] = { { "add", PERMISSION_ADD, 1, 1, handle_add }, { "addid", PERMISSION_ADD, 1, 2, handle_addid }, + { "channels", PERMISSION_READ, 0, 0, handle_channels }, { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, { "close", PERMISSION_NONE, -1, -1, handle_close }, @@ -1874,19 +2124,23 @@ static const struct command commands[] = { { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges }, { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid }, { "previous", PERMISSION_CONTROL, 0, 0, handle_previous }, + { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, + { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, { "random", PERMISSION_CONTROL, 1, 1, handle_random }, + { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, handle_replay_gain_mode }, { "replay_gain_status", PERMISSION_READ, 0, 0, handle_replay_gain_status }, - { "rescan", PERMISSION_ADMIN, 0, 1, handle_rescan }, + { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan }, { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, { "save", PERMISSION_CONTROL, 1, 1, handle_save }, { "search", PERMISSION_READ, 2, -1, handle_search }, { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, + { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message }, { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol }, { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle }, { "single", PERMISSION_CONTROL, 1, 1, handle_single }, @@ -1896,10 +2150,12 @@ static const struct command commands[] = { { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, #endif { "stop", PERMISSION_CONTROL, 0, 0, handle_stop }, + { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, - { "update", PERMISSION_ADMIN, 0, 1, handle_update }, + { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, + { "update", PERMISSION_CONTROL, 0, 1, handle_update }, { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, }; diff --git a/src/command.h b/src/command.h index 39389385d..68d1f95e4 100644 --- a/src/command.h +++ b/src/command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/conf.c b/src/conf.c index 14dac93a6..a79750477 100644 --- a/src/conf.c +++ b/src/conf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "conf.h" #include "utils.h" +#include "string_util.h" #include "tokenizer.h" #include "path.h" #include "glib_compat.h" @@ -58,6 +59,7 @@ static struct config_entry config_entries[] = { { .name = CONF_LOG_FILE, false, false }, { .name = CONF_PID_FILE, false, false }, { .name = CONF_STATE_FILE, false, false }, + { .name = "restore_paused", false, false }, { .name = CONF_USER, false, false }, { .name = CONF_GROUP, false, false }, { .name = CONF_BIND_TO_ADDRESS, true, false }, @@ -97,6 +99,9 @@ static struct config_entry config_entries[] = { { .name = CONF_PLAYLIST_PLUGIN, true, true }, { .name = CONF_AUTO_UPDATE, false, false }, { .name = CONF_AUTO_UPDATE_DEPTH, false, false }, + { .name = CONF_DESPOTIFY_USER, false, false }, + { .name = CONF_DESPOTIFY_PASSWORD, false, false}, + { .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false }, { .name = "filter", true, true }, }; @@ -269,7 +274,7 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r) } (*count)++; - line = g_strchug(line); + line = strchug_fast(line); if (*line == 0 || *line == CONF_COMMENT) continue; @@ -277,7 +282,7 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r) /* end of this block; return from the function (and from this "while" loop) */ - line = g_strchug(line + 1); + line = strchug_fast(line + 1); if (*line != 0 && *line != CONF_COMMENT) { config_param_free(ret); g_set_error(error_r, config_quark(), 0, @@ -355,7 +360,7 @@ config_read_file(const char *file, GError **error_r) count++; - line = g_strchug(string); + line = strchug_fast(string); if (*line == 0 || *line == CONF_COMMENT) continue; @@ -405,7 +410,7 @@ config_read_file(const char *file, GError **error_r) return false; } - line = g_strchug(line + 1); + line = strchug_fast(line + 1); if (*line != 0 && *line != CONF_COMMENT) { g_set_error(error_r, config_quark(), 0, "line %i: Unknown tokens after '{'", diff --git a/src/conf.h b/src/conf.h index 8a0678312..c26ff6958 100644 --- a/src/conf.h +++ b/src/conf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -72,6 +72,9 @@ #define CONF_PLAYLIST_PLUGIN "playlist_plugin" #define CONF_AUTO_UPDATE "auto_update" #define CONF_AUTO_UPDATE_DEPTH "auto_update_depth" +#define CONF_DESPOTIFY_USER "despotify_user" +#define CONF_DESPOTIFY_PASSWORD "despotify_password" +#define CONF_DESPOTIFY_HIGH_BITRATE "despotify_high_bitrate" #define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16) #define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false diff --git a/src/crossfade.c b/src/crossfade.c index cdfd82879..8bb2cde6b 100644 --- a/src/crossfade.c +++ b/src/crossfade.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/crossfade.h b/src/crossfade.h index 096a62020..d581dbfe0 100644 --- a/src/crossfade.h +++ b/src/crossfade.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/cue/cue_tag.c b/src/cue/cue_tag.c index ba1172559..6ee38bbd0 100644 --- a/src/cue/cue_tag.c +++ b/src/cue/cue_tag.c @@ -178,6 +178,15 @@ cue_tag(struct Cd *cd, unsigned tnum) if (tag == NULL) return NULL; + /* Create a tag number */ + + tag_clear_items_by_type(tag, TAG_TRACK); + + char convert_uinttostring[8]; + snprintf(convert_uinttostring, sizeof(convert_uinttostring), + "%02d/%02d", tnum, cd_get_ntrack(cd)); + tag_add_item(tag, TAG_TRACK, convert_uinttostring); + tag->time = track_get_length(track) - track_get_index(track, 1) + track_get_zero_pre(track); diff --git a/src/daemon.c b/src/daemon.c index 852541375..8bca9095a 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -80,7 +80,7 @@ daemonize_kill(void) ret = kill(pid, SIGTERM); if (ret < 0) - MPD_ERROR("unable to kill proccess %i: %s", + MPD_ERROR("unable to kill process %i: %s", pid, g_strerror(errno)); exit(EXIT_SUCCESS); diff --git a/src/daemon.h b/src/daemon.h index 71039543c..c43a74cfd 100644 --- a/src/daemon.h +++ b/src/daemon.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/database.c b/src/database.c index 9f29f95e1..660d9768a 100644 --- a/src/database.c +++ b/src/database.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/database.h b/src/database.h index 67149b20b..75233e3a2 100644 --- a/src/database.h +++ b/src/database.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/dbUtils.c b/src/dbUtils.c index f950d42cc..9a44b314b 100644 --- a/src/dbUtils.c +++ b/src/dbUtils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ #include "tag.h" #include "strset.h" #include "stored_playlist.h" +#include "client_internal.h" #include <glib.h> @@ -166,9 +167,11 @@ int printAllIn(struct client *client, const char *name) } static int -directoryAddSongToPlaylist(struct song *song, G_GNUC_UNUSED void *data) +directoryAddSongToPlaylist(struct song *song, void *data) { - return playlist_append_song(&g_playlist, song, NULL); + struct player_control *pc = data; + + return playlist_append_song(&g_playlist, pc, song, NULL); } struct add_data { @@ -185,9 +188,10 @@ directoryAddSongToStoredPlaylist(struct song *song, void *_data) return 0; } -int addAllIn(const char *name) +int +addAllIn(struct player_control *pc, const char *name) { - return db_walk(name, directoryAddSongToPlaylist, NULL, NULL); + return db_walk(name, directoryAddSongToPlaylist, NULL, pc); } int addAllInToStoredPlaylist(const char *name, const char *utf8file) @@ -205,7 +209,9 @@ findAddInDirectory(struct song *song, void *_data) struct search_data *data = _data; if (locate_song_match(song, data->criteria)) - return directoryAddSongToPlaylist(song, data); + return playlist_append_song(&g_playlist, + data->client->player_control, + song, NULL); return 0; } diff --git a/src/dbUtils.h b/src/dbUtils.h index bba253154..5de8af5c9 100644 --- a/src/dbUtils.h +++ b/src/dbUtils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,12 @@ struct client; struct locate_item_list; +struct player_control; int printAllIn(struct client *client, const char *name); -int addAllIn(const char *name); +int +addAllIn(struct player_control *pc, const char *name); int addAllInToStoredPlaylist(const char *name, const char *utf8file); diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c index 8dd22a253..fc42e5913 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/_flac_common.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h index 5c59ee123..1f4cbb273 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/_flac_common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/_ogg_common.c b/src/decoder/_ogg_common.c index bd0650ac4..bedd3de61 100644 --- a/src/decoder/_ogg_common.c +++ b/src/decoder/_ogg_common.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/_ogg_common.h b/src/decoder/_ogg_common.h index f8446c69c..85e4ebba6 100644 --- a/src/decoder/_ogg_common.h +++ b/src/decoder/_ogg_common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/audiofile_decoder_plugin.c b/src/decoder/audiofile_decoder_plugin.c index b099cf706..c862168f8 100644 --- a/src/decoder/audiofile_decoder_plugin.c +++ b/src/decoder/audiofile_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/faad_decoder_plugin.c b/src/decoder/faad_decoder_plugin.c index 8f932ad58..02c72a4a1 100644 --- a/src/decoder/faad_decoder_plugin.c +++ b/src/decoder/faad_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/ffmpeg_decoder_plugin.c b/src/decoder/ffmpeg_decoder_plugin.c index 70628df9d..78e874d9e 100644 --- a/src/decoder/ffmpeg_decoder_plugin.c +++ b/src/decoder/ffmpeg_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,22 +32,14 @@ #include <sys/stat.h> #include <unistd.h> -#ifdef OLD_FFMPEG_INCLUDES -#include <avcodec.h> -#include <avformat.h> -#include <avio.h> -#else #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> #include <libavutil/log.h> -#endif #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "ffmpeg" -#ifndef OLD_FFMPEG_INCLUDES - static GLogLevelFlags level_ffmpeg_to_glib(int level) { @@ -79,7 +71,6 @@ mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level, } } -#endif /* !OLD_FFMPEG_INCLUDES */ #ifndef AV_VERSION_INT #define AV_VERSION_INT(a, b, c) (a<<16 | b<<8 | c) @@ -126,11 +117,19 @@ mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input) struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1); stream->decoder = decoder; stream->input = input; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0) + stream->io = avio_alloc_context(stream->buffer, sizeof(stream->buffer), + false, stream, + mpd_ffmpeg_stream_read, NULL, + input->seekable + ? mpd_ffmpeg_stream_seek : NULL); +#else stream->io = av_alloc_put_byte(stream->buffer, sizeof(stream->buffer), false, stream, mpd_ffmpeg_stream_read, NULL, input->seekable ? mpd_ffmpeg_stream_seek : NULL); +#endif if (stream->io == NULL) { g_free(stream); return NULL; @@ -176,9 +175,7 @@ mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream) static bool ffmpeg_init(G_GNUC_UNUSED const struct config_param *param) { -#ifndef OLD_FFMPEG_INCLUDES av_log_set_callback(mpd_ffmpeg_log_callback); -#endif av_register_all(); return true; @@ -282,7 +279,6 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, static enum sample_format ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context) { -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(51, 41, 0) switch (codec_context->sample_fmt) { case SAMPLE_FMT_S16: return SAMPLE_FORMAT_S16; @@ -295,10 +291,6 @@ ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context) codec_context->sample_fmt); return SAMPLE_FORMAT_UNDEFINED; } -#else - /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */ - return SAMPLE_FORMAT_S16; -#endif } static AVInputFormat * @@ -449,7 +441,6 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input) mpd_ffmpeg_stream_close(stream); } -#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) typedef struct ffmpeg_tag_map { enum tag_type type; const char *name; @@ -500,8 +491,6 @@ ffmpeg_copy_metadata(struct tag *tag, return mt != NULL; } -#endif - //no tag reading in ffmpeg, check if playable static struct tag * ffmpeg_stream_tag(struct input_stream *is) @@ -533,8 +522,9 @@ ffmpeg_stream_tag(struct input_stream *is) ? f->duration / AV_TIME_BASE : 0; -#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,101,0) av_metadata_conv(f, NULL, f->iformat->metadata_conv); +#endif for (unsigned i = 0; i < sizeof(ffmpeg_tag_maps)/sizeof(ffmpeg_tag_map); i++) { int idx = ffmpeg_find_audio_stream(f); @@ -542,31 +532,6 @@ ffmpeg_stream_tag(struct input_stream *is) if (idx >= 0) ffmpeg_copy_metadata(tag, f->streams[idx]->metadata, ffmpeg_tag_maps[i]); } -#else - if (f->author[0]) - tag_add_item(tag, TAG_ARTIST, f->author); - if (f->title[0]) - tag_add_item(tag, TAG_TITLE, f->title); - if (f->album[0]) - tag_add_item(tag, TAG_ALBUM, f->album); - - if (f->track > 0) { - char buffer[16]; - snprintf(buffer, sizeof(buffer), "%d", f->track); - tag_add_item(tag, TAG_TRACK, buffer); - } - - if (f->comment[0]) - tag_add_item(tag, TAG_COMMENT, f->comment); - if (f->genre[0]) - tag_add_item(tag, TAG_GENRE, f->genre); - if (f->year > 0) { - char buffer[16]; - snprintf(buffer, sizeof(buffer), "%d", f->year); - tag_add_item(tag, TAG_DATE, buffer); - } - -#endif av_close_input_stream(f); mpd_ffmpeg_stream_close(stream); diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h index d597690a0..9a30acc26 100644 --- a/src/decoder/flac_compat.h +++ b/src/decoder/flac_compat.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/flac_decoder_plugin.c index 9d980b79d..ca9cd5968 100644 --- a/src/decoder/flac_decoder_plugin.c +++ b/src/decoder/flac_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c index 5b94fd426..a19220572 100644 --- a/src/decoder/flac_metadata.c +++ b/src/decoder/flac_metadata.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h index e52b0fb82..01bc1924a 100644 --- a/src/decoder/flac_metadata.h +++ b/src/decoder/flac_metadata.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c index bf6e2612c..3b56d50bd 100644 --- a/src/decoder/flac_pcm.c +++ b/src/decoder/flac_pcm.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/flac_pcm.h b/src/decoder/flac_pcm.h index bccfc645c..a931998c1 100644 --- a/src/decoder/flac_pcm.h +++ b/src/decoder/flac_pcm.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/fluidsynth_decoder_plugin.c b/src/decoder/fluidsynth_decoder_plugin.c index b9a2d0d99..814a7b554 100644 --- a/src/decoder/fluidsynth_decoder_plugin.c +++ b/src/decoder/fluidsynth_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -102,7 +102,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs) fluid_player_t *player; char *path_dup; int ret; - Timer *timer; + struct timer *timer; enum decoder_command cmd; soundfont_path = diff --git a/src/decoder/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c index 2c2906c5c..8f77052f7 100644 --- a/src/decoder/mad_decoder_plugin.c +++ b/src/decoder/mad_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/mikmod_decoder_plugin.c b/src/decoder/mikmod_decoder_plugin.c index 91478e86f..9dd5a79b6 100644 --- a/src/decoder/mikmod_decoder_plugin.c +++ b/src/decoder/mikmod_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/modplug_decoder_plugin.c b/src/decoder/modplug_decoder_plugin.c index 037c2fd74..341b00927 100644 --- a/src/decoder/modplug_decoder_plugin.c +++ b/src/decoder/modplug_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/mp4ff_decoder_plugin.c b/src/decoder/mp4ff_decoder_plugin.c index 861b08194..38ae5793a 100644 --- a/src/decoder/mp4ff_decoder_plugin.c +++ b/src/decoder/mp4ff_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/mpcdec_decoder_plugin.c b/src/decoder/mpcdec_decoder_plugin.c index eaf470a40..f31dcdb99 100644 --- a/src/decoder/mpcdec_decoder_plugin.c +++ b/src/decoder/mpcdec_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c index 7b48ebfaf..224f3db2a 100644 --- a/src/decoder/mpg123_decoder_plugin.c +++ b/src/decoder/mpg123_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include <glib.h> #include <mpg123.h> +#include <stdio.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "mpg123" @@ -105,6 +106,7 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs) int error; off_t num_samples; enum decoder_command cmd; + struct mpg123_frameinfo info; /* open the file */ @@ -124,10 +126,25 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs) /* tell MPD core we're ready */ - decoder_initialized(decoder, &audio_format, false, + decoder_initialized(decoder, &audio_format, true, (float)num_samples / (float)audio_format.sample_rate); + if (mpg123_info(handle, &info) != MPG123_OK) { + info.vbr = MPG123_CBR; + info.bitrate = 0; + } + + switch (info.vbr) { + case MPG123_ABR: + info.bitrate = info.abr_rate; + break; + case MPG123_CBR: + break; + default: + info.bitrate = 0; + } + /* the decoder main loop */ do { @@ -144,11 +161,30 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs) break; } + /* update bitrate for ABR/VBR */ + if (info.vbr != MPG123_CBR) { + /* FIXME: maybe skip, as too expensive? */ + /* FIXME: maybe, (info.vbr == MPG123_VBR) ? */ + if (mpg123_info (handle, &info) != MPG123_OK) + info.bitrate = 0; + } + /* send to MPD */ - cmd = decoder_data(decoder, NULL, buffer, nbytes, 0); + cmd = decoder_data(decoder, NULL, buffer, nbytes, info.bitrate); - /* seeking not yet implemented */ + if (cmd == DECODE_COMMAND_SEEK) { + off_t c = decoder_seek_where(decoder)*audio_format.sample_rate; + c = mpg123_seek(handle, c, SEEK_SET); + if (c < 0) + decoder_seek_error(decoder); + else { + decoder_command_finished(decoder); + decoder_timestamp(decoder, c/(double)audio_format.sample_rate); + } + + cmd = DECODE_COMMAND_NONE; + } } while (cmd == DECODE_COMMAND_NONE); /* cleanup */ diff --git a/src/decoder/oggflac_decoder_plugin.c b/src/decoder/oggflac_decoder_plugin.c index 7e5f48318..f14cd493c 100644 --- a/src/decoder/oggflac_decoder_plugin.c +++ b/src/decoder/oggflac_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/pcm_decoder_plugin.c b/src/decoder/pcm_decoder_plugin.c new file mode 100644 index 000000000..c8340ab67 --- /dev/null +++ b/src/decoder/pcm_decoder_plugin.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder/pcm_decoder_plugin.h" +#include "decoder_api.h" + +#include <glib.h> +#include <unistd.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "pcm" + +static void +pcm_stream_decode(struct decoder *decoder, struct input_stream *is) +{ + static const struct audio_format audio_format = { + .sample_rate = 44100, + .format = SAMPLE_FORMAT_S16, + .channels = 2, + }; + GError *error = NULL; + enum decoder_command cmd; + + double time_to_size = audio_format_time_to_size(&audio_format); + + float total_time = -1; + if (is->size >= 0) + total_time = is->size / time_to_size; + + decoder_initialized(decoder, &audio_format, is->seekable, total_time); + + do { + char buffer[4096]; + + size_t nbytes = decoder_read(decoder, is, + buffer, sizeof(buffer)); + + if (nbytes == 0 && input_stream_eof(is)) + break; + + cmd = nbytes > 0 + ? decoder_data(decoder, is, + buffer, nbytes, 0) + : decoder_get_command(decoder); + if (cmd == DECODE_COMMAND_SEEK) { + goffset offset = (goffset)(time_to_size * + decoder_seek_where(decoder)); + if (input_stream_seek(is, offset, SEEK_SET, &error)) { + decoder_command_finished(decoder); + } else { + g_warning("seeking failed: %s", error->message); + g_error_free(error); + decoder_seek_error(decoder); + } + + cmd = DECODE_COMMAND_NONE; + } + } while (cmd == DECODE_COMMAND_NONE); +} + +static const char *const pcm_mime_types[] = { + /* for streams obtained by the cdio_paranoia input plugin */ + "audio/x-mpd-cdda-pcm", + NULL +}; + +const struct decoder_plugin pcm_decoder_plugin = { + .name = "pcm", + .stream_decode = pcm_stream_decode, + .mime_types = pcm_mime_types, +}; diff --git a/src/decoder/pcm_decoder_plugin.h b/src/decoder/pcm_decoder_plugin.h new file mode 100644 index 000000000..11df80155 --- /dev/null +++ b/src/decoder/pcm_decoder_plugin.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Not really a decoder; this plugin forwards its input data "as-is". + * + * It was written only to support the "cdio_paranoia" input plugin, + * which does not need a decoder. + */ + +#ifndef MPD_DECODER_PCM_H +#define MPD_DECODER_PCM_H + +extern const struct decoder_plugin pcm_decoder_plugin; + +#endif diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx index 6fceeb30f..9aeec8b51 100644 --- a/src/decoder/sidplay_decoder_plugin.cxx +++ b/src/decoder/sidplay_decoder_plugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c index af68f117d..dbe9bf067 100644 --- a/src/decoder/sndfile_decoder_plugin.c +++ b/src/decoder/sndfile_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/vorbis_decoder_plugin.c index 0a3944ad6..c130005a7 100644 --- a/src/decoder/vorbis_decoder_plugin.c +++ b/src/decoder/vorbis_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/wavpack_decoder_plugin.c b/src/decoder/wavpack_decoder_plugin.c index 24d0c1703..200bf6455 100644 --- a/src/decoder/wavpack_decoder_plugin.c +++ b/src/decoder/wavpack_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/wildmidi_decoder_plugin.c b/src/decoder/wildmidi_decoder_plugin.c index 66e6c61cf..5bc36b4e3 100644 --- a/src/decoder/wildmidi_decoder_plugin.c +++ b/src/decoder/wildmidi_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_api.c b/src/decoder_api.c index fe34ea34a..5536552e5 100644 --- a/src/decoder_api.c +++ b/src/decoder_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,7 +21,6 @@ #include "decoder_api.h" #include "decoder_internal.h" #include "decoder_control.h" -#include "player_control.h" #include "audio.h" #include "song.h" #include "buffer.h" @@ -63,10 +62,9 @@ decoder_initialized(struct decoder *decoder, decoder_lock(dc); dc->state = DECODE_STATE_DECODE; + g_cond_signal(dc->client_cond); decoder_unlock(dc); - player_lock_signal(); - g_debug("audio_format=%s, seekable=%s", audio_format_to_string(&dc->in_audio_format, &af_string), seekable ? "true" : "false"); @@ -115,9 +113,8 @@ decoder_command_finished(struct decoder *decoder) } dc->command = DECODE_COMMAND_NONE; + g_cond_signal(dc->client_cond); decoder_unlock(dc); - - player_lock_signal(); } double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) @@ -214,7 +211,7 @@ do_send_tag(struct decoder *decoder, struct input_stream *is, /* there is a partial chunk - flush it, we want the tag in a new chunk */ decoder_flush_chunk(decoder); - player_lock_signal(); + g_cond_signal(decoder->dc->client_cond); } assert(decoder->chunk == NULL); @@ -329,7 +326,7 @@ decoder_data(struct decoder *decoder, if (dest == NULL) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - player_lock_signal(); + g_cond_signal(dc->client_cond); continue; } @@ -348,7 +345,7 @@ decoder_data(struct decoder *decoder, if (full) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - player_lock_signal(); + g_cond_signal(dc->client_cond); } data += nbytes; @@ -432,7 +429,7 @@ decoder_replay_gain(struct decoder *decoder, replay gain values affect the following samples */ decoder_flush_chunk(decoder); - player_lock_signal(); + g_cond_signal(decoder->dc->client_cond); } } else decoder->replay_gain_serial = 0; diff --git a/src/decoder_api.h b/src/decoder_api.h index 8b5f3d82b..9d032b451 100644 --- a/src/decoder_api.h +++ b/src/decoder_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_buffer.c b/src/decoder_buffer.c index 8f8eb8545..fcb135976 100644 --- a/src/decoder_buffer.c +++ b/src/decoder_buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_buffer.h b/src/decoder_buffer.h index b6051e122..77eff5dd1 100644 --- a/src/decoder_buffer.h +++ b/src/decoder_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_command.h b/src/decoder_command.h index 4a2e49f3e..795e13fb2 100644 --- a/src/decoder_command.h +++ b/src/decoder_command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_control.c b/src/decoder_control.c index a5e6e4ad3..685db6c22 100644 --- a/src/decoder_control.c +++ b/src/decoder_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,6 @@ #include "config.h" #include "decoder_control.h" -#include "player_control.h" #include "pipe.h" #include <assert.h> @@ -27,13 +26,16 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "decoder_control" -void -dc_init(struct decoder_control *dc) +struct decoder_control * +dc_new(GCond *client_cond) { + struct decoder_control *dc = g_new(struct decoder_control, 1); + dc->thread = NULL; dc->mutex = g_mutex_new(); dc->cond = g_cond_new(); + dc->client_cond = client_cond; dc->state = DECODE_STATE_STOP; dc->command = DECODE_COMMAND_NONE; @@ -43,34 +45,26 @@ dc_init(struct decoder_control *dc) dc->mixramp_start = NULL; dc->mixramp_end = NULL; dc->mixramp_prev_end = NULL; + + return dc; } void -dc_deinit(struct decoder_control *dc) +dc_free(struct decoder_control *dc) { g_cond_free(dc->cond); g_mutex_free(dc->mutex); g_free(dc->mixramp_start); g_free(dc->mixramp_end); g_free(dc->mixramp_prev_end); - dc->mixramp_start = NULL; - dc->mixramp_end = NULL; - dc->mixramp_prev_end = NULL; + g_free(dc); } static void dc_command_wait_locked(struct decoder_control *dc) { while (dc->command != DECODE_COMMAND_NONE) - player_wait_decoder(dc); -} - -void -dc_command_wait(struct decoder_control *dc) -{ - decoder_lock(dc); - dc_command_wait_locked(dc); - decoder_unlock(dc); + g_cond_wait(dc->client_cond, dc->mutex); } static void diff --git a/src/decoder_control.h b/src/decoder_control.h index 449e974b7..e1a718a59 100644 --- a/src/decoder_control.h +++ b/src/decoder_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -58,6 +58,12 @@ struct decoder_control { */ GCond *cond; + /** + * The trigger of this object's client. It is signalled + * whenever an event occurs. + */ + GCond *client_cond; + enum decoder_state state; enum decoder_command command; @@ -97,11 +103,12 @@ struct decoder_control { char *mixramp_prev_end; }; -void -dc_init(struct decoder_control *dc); +G_GNUC_MALLOC +struct decoder_control * +dc_new(GCond *client_cond); void -dc_deinit(struct decoder_control *dc); +dc_free(struct decoder_control *dc); /** * Locks the #decoder_control object. @@ -217,9 +224,6 @@ decoder_current_song(const struct decoder_control *dc) return NULL; } -void -dc_command_wait(struct decoder_control *dc); - /** * Start the decoder. * diff --git a/src/decoder_internal.c b/src/decoder_internal.c index 990d728e9..c9878b758 100644 --- a/src/decoder_internal.c +++ b/src/decoder_internal.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,6 @@ #include "config.h" #include "decoder_internal.h" #include "decoder_control.h" -#include "player_control.h" #include "pipe.h" #include "input_stream.h" #include "buffer.h" @@ -65,7 +64,7 @@ need_chunks(struct decoder_control *dc, struct input_stream *is, bool do_wait) if ((is == NULL || !decoder_input_buffer(dc, is)) && do_wait) { decoder_wait(dc); - player_signal(); + g_cond_signal(dc->client_cond); return dc->command; } diff --git a/src/decoder_internal.h b/src/decoder_internal.h index 9e7e2037a..8ed6a3c64 100644 --- a/src/decoder_internal.h +++ b/src/decoder_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_list.c b/src/decoder_list.c index d76050023..7cae818a9 100644 --- a/src/decoder_list.c +++ b/src/decoder_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,7 @@ #include "utils.h" #include "conf.h" #include "mpd_error.h" +#include "decoder/pcm_decoder_plugin.h" #include <glib.h> @@ -102,6 +103,7 @@ const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_GME &gme_decoder_plugin, #endif + &pcm_decoder_plugin, NULL }; diff --git a/src/decoder_list.h b/src/decoder_list.h index 7041db0c9..d259cb195 100644 --- a/src/decoder_list.h +++ b/src/decoder_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_plugin.c b/src/decoder_plugin.c index 062dad364..d32043f0e 100644 --- a/src/decoder_plugin.c +++ b/src/decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ #include "config.h" #include "decoder_plugin.h" -#include "utils.h" +#include "string_util.h" #include <assert.h> diff --git a/src/decoder_plugin.h b/src/decoder_plugin.h index d8371ddb8..0ce1af53e 100644 --- a/src/decoder_plugin.h +++ b/src/decoder_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_print.c b/src/decoder_print.c index a1c2da2e5..72c40ac75 100644 --- a/src/decoder_print.c +++ b/src/decoder_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_print.h b/src/decoder_print.h index 520438871..31713d5d8 100644 --- a/src/decoder_print.h +++ b/src/decoder_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_thread.c b/src/decoder_thread.c index 10a796967..320a04638 100644 --- a/src/decoder_thread.c +++ b/src/decoder_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,7 +26,6 @@ #include "decoder_api.h" #include "replay_gain_ape.h" #include "input_stream.h" -#include "player_control.h" #include "pipe.h" #include "song.h" #include "tag.h" @@ -55,6 +54,22 @@ decoder_lock_get_command(struct decoder_control *dc) } /** + * Marks the current decoder command as "finished" and notifies the + * player thread. + * + * @param dc the #decoder_control object; must be locked + */ +static void +decoder_command_finished_locked(struct decoder_control *dc) +{ + assert(dc->command != DECODE_COMMAND_NONE); + + dc->command = DECODE_COMMAND_NONE; + + g_cond_signal(dc->client_cond); +} + +/** * Opens the input stream with input_stream_open(), and waits until * the stream gets ready. If a decoder STOP command is received * during that, it cancels the operation (but does not close the @@ -381,9 +396,8 @@ decoder_run_song(struct decoder_control *dc, decoder.chunk = NULL; dc->state = DECODE_STATE_START; - dc->command = DECODE_COMMAND_NONE; - player_signal(); + decoder_command_finished_locked(dc); pcm_convert_init(&decoder.conv_state); @@ -429,6 +443,7 @@ decoder_run(struct decoder_control *dc) if (uri == NULL) { dc->state = DECODE_STATE_ERROR; + decoder_command_finished_locked(dc); return; } @@ -461,16 +476,10 @@ decoder_task(gpointer arg) case DECODE_COMMAND_SEEK: decoder_run(dc); - - dc->command = DECODE_COMMAND_NONE; - - player_signal(); break; case DECODE_COMMAND_STOP: - dc->command = DECODE_COMMAND_NONE; - - player_signal(); + decoder_command_finished_locked(dc); break; case DECODE_COMMAND_NONE: diff --git a/src/decoder_thread.h b/src/decoder_thread.h index 28042d7f8..78f12a54a 100644 --- a/src/decoder_thread.h +++ b/src/decoder_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/despotify_utils.c b/src/despotify_utils.c new file mode 100644 index 000000000..7555d105d --- /dev/null +++ b/src/despotify_utils.c @@ -0,0 +1,121 @@ +#include <glib.h> +#include <despotify.h> + +#include "tag.h" +#include "conf.h" +#include "despotify_utils.h" + +static struct despotify_session *g_session; +static void (*registered_callbacks[8])(struct despotify_session *, + int, void *, void *); +static void *registered_callback_data[8]; + +static void callback(struct despotify_session* ds, int sig, + void* data, G_GNUC_UNUSED void* callback_data) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i]; + void *cb_data = registered_callback_data[i]; + + if (cb) + cb(ds, sig, data, cb_data); + } +} + +bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), + void *cb_data) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + + if (!registered_callbacks[i]) { + registered_callbacks[i] = cb; + registered_callback_data[i] = cb_data; + + return true; + } + } + + return false; +} + +void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + + if (registered_callbacks[i] == cb) { + registered_callbacks[i] = NULL; + } + } +} + + +struct tag *mpd_despotify_tag_from_track(struct ds_track *track) +{ + char tracknum[20]; + char comment[80]; + char date[20]; + struct tag *tag; + + tag = tag_new(); + + if (!track->has_meta_data) + return tag; + + g_snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber); + g_snprintf(date, sizeof(date), "%d", track->year); + g_snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted", + track->file_bitrate / 1000, track->geo_restricted ? "" : "not "); + tag_add_item(tag, TAG_TITLE, track->title); + tag_add_item(tag, TAG_ARTIST, track->artist->name); + tag_add_item(tag, TAG_TRACK, tracknum); + tag_add_item(tag, TAG_ALBUM, track->album); + tag_add_item(tag, TAG_DATE, date); + tag_add_item(tag, TAG_COMMENT, comment); + tag->time = track->length / 1000; + + return tag; +} + +struct despotify_session *mpd_despotify_get_session(void) +{ + const char *user; + const char *passwd; + bool high_bitrate; + + if (g_session) + return g_session; + + user = config_get_string(CONF_DESPOTIFY_USER, NULL); + passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, NULL); + high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); + + if (user == NULL || passwd == NULL) { + g_debug("disabling despotify because account is not configured"); + return NULL; + } + if (!despotify_init()) { + g_debug("Can't initialize despotify\n"); + return false; + } + + g_session = despotify_init_client(callback, NULL, + high_bitrate, true); + if (!g_session) { + g_debug("Can't initialize despotify client\n"); + return false; + } + + if (!despotify_authenticate(g_session, user, passwd)) { + g_debug("Can't authenticate despotify session\n"); + despotify_exit(g_session); + return false; + } + + return g_session; +} diff --git a/src/despotify_utils.h b/src/despotify_utils.h new file mode 100644 index 000000000..7e35edc3c --- /dev/null +++ b/src/despotify_utils.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DESPOTIFY_H +#define MPD_DESPOTIFY_H + +struct despotify_session; +struct ds_track; + +/** + * Return the current despotify session. + * + * If the session isn't initialized, this function will initialize + * it and connect to Spotify. + * + * @return a pointer to the despotify session, or NULL if it can't + * be initialized (e.g., if the configuration isn't supplied) + */ +struct despotify_session *mpd_despotify_get_session(void); + +/** + * Create a MPD tags structure from a spotify track + * + * @param track the track to convert + * + * @return a pointer to the filled in tags structure + */ +struct tag *mpd_despotify_tag_from_track(struct ds_track *track); + +/** + * Register a despotify callback. + * + * Despotify calls this e.g., when a track ends. + * + * @param cb the callback + * @param cb_data the data to pass to the callback + * + * @return true if the callback could be registered + */ +bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), + void *cb_data); + +/** + * Unregister a despotify callback. + * + * @param cb the callback to unregister. + */ +void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)); + +#endif + diff --git a/src/directory.c b/src/directory.c index fa15d41b1..d8b241118 100644 --- a/src/directory.c +++ b/src/directory.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/directory.h b/src/directory.h index 151cf5423..a3ad0ffa3 100644 --- a/src/directory.h +++ b/src/directory.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/directory_print.c b/src/directory_print.c index 74ff26cb3..8cd906b7b 100644 --- a/src/directory_print.c +++ b/src/directory_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/directory_print.h b/src/directory_print.h index 0933f5a97..9a10a1fe5 100644 --- a/src/directory_print.h +++ b/src/directory_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/directory_save.c b/src/directory_save.c index 55896c289..71a75f8a9 100644 --- a/src/directory_save.c +++ b/src/directory_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/directory_save.h b/src/directory_save.h index 9193b6c41..626c99c73 100644 --- a/src/directory_save.h +++ b/src/directory_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/dirvec.c b/src/dirvec.c index 89b32a4f4..3df08e9ab 100644 --- a/src/dirvec.c +++ b/src/dirvec.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/dirvec.h b/src/dirvec.h index a1a97d9f1..a15510ab2 100644 --- a/src/dirvec.h +++ b/src/dirvec.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c index c34faad00..8b315d710 100644 --- a/src/encoder/flac_encoder.c +++ b/src/encoder/flac_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -205,7 +205,7 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, pcm_buffer_init(&encoder->buffer); pcm_buffer_init(&encoder->expand_buffer); - /* this immediatelly outputs data throught callback */ + /* this immediately outputs data through callback */ #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 { diff --git a/src/encoder/lame_encoder.c b/src/encoder/lame_encoder.c index df843471b..3bb99ea28 100644 --- a/src/encoder/lame_encoder.c +++ b/src/encoder/lame_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder/null_encoder.c b/src/encoder/null_encoder.c index bf7e61c3b..e83eaf2ba 100644 --- a/src/encoder/null_encoder.c +++ b/src/encoder/null_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder/twolame_encoder.c b/src/encoder/twolame_encoder.c index d20af551b..00ebcc0d7 100644 --- a/src/encoder/twolame_encoder.c +++ b/src/encoder/twolame_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder/vorbis_encoder.c b/src/encoder/vorbis_encoder.c index 38a998bd2..3e9d486b6 100644 --- a/src/encoder/vorbis_encoder.c +++ b/src/encoder/vorbis_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder/wave_encoder.c b/src/encoder/wave_encoder.c index 938be5e5e..6ebacab7d 100644 --- a/src/encoder/wave_encoder.c +++ b/src/encoder/wave_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder_api.h b/src/encoder_api.h index 5df486ebd..46c8d10c8 100644 --- a/src/encoder_api.h +++ b/src/encoder_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder_list.c b/src/encoder_list.c index f49ad48f7..d98e617b4 100644 --- a/src/encoder_list.c +++ b/src/encoder_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder_list.h b/src/encoder_list.h index 95f853004..6316d5d2f 100644 --- a/src/encoder_list.h +++ b/src/encoder_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h index fb00413e6..95b3da016 100644 --- a/src/encoder_plugin.h +++ b/src/encoder_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -80,7 +80,7 @@ encoder_struct_init(struct encoder *encoder, * * @param plugin the encoder plugin * @param param optional configuration - * @param error location to store the error occuring, or NULL to ignore errors. + * @param error location to store the error occurring, or NULL to ignore errors. * @return an encoder object on success, NULL on failure */ static inline struct encoder * @@ -109,7 +109,7 @@ encoder_finish(struct encoder *encoder) * @param encoder the encoder * @param audio_format the encoder's input audio format; the plugin * may modify the struct to adapt it to its abilities - * @param error location to store the error occuring, or NULL to ignore errors. + * @param error location to store the error occurring, or NULL to ignore errors. * @return true on success */ static inline bool @@ -137,7 +137,7 @@ encoder_close(struct encoder *encoder) * buffered available by encoder_read(). * * @param encoder the encoder - * @param error location to store the error occuring, or NULL to ignore errors. + * @param error location to store the error occurring, or NULL to ignore errors. * @return true on success */ static inline bool @@ -176,7 +176,7 @@ encoder_pre_tag(struct encoder *encoder, GError **error) * * @param encoder the encoder * @param tag the tag object - * @param error location to store the error occuring, or NULL to ignore errors. + * @param error location to store the error occurring, or NULL to ignore errors. * @return true on success */ static inline bool @@ -194,7 +194,7 @@ encoder_tag(struct encoder *encoder, const struct tag *tag, GError **error) * @param encoder the encoder * @param data the buffer containing PCM samples * @param length the length of the buffer in bytes - * @param error location to store the error occuring, or NULL to ignore errors. + * @param error location to store the error occurring, or NULL to ignore errors. * @return true on success */ static inline bool diff --git a/src/event_pipe.c b/src/event_pipe.c index 5b519984f..85fd6d917 100644 --- a/src/event_pipe.c +++ b/src/event_pipe.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/event_pipe.h b/src/event_pipe.h index 923544bf4..3734bb86c 100644 --- a/src/event_pipe.h +++ b/src/event_pipe.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/exclude.c b/src/exclude.c index dd46b58c7..438039d30 100644 --- a/src/exclude.c +++ b/src/exclude.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/exclude.h b/src/exclude.h index fd7cf8795..5b1229e29 100644 --- a/src/exclude.h +++ b/src/exclude.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/fd_util.c b/src/fd_util.c index 1f3004d0c..36f6e2fc9 100644 --- a/src/fd_util.c +++ b/src/fd_util.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without @@ -206,6 +206,29 @@ socketpair_cloexec(int domain, int type, int protocol, int sv[2]) return ret; } +int +socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]) +{ + int ret; + +#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + ret = socketpair(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, protocol, + sv); + if (ret >= 0 || errno != EINVAL) + return ret; +#endif + + ret = socketpair(domain, type, protocol, sv); + if (ret >= 0) { + fd_set_cloexec(sv[0], true); + fd_set_nonblock(sv[0]); + fd_set_cloexec(sv[1], true); + fd_set_nonblock(sv[1]); + } + + return ret; +} + #endif int diff --git a/src/fd_util.h b/src/fd_util.h index 5b80df2c7..96ad784d4 100644 --- a/src/fd_util.h +++ b/src/fd_util.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without @@ -89,6 +89,13 @@ pipe_cloexec_nonblock(int fd[2]); int socketpair_cloexec(int domain, int type, int protocol, int sv[2]); +/** + * Wrapper for socketpair(), which sets the flags CLOEXEC and NONBLOCK + * (atomically if supported by the OS). + */ +int +socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]); + #endif /** diff --git a/src/fifo_buffer.c b/src/fifo_buffer.c index 9ac7270bb..e5735f5eb 100644 --- a/src/fifo_buffer.c +++ b/src/fifo_buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without diff --git a/src/fifo_buffer.h b/src/fifo_buffer.h index 661dfd57e..2730207b5 100644 --- a/src/fifo_buffer.h +++ b/src/fifo_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c index 9e197a5f6..3826a0fb3 100644 --- a/src/filter/autoconvert_filter_plugin.c +++ b/src/filter/autoconvert_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/autoconvert_filter_plugin.h b/src/filter/autoconvert_filter_plugin.h index 730db197d..def08ab7e 100644 --- a/src/filter/autoconvert_filter_plugin.h +++ b/src/filter/autoconvert_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c index 06d4d0e6b..2c785a36f 100644 --- a/src/filter/chain_filter_plugin.c +++ b/src/filter/chain_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/chain_filter_plugin.h b/src/filter/chain_filter_plugin.h index 42c6a9b78..1dba46667 100644 --- a/src/filter/chain_filter_plugin.h +++ b/src/filter/chain_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/convert_filter_plugin.c b/src/filter/convert_filter_plugin.c index cb9e0940a..757084de1 100644 --- a/src/filter/convert_filter_plugin.c +++ b/src/filter/convert_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/convert_filter_plugin.h b/src/filter/convert_filter_plugin.h index ba9180e64..156adf8e3 100644 --- a/src/filter/convert_filter_plugin.h +++ b/src/filter/convert_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/normalize_filter_plugin.c b/src/filter/normalize_filter_plugin.c index 63bbb6e4f..fa992f0d4 100644 --- a/src/filter/normalize_filter_plugin.c +++ b/src/filter/normalize_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c index 650f95bc4..e7c998827 100644 --- a/src/filter/null_filter_plugin.c +++ b/src/filter/null_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/replay_gain_filter_plugin.c b/src/filter/replay_gain_filter_plugin.c index 3a0af66ff..656e464e2 100644 --- a/src/filter/replay_gain_filter_plugin.c +++ b/src/filter/replay_gain_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/replay_gain_filter_plugin.h index 348b4f50c..45b738e40 100644 --- a/src/filter/replay_gain_filter_plugin.h +++ b/src/filter/replay_gain_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c index 6b9aa2a2f..3bf8677e5 100644 --- a/src/filter/route_filter_plugin.c +++ b/src/filter/route_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c index 42311ca5e..8c50e3cd1 100644 --- a/src/filter/volume_filter_plugin.c +++ b/src/filter/volume_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/volume_filter_plugin.h b/src/filter/volume_filter_plugin.h index ad3b2c6f1..5b16f7e57 100644 --- a/src/filter/volume_filter_plugin.h +++ b/src/filter/volume_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter_config.c b/src/filter_config.c index 90de199b7..ab9bdb0c5 100644 --- a/src/filter_config.c +++ b/src/filter_config.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter_config.h b/src/filter_config.h index 9ed4d204b..920cbc07c 100644 --- a/src/filter_config.h +++ b/src/filter_config.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter_internal.h b/src/filter_internal.h index 8dd6da491..4e94599a2 100644 --- a/src/filter_internal.h +++ b/src/filter_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter_plugin.c b/src/filter_plugin.c index 492d703ac..7173134b3 100644 --- a/src/filter_plugin.c +++ b/src/filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter_plugin.h b/src/filter_plugin.h index ac6b34522..58e34dfb2 100644 --- a/src/filter_plugin.h +++ b/src/filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -79,7 +79,7 @@ struct filter_plugin { * * @param plugin the filter plugin * @param param optional configuration section - * @param error location to store the error occuring, or NULL to + * @param error location to store the error occurring, or NULL to * ignore errors. * @return a new filter object, or NULL on error */ @@ -92,7 +92,7 @@ filter_new(const struct filter_plugin *plugin, * the specified configuration section. * * @param param the configuration section - * @param error location to store the error occuring, or NULL to + * @param error location to store the error occurring, or NULL to * ignore errors. * @return a new filter object, or NULL on error */ @@ -114,7 +114,7 @@ filter_free(struct filter *filter); * @param filter the filter object * @param audio_format the audio format of incoming data; the plugin * may modify the object to enforce another input format - * @param error location to store the error occuring, or NULL to + * @param error location to store the error occurring, or NULL to * ignore errors. * @return the format of outgoing data */ @@ -137,7 +137,7 @@ filter_close(struct filter *filter); * @param src the input buffer * @param src_size the size of #src_buffer in bytes * @param dest_size_r the size of the returned buffer - * @param error location to store the error occuring, or NULL to + * @param error location to store the error occurring, or NULL to * ignore errors. * @return the destination buffer on success (will be invalidated by * filter_close() or filter_filter()), NULL on error diff --git a/src/filter_registry.c b/src/filter_registry.c index 150043cc5..dc1889398 100644 --- a/src/filter_registry.c +++ b/src/filter_registry.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter_registry.h b/src/filter_registry.h index 551a7afa1..d3949c7c4 100644 --- a/src/filter_registry.h +++ b/src/filter_registry.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/glib_compat.h b/src/glib_compat.h index 4d0e7040d..8307997d5 100644 --- a/src/glib_compat.h +++ b/src/glib_compat.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,6 +32,12 @@ #define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0) +static inline GSource * +g_timeout_source_new_seconds(guint interval) +{ + return g_timeout_source_new(interval * 1000); +} + static inline guint g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data) { @@ -74,4 +80,15 @@ g_uri_parse_scheme(const char *uri) #endif +#if !GLIB_CHECK_VERSION(2,18,0) + +static inline void +g_set_error_literal(GError **err, GQuark domain, gint code, + const gchar *message) +{ + g_set_error(err, domain, code, "%s", message); +} + +#endif + #endif diff --git a/src/icy_metadata.c b/src/icy_metadata.c index 6a79121cf..32953e69f 100644 --- a/src/icy_metadata.c +++ b/src/icy_metadata.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/icy_metadata.h b/src/icy_metadata.h index 4a51b4cf0..9797122ca 100644 --- a/src/icy_metadata.h +++ b/src/icy_metadata.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/icy_server.c b/src/icy_server.c index 62a2c67af..b6c89eaf6 100644 --- a/src/icy_server.c +++ b/src/icy_server.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -70,7 +70,7 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url) meta_length = strlen(icy_metadata); - meta_length--; // substract placeholder + meta_length--; // subtract placeholder meta_length = ((int)meta_length / 16) + 1; diff --git a/src/icy_server.h b/src/icy_server.h index 3ce4ab635..04f21d2ad 100644 --- a/src/icy_server.h +++ b/src/icy_server.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/idle.c b/src/idle.c index eccb62322..2d174d78a 100644 --- a/src/idle.c +++ b/src/idle.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -42,6 +42,8 @@ static const char *const idle_names[] = { "options", "sticker", "update", + "subscription", + "message", NULL }; diff --git a/src/idle.h b/src/idle.h index 7caeb4a8c..0156933c0 100644 --- a/src/idle.h +++ b/src/idle.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -53,6 +53,12 @@ enum { /** a database update has started or finished. */ IDLE_UPDATE = 0x100, + + /** a client has subscribed or unsubscribed to/from a channel */ + IDLE_SUBSCRIPTION = 0x200, + + /** a message on the subscribed channel was receivedd */ + IDLE_MESSAGE = 0x400, }; /** diff --git a/src/inotify_queue.c b/src/inotify_queue.c index 5391a1715..d5e2228c3 100644 --- a/src/inotify_queue.c +++ b/src/inotify_queue.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/inotify_queue.h b/src/inotify_queue.h index 2e43d2f25..cfc28ebfe 100644 --- a/src/inotify_queue.h +++ b/src/inotify_queue.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/inotify_source.c b/src/inotify_source.c index 3a986cbad..e415f5e72 100644 --- a/src/inotify_source.c +++ b/src/inotify_source.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/inotify_source.h b/src/inotify_source.h index e78b92c0f..f92e18e39 100644 --- a/src/inotify_source.h +++ b/src/inotify_source.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/inotify_update.c b/src/inotify_update.c index 8d9657961..e78c15fc1 100644 --- a/src/inotify_update.c +++ b/src/inotify_update.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/inotify_update.h b/src/inotify_update.h index 92b4e0cc6..ca75c0f45 100644 --- a/src/inotify_update.h +++ b/src/inotify_update.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c index 97e4836ff..8d78f4c89 100644 --- a/src/input/archive_input_plugin.c +++ b/src/input/archive_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/archive_input_plugin.h b/src/input/archive_input_plugin.h index 20568cfbe..51095f37f 100644 --- a/src/input/archive_input_plugin.h +++ b/src/input/archive_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/cdio_paranoia_input_plugin.c b/src/input/cdio_paranoia_input_plugin.c new file mode 100644 index 000000000..400c66ef3 --- /dev/null +++ b/src/input/cdio_paranoia_input_plugin.c @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * CD-Audio handling (requires libcdio_paranoia) + */ + +#include "config.h" +#include "input/cdio_paranoia_input_plugin.h" +#include "input_plugin.h" +#include "refcount.h" +#include "pcm_buffer.h" + +#include <stdio.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <glib.h> +#include <assert.h> + +#include <cdio/paranoia.h> +#include <cdio/cd_types.h> + +struct input_cdio_paranoia { + struct input_stream base; + + cdrom_drive_t *drv; + CdIo_t *cdio; + cdrom_paranoia_t *para; + + int endian; + + lsn_t lsn_from, lsn_to; + int lsn_relofs; + + int trackno; + + char buffer[CDIO_CD_FRAMESIZE_RAW]; + int buffer_lsn; + + struct pcm_buffer conv_buffer; +}; + +static inline GQuark +cdio_quark(void) +{ + return g_quark_from_static_string("cdio"); +} + +static void +input_cdio_close(struct input_stream *is) +{ + struct input_cdio_paranoia *i = (struct input_cdio_paranoia *)is; + + pcm_buffer_deinit(&i->conv_buffer); + + if (i->para) + cdio_paranoia_free(i->para); + if (i->drv) + cdio_cddap_close_no_free_cdio( i->drv); + if (i->cdio) + cdio_destroy( i->cdio ); + + input_stream_deinit(&i->base); + g_free(i); +} + +struct cdio_uri { + char device[64]; + int track; +}; + +static bool +parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r) +{ + if (!g_str_has_prefix(src, "cdda://")) + return false; + + src += 7; + + if (*src == 0) { + /* play the whole CD in the default drive */ + dest->device[0] = 0; + dest->track = -1; + return true; + } + + const char *slash = strrchr(src, '/'); + if (slash == NULL) { + /* play the whole CD in the specified drive */ + g_strlcpy(dest->device, src, sizeof(dest->device)); + dest->track = -1; + return true; + } + + size_t device_length = slash - src; + if (device_length >= sizeof(dest->device)) + device_length = sizeof(dest->device) - 1; + + memcpy(dest->device, src, device_length); + dest->device[device_length] = 0; + + const char *track = slash + 1; + + char *endptr; + dest->track = strtoul(track, &endptr, 10); + if (*endptr != 0) { + g_set_error(error_r, cdio_quark(), 0, + "Malformed track number"); + return false; + } + + if (endptr == track) + /* play the whole CD */ + dest->track = -1; + + return true; +} + +static char * +cdio_detect_device(void) +{ + char **devices = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false); + if (devices == NULL) + return NULL; + + char *device = g_strdup(devices[0]); + cdio_free_device_list(devices); + + return device; +} + +static struct input_stream * +input_cdio_open(const char *uri, GError **error_r) +{ + struct input_cdio_paranoia *i; + + struct cdio_uri parsed_uri; + if (!parse_cdio_uri(&parsed_uri, uri, error_r)) + return NULL; + + i = g_new(struct input_cdio_paranoia, 1); + input_stream_init(&i->base, &input_plugin_cdio_paranoia, uri); + + /* initialize everything (should be already) */ + i->drv = NULL; + i->cdio = NULL; + i->para = NULL; + i->trackno = parsed_uri.track; + pcm_buffer_init(&i->conv_buffer); + + /* get list of CD's supporting CD-DA */ + char *device = parsed_uri.device[0] != 0 + ? g_strdup(parsed_uri.device) + : cdio_detect_device(); + if (device == NULL) { + g_set_error(error_r, cdio_quark(), 0, + "Unable find or access a CD-ROM drive with an audio CD in it."); + input_cdio_close(&i->base); + return NULL; + } + + /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */ + i->cdio = cdio_open(device, DRIVER_UNKNOWN); + g_free(device); + + i->drv = cdio_cddap_identify_cdio(i->cdio, 1, NULL); + + if ( !i->drv ) { + g_set_error(error_r, cdio_quark(), 0, + "Unable to identify audio CD disc."); + input_cdio_close(&i->base); + return NULL; + } + + cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); + + if ( 0 != cdio_cddap_open(i->drv) ) { + g_set_error(error_r, cdio_quark(), 0, "Unable to open disc."); + input_cdio_close(&i->base); + return NULL; + } + + i->endian = data_bigendianp(i->drv); + switch (i->endian) { + case -1: + g_debug("cdda: drive returns unknown audio data, assuming Little Endian"); + i->endian = 0; + break; + case 0: + g_debug("cdda: drive returns audio data Little Endian."); + break; + case 1: + g_debug("cdda: drive returns audio data Big Endian."); + break; + default: + g_set_error(error_r, cdio_quark(), 0, + "Drive returns unknown data type %d", i->endian); + input_cdio_close(&i->base); + return NULL; + } + + i->lsn_relofs = 0; + + if (i->trackno >= 0) { + i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno); + i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno); + } else { + i->lsn_from = 0; + i->lsn_to = cdio_get_disc_last_lsn(i->cdio); + } + + i->para = cdio_paranoia_init(i->drv); + + /* Set reading mode for full paranoia, but allow skipping sectors. */ + paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); + + /* seek to beginning of the track */ + cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET); + + i->base.ready = true; + i->base.seekable = true; + i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW; + + /* hack to make MPD select the "pcm" decoder plugin */ + i->base.mime = g_strdup("audio/x-mpd-cdda-pcm"); + + return &i->base; +} + +static bool +input_cdio_seek(struct input_stream *is, + goffset offset, int whence, GError **error_r) +{ + struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; + + /* calculate absolute offset */ + switch (whence) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += cis->base.offset; + break; + case SEEK_END: + offset += cis->base.size; + break; + } + + if (offset < 0 || offset > cis->base.size) { + g_set_error(error_r, cdio_quark(), 0, + "Invalid offset to seek %ld (%ld)", + (long int)offset, (long int)cis->base.size); + return false; + } + + /* simple case */ + if (offset == cis->base.offset) + return true; + + /* calculate current LSN */ + cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; + cis->base.offset = offset; + + cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET); + + return true; +} + +static inline size_t +pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length) +{ + size_t cnt = length >> 1; + while (cnt > 0) { + *dst16++ = GUINT16_TO_LE(*src16++); + cnt--; + } + return length; +} + +static size_t +input_cdio_read(struct input_stream *is, void *ptr, size_t length, + GError **error_r) +{ + struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; + size_t nbytes = 0; + int diff; + size_t len, maxwrite; + int16_t *rbuf; + char *s_err, *s_mess; + char *wptr = (char *) ptr; + + while (length > 0) { + + + /* end of track ? */ + if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to) + break; + + //current sector was changed ? + if (cis->lsn_relofs != cis->buffer_lsn) { + rbuf = cdio_paranoia_read(cis->para, NULL); + + s_err = cdda_errors(cis->drv); + if (s_err) { + g_warning("paranoia_read: %s", s_err ); + free(s_err); + } + s_mess = cdda_messages(cis->drv); + if (s_mess) { + free(s_mess); + } + if (!rbuf) { + g_set_error(error_r, cdio_quark(), 0, + "paranoia read error. Stopping."); + return 0; + } + //do the swapping if nessesary + if (cis->endian != 0) { + uint16_t *conv_buffer = pcm_buffer_get(&cis->conv_buffer, CDIO_CD_FRAMESIZE_RAW ); + /* do endian conversion ! */ + pcm16_to_wave( conv_buffer, (uint16_t*) rbuf, CDIO_CD_FRAMESIZE_RAW); + rbuf = (int16_t *)conv_buffer; + } + //store current buffer + memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); + cis->buffer_lsn = cis->lsn_relofs; + } else { + //use cached sector + rbuf = (int16_t*) cis->buffer; + } + + //correct offset + diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW; + + assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW); + + maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer + len = (length < maxwrite? length : maxwrite); + + //skip diff bytes from this lsn + memcpy(wptr, ((char*)rbuf) + diff, len); + //update pointer + wptr += len; + nbytes += len; + + //update offset + cis->base.offset += len; + cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW; + //update length + length -= len; + } + + return nbytes; +} + +static bool +input_cdio_eof(struct input_stream *is) +{ + struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; + + return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); +} + +const struct input_plugin input_plugin_cdio_paranoia = { + .name = "cdio_paranoia", + .open = input_cdio_open, + .close = input_cdio_close, + .seek = input_cdio_seek, + .read = input_cdio_read, + .eof = input_cdio_eof +}; diff --git a/src/input/cdio_paranoia_input_plugin.h b/src/input/cdio_paranoia_input_plugin.h new file mode 100644 index 000000000..71c5cbe8d --- /dev/null +++ b/src/input/cdio_paranoia_input_plugin.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_H +#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_H + +/** + * An input plugin based on libcdio_paranoia library. + */ +extern const struct input_plugin input_plugin_cdio_paranoia; + +#endif diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c index 2c4ac2ff8..a591c9272 100644 --- a/src/input/curl_input_plugin.c +++ b/src/input/curl_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,7 @@ #include "conf.h" #include "tag.h" #include "icy_metadata.h" +#include "io_thread.h" #include "glib_compat.h" #include <assert.h> @@ -73,17 +74,29 @@ struct input_curl { /** the curl handles */ CURL *easy; - CURLM *multi; + + /** the GMainLoop source used to poll all CURL file + descriptors */ + GSource *source; + + /** the source id of #source */ + guint source_id; + + /** a linked list of all registered GPollFD objects */ + GSList *fds; /** list of buffers, where input_curl_writefunction() appends to, and input_curl_read() reads from them */ GQueue *buffers; - /** has something been added to the buffers list? */ - bool buffered; - - /** did libcurl tell us the we're at the end of the response body? */ - bool eof; +#if LIBCURL_VERSION_NUM >= 0x071200 + /** + * Is the connection currently paused? That happens when the + * buffer was getting too large. It will be unpaused when the + * buffer is below the threshold again. + */ + bool paused; +#endif /** error message provided by libcurl */ char error[CURL_ERROR_SIZE]; @@ -97,6 +110,8 @@ struct input_curl { /** the tag object ready to be requested via input_stream_tag() */ struct tag *tag; + + GError *postponed_error; }; /** libcurl should accept "ICY 200 OK" */ @@ -106,20 +121,534 @@ static struct curl_slist *http_200_aliases; static const char *proxy, *proxy_user, *proxy_password; static unsigned proxy_port; +static struct { + GStaticMutex mutex; + GCond *cond; + CURLM *multi; + + /** + * A linked list of all active HTTP requests. An active + * request is one that doesn't have the "eof" flag set. + */ + GSList *requests; + + /** + * The GMainLoop source used to poll all CURL file + * descriptors. + */ + GSource *source; + + /** + * The source id of #source. + */ + guint source_id; + + GSList *fds; + + /** + * When this is non-zero, then an update of #fds is scheduled. + */ + guint dirty_source_id; + +#if LIBCURL_VERSION_NUM >= 0x070f04 + /** + * Did CURL give us a timeout? If yes, then we need to call + * curl_multi_perform(), even if there was no event on any + * file descriptor. + */ + bool timeout; + + /** + * The absolute time stamp when the timeout expires. This is + * used in the GSource method check(). + */ + GTimeVal absolute_timeout; +#endif +} curl = { + .mutex = G_STATIC_MUTEX_INIT, +}; + static inline GQuark curl_quark(void) { return g_quark_from_static_string("curl"); } +/** + * Find a request by its CURL "easy" handle. + * + * The caller must lock the mutex. + */ +static struct input_curl * +input_curl_find_request(CURL *easy) +{ + for (GSList *i = curl.requests; i != NULL; i = g_slist_next(i)) { + struct input_curl *c = i->data; + if (c->easy == easy) + return c; + } + + return NULL; +} + +#if LIBCURL_VERSION_NUM >= 0x071200 + +static gpointer +input_curl_resume(gpointer data) +{ + struct input_curl *c = data; + + if (c->paused) { + curl_easy_pause(c->easy, CURLPAUSE_CONT); + c->paused = false; + } + + return NULL; +} + +#endif + +/** + * Calculates the GLib event bit mask for one file descriptor, + * obtained from three #fd_set objects filled by curl_multi_fdset(). + */ +static gushort +input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) +{ + gushort events = 0; + + if (FD_ISSET(fd, rfds)) { + events |= G_IO_IN | G_IO_HUP | G_IO_ERR; + FD_CLR(fd, rfds); + } + + if (FD_ISSET(fd, wfds)) { + events |= G_IO_OUT | G_IO_ERR; + FD_CLR(fd, wfds); + } + + if (FD_ISSET(fd, efds)) { + events |= G_IO_HUP | G_IO_ERR; + FD_CLR(fd, efds); + } + + return events; +} + +/** + * Updates all registered GPollFD objects, unregisters old ones, + * registers new ones. + * + * The caller must lock the mutex. Runs in the I/O thread. + */ +static void +curl_update_fds(void) +{ + fd_set rfds, wfds, efds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + + int max_fd; + CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds, + &efds, &max_fd); + if (mcode != CURLM_OK) { + g_warning("curl_multi_fdset() failed: %s\n", + curl_multi_strerror(mcode)); + return; + } + + GSList *fds = curl.fds; + curl.fds = NULL; + + while (fds != NULL) { + GPollFD *poll_fd = fds->data; + gushort events = input_curl_fd_events(poll_fd->fd, &rfds, + &wfds, &efds); + + assert(poll_fd->events != 0); + + fds = g_slist_remove(fds, poll_fd); + + if (events != poll_fd->events) + g_source_remove_poll(curl.source, poll_fd); + + if (events != 0) { + if (events != poll_fd->events) { + poll_fd->events = events; + g_source_add_poll(curl.source, poll_fd); + } + + curl.fds = g_slist_prepend(curl.fds, poll_fd); + } else { + g_free(poll_fd); + } + } + + for (int fd = 0; fd <= max_fd; ++fd) { + gushort events = input_curl_fd_events(fd, &rfds, &wfds, &efds); + if (events != 0) { + GPollFD *poll_fd = g_new(GPollFD, 1); + poll_fd->fd = fd; + poll_fd->events = events; + g_source_add_poll(curl.source, poll_fd); + curl.fds = g_slist_prepend(curl.fds, poll_fd); + } + } +} + +/** + * Callback for curl_schedule_update() that runs in the I/O thread. + */ +static gboolean +input_curl_dirty_callback(G_GNUC_UNUSED gpointer data) +{ + g_static_mutex_lock(&curl.mutex); + + assert(curl.dirty_source_id != 0 || curl.requests == NULL); + curl.dirty_source_id = 0; + + curl_update_fds(); + + g_static_mutex_unlock(&curl.mutex); + return false; +} + +/** + * Schedule a refresh of curl.fds. Does nothing if that is already + * scheduled. + * + * The caller must lock the mutex. + */ +static void +input_curl_schedule_update(void) +{ + if (curl.dirty_source_id != 0) + /* already scheduled */ + return; + + curl.dirty_source_id = + io_thread_idle_add(input_curl_dirty_callback, NULL); +} + +static bool +input_curl_easy_add(struct input_curl *c, GError **error_r) +{ + assert(c != NULL); + assert(c->easy != NULL); + assert(input_curl_find_request(c->easy) == NULL); + + curl.requests = g_slist_prepend(curl.requests, c); + + CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy); + if (mcode != CURLM_OK) { + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_add_handle() failed: %s", + curl_multi_strerror(mcode)); + return false; + } + + input_curl_schedule_update(); + + return true; +} + +/** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + * + * The caller must lock the mutex. + */ +static void +input_curl_easy_free(struct input_curl *c) +{ + assert(c != NULL); + + if (c->easy == NULL) + return; + + curl.requests = g_slist_remove(curl.requests, c); + + curl_multi_remove_handle(curl.multi, c->easy); + curl_easy_cleanup(c->easy); + c->easy = NULL; + + curl_slist_free_all(c->request_headers); + c->request_headers = NULL; + + g_free(c->range); + c->range = NULL; + + c->base.ready = true; +} + +static gpointer +input_curl_easy_free_callback(gpointer data) +{ + struct input_curl *c = data; + + g_static_mutex_lock(&curl.mutex); + + input_curl_easy_free(c); + curl_update_fds(); + + g_static_mutex_unlock(&curl.mutex); + + return NULL; +} + +/** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + * + * The mutex must not be locked. + */ +static void +input_curl_easy_free_indirect(struct input_curl *c) +{ + io_thread_call(input_curl_easy_free_callback, c); + assert(c->easy == NULL); +} + +/** + * Aborts and frees a running HTTP request. + * + * The caller must lock the mutex. Runs in the I/O thread. + */ +static void +input_curl_request_abort(struct input_curl *c, GError *error) +{ + assert(c != NULL); + assert(c->postponed_error == NULL); + assert(error != NULL); + + input_curl_easy_free(c); + + c->postponed_error = error; + + g_cond_broadcast(curl.cond); +} + +/** + * Abort and free all HTTP requests. + * + * The caller must lock the mutex. Runs in the I/O thread. + */ +static void +input_curl_abort_all_requests(GError *error) +{ + while (curl.requests != NULL) { + struct input_curl *is = curl.requests->data; + input_curl_request_abort(is, g_error_copy(error)); + } + + g_error_free(error); +} + +/** + * A HTTP request is finished. + * + * The caller must lock the mutex. Runs in the I/O thread. + */ +static void +input_curl_request_done(struct input_curl *c, CURLcode result, long status) +{ + assert(c->easy == NULL); + assert(c->base.ready); + + if (result != CURLE_OK) { + GError *error = g_error_new(curl_quark(), result, + "curl failed: %s", + c->error); + input_curl_request_abort(c, error); + } else if (status < 200 || status >= 300) { + GError *error = g_error_new(curl_quark(), 0, + "got HTTP status %ld", + status); + input_curl_request_abort(c, error); + } else { + g_cond_broadcast(curl.cond); + } +} + +static void +input_curl_handle_done(CURL *easy_handle, CURLcode result) +{ + struct input_curl *c = input_curl_find_request(easy_handle); + assert(c != NULL); + + long status = 0; + curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); + + input_curl_easy_free(c); + input_curl_request_done(c, result, status); +} + +/** + * Check for finished HTTP responses. + * + * The caller must lock the mutex. Runs in the I/O thread. + */ +static void +input_curl_info_read(void) +{ + CURLMsg *msg; + int msgs_in_queue; + + while ((msg = curl_multi_info_read(curl.multi, + &msgs_in_queue)) != NULL) { + if (msg->msg == CURLMSG_DONE) + input_curl_handle_done(msg->easy_handle, msg->data.result); + } +} + +/** + * Give control to CURL. + * + * The caller must lock the mutex. Runs in the I/O thread. + */ +static bool +input_curl_perform(void) +{ + CURLMcode mcode; + + do { + int running_handles; + mcode = curl_multi_perform(curl.multi, &running_handles); + } while (mcode == CURLM_CALL_MULTI_PERFORM); + + if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { + GError *error = g_error_new(curl_quark(), mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); + input_curl_abort_all_requests(error); + return false; + } + + return true; +} + +/* + * GSource methods + * + */ + +/** + * The GSource prepare() method implementation. + */ +static gboolean +input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r) +{ + curl_update_fds(); + +#if LIBCURL_VERSION_NUM >= 0x070f04 + curl.timeout = false; + + long timeout2; + CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2); + if (mcode == CURLM_OK) { + if (timeout2 >= 0) { + g_source_get_current_time(source, + &curl.absolute_timeout); + g_time_val_add(&curl.absolute_timeout, + timeout2 * 1000); + } + + if (timeout2 >= 0 && timeout2 < 10) + /* CURL 7.21.1 likes to report "timeout=0", + which means we're running in a busy loop. + Quite a bad idea to waste so much CPU. + Let's use a lower limit of 10ms. */ + timeout2 = 10; + + *timeout_r = timeout2; + + curl.timeout = timeout2 >= 0; + } else + g_warning("curl_multi_timeout() failed: %s\n", + curl_multi_strerror(mcode)); +#else + (void)timeout_r; +#endif + + return false; +} + +/** + * The GSource check() method implementation. + */ +static gboolean +input_curl_source_check(G_GNUC_UNUSED GSource *source) +{ +#if LIBCURL_VERSION_NUM >= 0x070f04 + if (curl.timeout) { + /* when a timeout has expired, we need to call + curl_multi_perform(), even if there was no file + descriptor event */ + + GTimeVal now; + g_source_get_current_time(source, &now); + if (now.tv_sec > curl.absolute_timeout.tv_sec || + (now.tv_sec == curl.absolute_timeout.tv_sec && + now.tv_usec >= curl.absolute_timeout.tv_usec)) + return true; + } +#endif + + for (GSList *i = curl.fds; i != NULL; i = i->next) { + GPollFD *poll_fd = i->data; + if (poll_fd->revents != 0) + return true; + } + + return false; +} + +/** + * The GSource dispatch() method implementation. The callback isn't + * used, because we're handling all events directly. + */ +static gboolean +input_curl_source_dispatch(G_GNUC_UNUSED GSource *source, + G_GNUC_UNUSED GSourceFunc callback, + G_GNUC_UNUSED gpointer user_data) +{ + g_static_mutex_lock(&curl.mutex); + + if (input_curl_perform()) + input_curl_info_read(); + + g_static_mutex_unlock(&curl.mutex); + + return true; +} + +/** + * The vtable for our GSource implementation. Unfortunately, we + * cannot declare it "const", because g_source_new() takes a non-const + * pointer, for whatever reason. + */ +static GSourceFuncs curl_source_funcs = { + .prepare = input_curl_source_prepare, + .check = input_curl_source_check, + .dispatch = input_curl_source_dispatch, +}; + +/* + * input_plugin methods + * + */ + static bool input_curl_init(const struct config_param *param, G_GNUC_UNUSED GError **error_r) { CURLcode code = curl_global_init(CURL_GLOBAL_ALL); if (code != CURLE_OK) { - g_warning("curl_global_init() failed: %s\n", - curl_easy_strerror(code)); + g_set_error(error_r, curl_quark(), code, + "curl_global_init() failed: %s\n", + curl_easy_strerror(code)); return false; } @@ -140,20 +669,61 @@ input_curl_init(const struct config_param *param, ""); } + curl.multi = curl_multi_init(); + if (curl.multi == NULL) { + g_set_error(error_r, curl_quark(), 0, + "curl_multi_init() failed"); + return false; + } + + curl.cond = g_cond_new(); + + curl.source = g_source_new(&curl_source_funcs, sizeof(*curl.source)); + curl.source_id = g_source_attach(curl.source, io_thread_context()); + return true; } +static gpointer +curl_destroy_sources(G_GNUC_UNUSED gpointer data) +{ + g_source_destroy(curl.source); + + if (curl.dirty_source_id != 0) { + GSource *source = + g_main_context_find_source_by_id(io_thread_context(), + curl.dirty_source_id); + assert(source != NULL); + curl.dirty_source_id = 0; + + g_source_destroy(source); + } + + return NULL; +} + static void input_curl_finish(void) { + assert(curl.requests == NULL); + + io_thread_call(curl_destroy_sources, NULL); + + curl_multi_cleanup(curl.multi); + g_cond_free(curl.cond); + curl_slist_free_all(http_200_aliases); curl_global_cleanup(); } +#if LIBCURL_VERSION_NUM >= 0x071200 + /** * Determine the total sizes of all buffers, including portions that * have already been consumed. + * + * The caller must lock the mutex. */ G_GNUC_PURE static size_t @@ -170,6 +740,8 @@ curl_total_buffer_size(const struct input_curl *c) return total; } +#endif + static void buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) { @@ -180,25 +752,9 @@ buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) g_free(data); } -/** - * Frees the current "libcurl easy" handle, and everything associated - * with it. - */ static void -input_curl_easy_free(struct input_curl *c) +input_curl_flush_buffers(struct input_curl *c) { - if (c->easy != NULL) { - curl_multi_remove_handle(c->multi, c->easy); - curl_easy_cleanup(c->easy); - c->easy = NULL; - } - - curl_slist_free_all(c->request_headers); - c->request_headers = NULL; - - g_free(c->range); - c->range = NULL; - g_queue_foreach(c->buffers, buffer_free_callback, NULL); g_queue_clear(c->buffers); } @@ -213,10 +769,8 @@ input_curl_free(struct input_curl *c) tag_free(c->tag); g_free(c->meta_name); - input_curl_easy_free(c); - - if (c->multi != NULL) - curl_multi_cleanup(c->multi); + input_curl_easy_free_indirect(c); + input_curl_flush_buffers(c); g_queue_free(c->buffers); @@ -236,119 +790,15 @@ input_curl_tag(struct input_stream *is) } static bool -input_curl_multi_info_read(struct input_curl *c, GError **error_r) -{ - CURLMsg *msg; - int msgs_in_queue; - - while ((msg = curl_multi_info_read(c->multi, - &msgs_in_queue)) != NULL) { - if (msg->msg == CURLMSG_DONE) { - c->eof = true; - c->base.ready = true; - - if (msg->data.result != CURLE_OK) { - g_set_error(error_r, curl_quark(), - msg->data.result, - "curl failed: %s", c->error); - return false; - } - } - } - - return true; -} - -/** - * Wait for the libcurl socket. - * - * @return -1 on error, 0 if no data is available yet, 1 if data is - * available - */ -static int -input_curl_select(struct input_curl *c, GError **error_r) -{ - fd_set rfds, wfds, efds; - int max_fd, ret; - CURLMcode mcode; - struct timeval timeout = { - .tv_sec = 1, - .tv_usec = 0, - }; - - assert(!c->eof); - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&efds); - - mcode = curl_multi_fdset(c->multi, &rfds, &wfds, &efds, &max_fd); - if (mcode != CURLM_OK) { - g_set_error(error_r, curl_quark(), mcode, - "curl_multi_fdset() failed: %s", - curl_multi_strerror(mcode)); - return -1; - } - -#if LIBCURL_VERSION_NUM >= 0x070f04 - long timeout2; - mcode = curl_multi_timeout(c->multi, &timeout2); - if (mcode != CURLM_OK) { - g_warning("curl_multi_timeout() failed: %s\n", - curl_multi_strerror(mcode)); - return -1; - } - - if (timeout2 >= 0) { - if (timeout2 > 10000) - timeout2 = 10000; - - timeout.tv_sec = timeout2 / 1000; - timeout.tv_usec = (timeout2 % 1000) * 1000; - } -#endif - - ret = select(max_fd + 1, &rfds, &wfds, &efds, &timeout); - if (ret < 0) - g_set_error(error_r, g_quark_from_static_string("errno"), - errno, - "select() failed: %s\n", g_strerror(errno)); - - return ret; -} - -static bool -fill_buffer(struct input_stream *is, GError **error_r) +fill_buffer(struct input_curl *c, GError **error_r) { - struct input_curl *c = (struct input_curl *)is; - CURLMcode mcode = CURLM_CALL_MULTI_PERFORM; - - while (!c->eof && g_queue_is_empty(c->buffers)) { - int running_handles; - bool bret; - - if (mcode != CURLM_CALL_MULTI_PERFORM) { - /* if we're still here, there is no input yet - - wait for input */ - int ret = input_curl_select(c, error_r); - if (ret <= 0) - /* no data yet or error */ - return false; - } - - mcode = curl_multi_perform(c->multi, &running_handles); - if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - g_set_error(error_r, curl_quark(), mcode, - "curl_multi_perform() failed: %s", - curl_multi_strerror(mcode)); - c->eof = true; - is->ready = true; - return false; - } + while (c->easy != NULL && g_queue_is_empty(c->buffers)) + g_cond_wait(curl.cond, g_static_mutex_get_mutex(&curl.mutex)); - bret = input_curl_multi_info_read(c, error_r); - if (!bret) - return false; + if (c->postponed_error != NULL) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; + return false; } return !g_queue_is_empty(c->buffers); @@ -453,12 +903,16 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size, size_t nbytes = 0; char *dest = ptr; + g_static_mutex_lock(&curl.mutex); + do { /* fill the buffer */ - success = fill_buffer(is, error_r); - if (!success) + success = fill_buffer(c, error_r); + if (!success) { + g_static_mutex_unlock(&curl.mutex); return 0; + } /* send buffer contents */ @@ -476,6 +930,13 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size, is->offset += (goffset)nbytes; +#if LIBCURL_VERSION_NUM >= 0x071200 + if (c->paused && curl_total_buffer_size(c) < CURL_MAX_BUFFERED) + io_thread_call(input_curl_resume, c); +#endif + + g_static_mutex_unlock(&curl.mutex); + return nbytes; } @@ -492,7 +953,11 @@ input_curl_eof(G_GNUC_UNUSED struct input_stream *is) { struct input_curl *c = (struct input_curl *)is; - return c->eof && g_queue_is_empty(c->buffers); + g_static_mutex_lock(&curl.mutex); + bool eof = c->easy == NULL && g_queue_is_empty(c->buffers); + g_static_mutex_unlock(&curl.mutex); + + return eof; } static int @@ -500,41 +965,21 @@ input_curl_buffer(struct input_stream *is, GError **error_r) { struct input_curl *c = (struct input_curl *)is; - if (curl_total_buffer_size(c) >= CURL_MAX_BUFFERED) - return 0; + g_static_mutex_lock(&curl.mutex); - CURLMcode mcode; - int running_handles; - bool ret; + int result; + if (c->postponed_error != NULL) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; + result = -1; + } else if (g_queue_is_empty(c->buffers)) + result = 0; + else + result = 1; - c->buffered = false; - - if (!is->ready && !c->eof) - /* not ready yet means the caller is waiting in a busy - loop; relax that by calling select() on the - socket */ - if (input_curl_select(c, error_r) < 0) - return -1; - - do { - mcode = curl_multi_perform(c->multi, &running_handles); - } while (mcode == CURLM_CALL_MULTI_PERFORM && - g_queue_is_empty(c->buffers)); + g_static_mutex_unlock(&curl.mutex); - if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - g_set_error(error_r, curl_quark(), mcode, - "curl_multi_perform() failed: %s", - curl_multi_strerror(mcode)); - c->eof = true; - is->ready = true; - return -1; - } - - ret = input_curl_multi_info_read(c, error_r); - if (!ret) - return -1; - - return c->buffered; + return result; } /** called by curl when new data is available */ @@ -632,15 +1077,24 @@ input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) if (size == 0) return 0; +#if LIBCURL_VERSION_NUM >= 0x071200 + if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) { + c->paused = true; + return CURL_WRITEFUNC_PAUSE; + } +#endif + buffer = g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); buffer->size = size; buffer->consumed = 0; memcpy(buffer->data, ptr, size); + g_queue_push_tail(c->buffers, buffer); - c->buffered = true; c->base.ready = true; + g_cond_broadcast(curl.cond); + return size; } @@ -648,9 +1102,6 @@ static bool input_curl_easy_init(struct input_curl *c, GError **error_r) { CURLcode code; - CURLMcode mcode; - - c->eof = false; c->easy = curl_easy_init(); if (c->easy == NULL) { @@ -659,14 +1110,6 @@ input_curl_easy_init(struct input_curl *c, GError **error_r) return false; } - mcode = curl_multi_add_handle(c->multi, c->easy); - if (mcode != CURLM_OK) { - g_set_error(error_r, curl_quark(), mcode, - "curl_multi_add_handle() failed: %s", - curl_multi_strerror(mcode)); - return false; - } - curl_easy_setopt(c->easy, CURLOPT_USERAGENT, "Music Player Daemon " VERSION); curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION, @@ -677,6 +1120,7 @@ input_curl_easy_init(struct input_curl *c, GError **error_r) curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c); curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(c->easy, CURLOPT_NETRC, 1); curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true); curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error); @@ -713,38 +1157,6 @@ input_curl_easy_init(struct input_curl *c, GError **error_r) return true; } -void -input_curl_reinit(struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - assert(c->base.plugin == &input_plugin_curl); - assert(c->easy != NULL); - - curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is); - curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, is); -} - -static bool -input_curl_send_request(struct input_curl *c, GError **error_r) -{ - CURLMcode mcode; - int running_handles; - - do { - mcode = curl_multi_perform(c->multi, &running_handles); - } while (mcode == CURLM_CALL_MULTI_PERFORM); - - if (mcode != CURLM_OK) { - g_set_error(error_r, curl_quark(), mcode, - "curl_multi_perform() failed: %s", - curl_multi_strerror(mcode)); - return false; - } - - return true; -} - static bool input_curl_seek(struct input_stream *is, goffset offset, int whence, GError **error_r) @@ -810,14 +1222,14 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence, /* close the old connection and open a new one */ - input_curl_easy_free(c); + input_curl_easy_free_indirect(c); + input_curl_flush_buffers(c); is->offset = offset; if (is->offset == is->size) { /* seek to EOF: simulate empty result; avoid triggering a "416 Requested Range Not Satisfiable" response */ - c->eof = true; return true; } @@ -832,18 +1244,34 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence, curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); } - ret = input_curl_send_request(c, error_r); - if (!ret) + g_static_mutex_lock(&curl.mutex); + + c->base.ready = false; + + if (!input_curl_easy_add(c, error_r)) { + g_static_mutex_unlock(&curl.mutex); return false; + } + + while (!c->base.ready) + g_cond_wait(curl.cond, g_static_mutex_get_mutex(&curl.mutex)); - return input_curl_multi_info_read(c, error_r); + if (c->postponed_error != NULL) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; + g_static_mutex_unlock(&curl.mutex); + return false; + } + + g_static_mutex_unlock(&curl.mutex); + + return true; } static struct input_stream * input_curl_open(const char *url, GError **error_r) { struct input_curl *c; - bool ret; if (strncmp(url, "http://", 7) != 0) return NULL; @@ -854,35 +1282,27 @@ input_curl_open(const char *url, GError **error_r) c->url = g_strdup(url); c->buffers = g_queue_new(); - c->multi = curl_multi_init(); - if (c->multi == NULL) { - g_set_error(error_r, curl_quark(), 0, - "curl_multi_init() failed"); - input_curl_free(c); - return NULL; - } - icy_clear(&c->icy_metadata); c->tag = NULL; - ret = input_curl_easy_init(c, error_r); - if (!ret) { - input_curl_free(c); - return NULL; - } +#if LIBCURL_VERSION_NUM >= 0x071200 + c->paused = false; +#endif - ret = input_curl_send_request(c, error_r); - if (!ret) { + if (!input_curl_easy_init(c, error_r)) { input_curl_free(c); return NULL; } - ret = input_curl_multi_info_read(c, error_r); - if (!ret) { + g_static_mutex_lock(&curl.mutex); + if (!input_curl_easy_add(c, error_r)) { + g_static_mutex_unlock(&curl.mutex); input_curl_free(c); return NULL; } + g_static_mutex_unlock(&curl.mutex); + return &c->base; } diff --git a/src/input/curl_input_plugin.h b/src/input/curl_input_plugin.h index be7db4e26..c6e71bf40 100644 --- a/src/input/curl_input_plugin.h +++ b/src/input/curl_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,12 +24,4 @@ struct input_stream; extern const struct input_plugin input_plugin_curl; -/** - * This is a workaround for an input_stream API deficiency; after - * exchanging the input_stream pointer in input_rewind_open(), this - * function is called to reinitialize CURL's data pointers. - */ -void -input_curl_reinit(struct input_stream *is); - #endif diff --git a/src/input/despotify_input_plugin.c b/src/input/despotify_input_plugin.c new file mode 100644 index 000000000..b63663c50 --- /dev/null +++ b/src/input/despotify_input_plugin.c @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "input/despotify_input_plugin.h" +#include "input_plugin.h" +#include "tag.h" +#include "despotify_utils.h" + +#include <glib.h> + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <despotify.h> + +#include <stdio.h> + +struct input_despotify { + struct input_stream base; + + struct despotify_session *session; + struct ds_track *track; + struct tag *tag; + struct ds_pcm_data pcm; + size_t len_available; + bool eof; +}; + + +static void +refill_buffer(struct input_despotify *ctx) +{ + /* Wait until there is data */ + while (1) { + int rc = despotify_get_pcm(ctx->session, &ctx->pcm); + + if (rc == 0 && ctx->pcm.len) { + ctx->len_available = ctx->pcm.len; + break; + } + if (ctx->eof == true) + break; + + if (rc < 0) { + g_debug("despotify_get_pcm error\n"); + ctx->eof = true; + break; + } + + /* Wait a while until next iteration */ + usleep(50 * 1000); + } +} + +static void callback(G_GNUC_UNUSED struct despotify_session* ds, + int sig, G_GNUC_UNUSED void* data, void* callback_data) +{ + struct input_despotify *ctx = (struct input_despotify *)callback_data; + + switch (sig) { + case DESPOTIFY_NEW_TRACK: + break; + + case DESPOTIFY_TIME_TELL: + break; + + case DESPOTIFY_TRACK_PLAY_ERROR: + g_debug("Track play error\n"); + ctx->eof = true; + ctx->len_available = 0; + break; + + case DESPOTIFY_END_OF_PLAYLIST: + ctx->eof = true; + g_debug("End of playlist: %d\n", ctx->eof); + break; + } +} + + +static struct input_stream * +input_despotify_open(const char *url, G_GNUC_UNUSED GError **error_r) +{ + struct input_despotify *ctx; + struct despotify_session *session; + struct ds_link *ds_link; + struct ds_track *track; + + if (!g_str_has_prefix(url, "spt://")) + return NULL; + + session = mpd_despotify_get_session(); + if (!session) + return NULL; + + ds_link = despotify_link_from_uri(url + 6); + if (!ds_link) { + g_debug("Can't find %s\n", url); + return NULL; + } + if (ds_link->type != LINK_TYPE_TRACK) { + despotify_free_link(ds_link); + return NULL; + } + + ctx = g_new(struct input_despotify, 1); + memset(ctx, 0, sizeof(*ctx)); + + track = despotify_link_get_track(session, ds_link); + despotify_free_link(ds_link); + if (!track) { + g_free(ctx); + return NULL; + } + + input_stream_init(&ctx->base, &input_plugin_despotify, url); + ctx->session = session; + ctx->track = track; + ctx->tag = mpd_despotify_tag_from_track(track); + ctx->eof = false; + /* Despotify outputs pcm data */ + ctx->base.mime = g_strdup("audio/x-mpd-cdda-pcm"); + ctx->base.ready = true; + + if (!mpd_despotify_register_callback(callback, ctx)) { + despotify_free_link(ds_link); + + return NULL; + } + + if (despotify_play(ctx->session, ctx->track, false) == false) { + despotify_free_track(ctx->track); + g_free(ctx); + return NULL; + } + + return &ctx->base; +} + +static size_t +input_despotify_read(struct input_stream *is, void *ptr, size_t size, + G_GNUC_UNUSED GError **error_r) +{ + struct input_despotify *ctx = (struct input_despotify *)is; + size_t to_cpy = size; + + if (ctx->len_available == 0) + refill_buffer(ctx); + + if (ctx->len_available < size) + to_cpy = ctx->len_available; + memcpy(ptr, ctx->pcm.buf, to_cpy); + ctx->len_available -= to_cpy; + + is->offset += to_cpy; + + return to_cpy; +} + +static void +input_despotify_close(struct input_stream *is) +{ + struct input_despotify *ctx = (struct input_despotify *)is; + + if (ctx->tag != NULL) + tag_free(ctx->tag); + + mpd_despotify_unregister_callback(callback); + despotify_free_track(ctx->track); + input_stream_deinit(&ctx->base); + g_free(ctx); +} + +static bool +input_despotify_eof(struct input_stream *is) +{ + struct input_despotify *ctx = (struct input_despotify *)is; + + return ctx->eof; +} + +static bool +input_despotify_seek(G_GNUC_UNUSED struct input_stream *is, + G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, + G_GNUC_UNUSED GError **error_r) +{ + return false; +} + +static struct tag * +input_despotify_tag(struct input_stream *is) +{ + struct input_despotify *ctx = (struct input_despotify *)is; + struct tag *tag = ctx->tag; + + ctx->tag = NULL; + + return tag; +} + +const struct input_plugin input_plugin_despotify = { + .name = "spt", + .open = input_despotify_open, + .close = input_despotify_close, + .read = input_despotify_read, + .eof = input_despotify_eof, + .seek = input_despotify_seek, + .tag = input_despotify_tag, +}; diff --git a/src/input/despotify_input_plugin.h b/src/input/despotify_input_plugin.h new file mode 100644 index 000000000..4c070d882 --- /dev/null +++ b/src/input/despotify_input_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef INPUT_DESPOTIFY_H +#define INPUT_DESPOTIFY_H + +extern const struct input_plugin input_plugin_despotify; + +#endif diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/ffmpeg_input_plugin.c index 0a6be29bc..24d80a379 100644 --- a/src/input/ffmpeg_input_plugin.c +++ b/src/input/ffmpeg_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,13 +21,8 @@ #include "input/ffmpeg_input_plugin.h" #include "input_plugin.h" -#ifdef OLD_FFMPEG_INCLUDES -#include <avio.h> -#include <avformat.h> -#else #include <libavformat/avio.h> #include <libavformat/avformat.h> -#endif #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_ffmpeg" @@ -35,7 +30,11 @@ struct input_ffmpeg { struct input_stream base; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) + AVIOContext *h; +#else URLContext *h; +#endif bool eof; }; @@ -46,20 +45,29 @@ ffmpeg_quark(void) return g_quark_from_static_string("ffmpeg"); } +static inline bool +input_ffmpeg_supported(void) +{ +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) + void *opaque = NULL; + return avio_enum_protocols(&opaque, 0) != NULL; +#else + return av_protocol_next(NULL) != NULL; +#endif +} + static bool input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param, G_GNUC_UNUSED GError **error_r) { av_register_all(); -#if LIBAVFORMAT_VERSION_MAJOR >= 52 /* disable this plugin if there's no registered protocol */ - if (av_protocol_next(NULL) == NULL) { + if (!input_ffmpeg_supported()) { g_set_error(error_r, ffmpeg_quark(), 0, "No protocol"); return false; } -#endif return true; } @@ -80,7 +88,13 @@ input_ffmpeg_open(const char *uri, GError **error_r) i = g_new(struct input_ffmpeg, 1); input_stream_init(&i->base, &input_plugin_ffmpeg, uri); +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0) + int ret = avio_open(&i->h, uri, AVIO_FLAG_READ); +#elif LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) + int ret = avio_open(&i->h, uri, AVIO_RDONLY); +#else int ret = url_open(&i->h, uri, URL_RDONLY); +#endif if (ret != 0) { g_free(i); g_set_error(error_r, ffmpeg_quark(), ret, @@ -91,8 +105,13 @@ input_ffmpeg_open(const char *uri, GError **error_r) i->eof = false; i->base.ready = true; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) + i->base.seekable = (i->h->seekable & AVIO_SEEKABLE_NORMAL) != 0; + i->base.size = avio_size(i->h); +#else i->base.seekable = !i->h->is_streamed; i->base.size = url_filesize(i->h); +#endif /* hack to make MPD select the "ffmpeg" decoder plugin - since avio.h doesn't tell us the MIME type of the resource, we @@ -109,7 +128,11 @@ input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size, { struct input_ffmpeg *i = (struct input_ffmpeg *)is; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) + int ret = avio_read(i->h, ptr, size); +#else int ret = url_read(i->h, ptr, size); +#endif if (ret <= 0) { if (ret < 0) g_set_error(error_r, ffmpeg_quark(), 0, @@ -128,7 +151,11 @@ input_ffmpeg_close(struct input_stream *is) { struct input_ffmpeg *i = (struct input_ffmpeg *)is; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) + avio_close(i->h); +#else url_close(i->h); +#endif input_stream_deinit(&i->base); g_free(i); } @@ -146,7 +173,11 @@ input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence, G_GNUC_UNUSED GError **error_r) { struct input_ffmpeg *i = (struct input_ffmpeg *)is; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) + int64_t ret = avio_seek(i->h, offset, whence); +#else int64_t ret = url_seek(i->h, offset, whence); +#endif if (ret >= 0) { i->eof = false; diff --git a/src/input/ffmpeg_input_plugin.h b/src/input/ffmpeg_input_plugin.h index ff87064be..393836ca5 100644 --- a/src/input/ffmpeg_input_plugin.h +++ b/src/input/ffmpeg_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c index 3646c656e..82ce59e5e 100644 --- a/src/input/file_input_plugin.c +++ b/src/input/file_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/file_input_plugin.h b/src/input/file_input_plugin.h index 40340e8bd..f24769d57 100644 --- a/src/input/file_input_plugin.h +++ b/src/input/file_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c index 834d111b8..d74196574 100644 --- a/src/input/mms_input_plugin.c +++ b/src/input/mms_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/mms_input_plugin.h b/src/input/mms_input_plugin.h index 2e10cfbb9..d6aa593f2 100644 --- a/src/input/mms_input_plugin.h +++ b/src/input/mms_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c index 6325a978e..5d2ccb093 100644 --- a/src/input/rewind_input_plugin.c +++ b/src/input/rewind_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,6 @@ #include "config.h" #include "input/rewind_input_plugin.h" -#include "input/curl_input_plugin.h" #include "input_plugin.h" #include "tag.h" diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h index 23d25d94d..83abe257a 100644 --- a/src/input/rewind_input_plugin.h +++ b/src/input/rewind_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/soup_input_plugin.c b/src/input/soup_input_plugin.c new file mode 100644 index 000000000..b732f2e15 --- /dev/null +++ b/src/input/soup_input_plugin.c @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "input/soup_input_plugin.h" +#include "input_plugin.h" +#include "io_thread.h" +#include "conf.h" + +#include <libsoup/soup-uri.h> +#include <libsoup/soup-session-async.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_soup" + +/** + * Do not buffer more than this number of bytes. It should be a + * reasonable limit that doesn't make low-end machines suffer too + * much, but doesn't cause stuttering on high-latency lines. + */ +static const size_t SOUP_MAX_BUFFERED = 512 * 1024; + +static SoupURI *soup_proxy; +static SoupSession *soup_session; + +struct input_soup { + struct input_stream base; + + GMutex *mutex; + GCond *cond; + + SoupMessage *msg; + + GQueue *buffers; + + size_t current_consumed; + + size_t total_buffered; + + bool alive, ready, pause, eof; +}; + +static inline GQuark +soup_quark(void) +{ + return g_quark_from_static_string("soup"); +} + +static bool +input_soup_init(const struct config_param *param, GError **error_r) +{ + assert(soup_proxy == NULL); + assert(soup_session == NULL); + + g_type_init(); + + const char *proxy = config_get_block_string(param, "proxy", NULL); + + if (proxy != NULL) { + soup_proxy = soup_uri_new(proxy); + if (soup_proxy == NULL) { + g_set_error(error_r, soup_quark(), 0, + "failed to parse proxy setting"); + return false; + } + } + + soup_session = + soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI, + soup_proxy, + SOUP_SESSION_ASYNC_CONTEXT, + io_thread_context(), + NULL); + + return true; +} + +static void +input_soup_finish(void) +{ + assert(soup_session != NULL); + + soup_session_abort(soup_session); + g_object_unref(G_OBJECT(soup_session)); + + if (soup_proxy != NULL) + soup_uri_free(soup_proxy); +} + +static void +input_soup_session_callback(G_GNUC_UNUSED SoupSession *session, + G_GNUC_UNUSED SoupMessage *msg, gpointer user_data) +{ + struct input_soup *s = user_data; + + assert(msg == s->msg); + + g_mutex_lock(s->mutex); + s->alive = false; + g_cond_broadcast(s->cond); + g_mutex_unlock(s->mutex); +} + +static void +input_soup_got_headers(SoupMessage *msg, gpointer user_data) +{ + struct input_soup *s = user_data; + + if (!SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) { + soup_session_cancel_message(soup_session, msg, + SOUP_STATUS_CANCELLED); + return; + } + + soup_message_body_set_accumulate(msg->response_body, false); + + g_mutex_lock(s->mutex); + s->ready = true; + g_cond_broadcast(s->cond); + g_mutex_unlock(s->mutex); +} + +static void +input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) +{ + struct input_soup *s = user_data; + + assert(msg == s->msg); + + g_mutex_lock(s->mutex); + + g_queue_push_tail(s->buffers, soup_buffer_copy(chunk)); + s->total_buffered += chunk->length; + + if (s->total_buffered >= SOUP_MAX_BUFFERED && !s->pause) { + s->pause = true; + soup_session_pause_message(soup_session, msg); + } + + g_cond_broadcast(s->cond); + g_mutex_unlock(s->mutex); +} + +static void +input_soup_got_body(SoupMessage *msg, gpointer user_data) +{ + struct input_soup *s = user_data; + + assert(msg == s->msg); + + g_mutex_lock(s->mutex); + + s->eof = true; + s->alive = false; + + g_cond_broadcast(s->cond); + g_mutex_unlock(s->mutex); +} + +static bool +input_soup_wait_data(struct input_soup *s) +{ + while (true) { + if (s->eof) + return true; + + if (!s->alive) + return false; + + if (!g_queue_is_empty(s->buffers)) + return true; + + assert(s->current_consumed == 0); + + g_cond_wait(s->cond, s->mutex); + } +} + +static struct input_stream * +input_soup_open(const char *uri, G_GNUC_UNUSED GError **error_r) +{ + if (strncmp(uri, "http://", 7) != 0) + return NULL; + + struct input_soup *s = g_new(struct input_soup, 1); + input_stream_init(&s->base, &input_plugin_soup, uri); + + s->mutex = g_mutex_new(); + s->cond = g_cond_new(); + + s->buffers = g_queue_new(); + s->current_consumed = 0; + s->total_buffered = 0; + + s->msg = soup_message_new(SOUP_METHOD_GET, uri); + soup_message_set_flags(s->msg, SOUP_MESSAGE_NO_REDIRECT); + + soup_message_headers_append(s->msg->request_headers, "User-Agent", + "Music Player Daemon " VERSION); + + g_signal_connect(s->msg, "got-headers", + G_CALLBACK(input_soup_got_headers), s); + g_signal_connect(s->msg, "got-chunk", + G_CALLBACK(input_soup_got_chunk), s); + g_signal_connect(s->msg, "got-body", + G_CALLBACK(input_soup_got_body), s); + + s->alive = true; + s->ready = false; + s->pause = false; + s->eof = false; + + soup_session_queue_message(soup_session, s->msg, + input_soup_session_callback, s); + + return &s->base; +} + +static void +input_soup_close(struct input_stream *is) +{ + struct input_soup *s = (struct input_soup *)is; + + g_mutex_lock(s->mutex); + + if (s->alive) { + assert(s->msg != NULL); + + soup_session_cancel_message(soup_session, s->msg, + SOUP_STATUS_CANCELLED); + s->alive = false; + } + + g_mutex_unlock(s->mutex); + g_mutex_free(s->mutex); + g_cond_free(s->cond); + + SoupBuffer *buffer; + while ((buffer = g_queue_pop_head(s->buffers)) != NULL) + soup_buffer_free(buffer); + g_queue_free(s->buffers); + + input_stream_deinit(&s->base); + g_free(s); +} + +static int +input_soup_buffer(struct input_stream *is, GError **error_r) +{ + struct input_soup *s = (struct input_soup *)is; + + g_mutex_lock(s->mutex); + + if (s->pause) { + if (s->total_buffered >= SOUP_MAX_BUFFERED) { + g_mutex_unlock(s->mutex); + return 1; + } + + s->pause = false; + soup_session_unpause_message(soup_session, s->msg); + } + + + bool success = input_soup_wait_data(s); + s->base.ready = s->ready; + g_mutex_unlock(s->mutex); + + if (!success) { + g_set_error_literal(error_r, soup_quark(), 0, "HTTP failure"); + return -1; + } + + return 1; +} + +static size_t +input_soup_read(struct input_stream *is, void *ptr, size_t size, + G_GNUC_UNUSED GError **error_r) +{ + struct input_soup *s = (struct input_soup *)is; + + g_mutex_lock(s->mutex); + + if (!input_soup_wait_data(s)) { + assert(!s->alive); + g_mutex_unlock(s->mutex); + + return 0; + } + + s->base.ready = s->ready; + + char *p0 = ptr, *p = p0, *p_end = p0 + size; + + while (p < p_end) { + SoupBuffer *buffer = g_queue_pop_head(s->buffers); + if (buffer == NULL) { + assert(s->current_consumed == 0); + break; + } + + assert(s->current_consumed < buffer->length); + assert(s->total_buffered >= buffer->length); + + const char *q = buffer->data; + q += s->current_consumed; + + size_t remaining = buffer->length - s->current_consumed; + size_t nbytes = p_end - p; + if (nbytes > remaining) + nbytes = remaining; + + memcpy(p, q, nbytes); + p += nbytes; + + s->current_consumed += remaining; + if (s->current_consumed >= buffer->length) { + /* done with this buffer */ + s->total_buffered -= buffer->length; + soup_buffer_free(buffer); + s->current_consumed = 0; + } else { + /* partial read */ + assert(p == p_end); + + g_queue_push_head(s->buffers, buffer); + } + } + + if (s->pause && s->total_buffered < SOUP_MAX_BUFFERED) { + s->pause = false; + soup_session_unpause_message(soup_session, s->msg); + } + + size_t nbytes = p - p0; + s->base.offset += nbytes; + + g_mutex_unlock(s->mutex); + return nbytes; +} + +static bool +input_soup_eof(G_GNUC_UNUSED struct input_stream *is) +{ + struct input_soup *s = (struct input_soup *)is; + + return !s->alive && g_queue_is_empty(s->buffers); +} + +const struct input_plugin input_plugin_soup = { + .name = "soup", + .init = input_soup_init, + .finish = input_soup_finish, + + .open = input_soup_open, + .close = input_soup_close, + .buffer = input_soup_buffer, + .read = input_soup_read, + .eof = input_soup_eof, +}; diff --git a/src/input/soup_input_plugin.h b/src/input/soup_input_plugin.h new file mode 100644 index 000000000..689b2d971 --- /dev/null +++ b/src/input/soup_input_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_SOUP_H +#define MPD_INPUT_SOUP_H + +extern const struct input_plugin input_plugin_soup; + +#endif diff --git a/src/input_init.c b/src/input_init.c index 1438c3e52..cf5affb4e 100644 --- a/src/input_init.c +++ b/src/input_init.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "conf.h" #include "glib_compat.h" +#include <assert.h> #include <string.h> static inline GQuark @@ -67,6 +68,11 @@ input_stream_global_init(GError **error_r) for (unsigned i = 0; input_plugins[i] != NULL; ++i) { const struct input_plugin *plugin = input_plugins[i]; + + assert(plugin->name != NULL); + assert(*plugin->name != 0); + assert(plugin->open != NULL); + const struct config_param *param = input_plugin_config(plugin->name, &error); if (param == NULL && error != NULL) { diff --git a/src/input_init.h b/src/input_init.h index eded15fa9..ad92cda08 100644 --- a/src/input_init.h +++ b/src/input_init.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ /** * Initializes this library and all input_stream implementations. * - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors */ bool diff --git a/src/input_plugin.h b/src/input_plugin.h index 10be48dbb..70b9155aa 100644 --- a/src/input_plugin.h +++ b/src/input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -35,7 +35,7 @@ struct input_plugin { /** * Global initialization. This method is called when MPD starts. * - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, or * NULL to ignore errors * @return true on success, false if the plugin should be * disabled diff --git a/src/input_registry.c b/src/input_registry.c index 0b9b47d10..5987d5da2 100644 --- a/src/input_registry.c +++ b/src/input_registry.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,10 @@ #include "input/curl_input_plugin.h" #endif +#ifdef ENABLE_SOUP +#include "input/soup_input_plugin.h" +#endif + #ifdef HAVE_FFMPEG #include "input/ffmpeg_input_plugin.h" #endif @@ -37,6 +41,14 @@ #include "input/mms_input_plugin.h" #endif +#ifdef ENABLE_CDIO_PARANOIA +#include "input/cdio_paranoia_input_plugin.h" +#endif + +#ifdef ENABLE_DESPOTIFY +#include "input/despotify_input_plugin.h" +#endif + #include <glib.h> const struct input_plugin *const input_plugins[] = { @@ -47,12 +59,21 @@ const struct input_plugin *const input_plugins[] = { #ifdef ENABLE_CURL &input_plugin_curl, #endif +#ifdef ENABLE_SOUP + &input_plugin_soup, +#endif #ifdef HAVE_FFMPEG &input_plugin_ffmpeg, #endif #ifdef ENABLE_MMS &input_plugin_mms, #endif +#ifdef ENABLE_CDIO_PARANOIA + &input_plugin_cdio_paranoia, +#endif +#ifdef ENABLE_DESPOTIFY + &input_plugin_despotify, +#endif NULL }; diff --git a/src/input_registry.h b/src/input_registry.h index e85d6be8e..a1b057469 100644 --- a/src/input_registry.h +++ b/src/input_registry.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input_stream.c b/src/input_stream.c index e769adb92..d207188cd 100644 --- a/src/input_stream.c +++ b/src/input_stream.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input_stream.h b/src/input_stream.h index 056d008a7..514512592 100644 --- a/src/input_stream.h +++ b/src/input_stream.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/io_thread.c b/src/io_thread.c new file mode 100644 index 000000000..fa6dee337 --- /dev/null +++ b/src/io_thread.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "io_thread.h" +#include "glib_compat.h" + +#include <assert.h> + +static struct { + GMutex *mutex; + GCond *cond; + + GMainContext *context; + GMainLoop *loop; + GThread *thread; +} io; + +void +io_thread_run(void) +{ + assert(io.context != NULL); + assert(io.loop != NULL); + + g_main_loop_run(io.loop); +} + +static gpointer +io_thread_func(G_GNUC_UNUSED gpointer arg) +{ + io_thread_run(); + return NULL; +} + +void +io_thread_init(void) +{ + assert(io.context == NULL); + assert(io.loop == NULL); + assert(io.thread == NULL); + + io.mutex = g_mutex_new(); + io.cond = g_cond_new(); + io.context = g_main_context_new(); + io.loop = g_main_loop_new(io.context, false); +} + +bool +io_thread_start(GError **error_r) +{ + assert(io.context != NULL); + assert(io.loop != NULL); + assert(io.thread == NULL); + + io.thread = g_thread_create(io_thread_func, NULL, true, error_r); + if (io.thread == NULL) + return false; + + return true; +} + +void +io_thread_quit(void) +{ + assert(io.loop != NULL); + + g_main_loop_quit(io.loop); +} + +void +io_thread_deinit(void) +{ + if (io.thread != NULL) { + io_thread_quit(); + + g_thread_join(io.thread); + } + + if (io.loop != NULL) + g_main_loop_unref(io.loop); + + if (io.context != NULL) + g_main_context_unref(io.context); + + g_cond_free(io.cond); + g_mutex_free(io.mutex); +} + +GMainContext * +io_thread_context(void) +{ + return io.context; +} + +bool +io_thread_inside(void) +{ + return io.thread != NULL && g_thread_self() == io.thread; +} + +guint +io_thread_idle_add(GSourceFunc function, gpointer data) +{ + GSource *source = g_idle_source_new(); + g_source_set_callback(source, function, data, NULL); + guint id = g_source_attach(source, io.context); + g_source_unref(source); + return id; +} + +guint +io_thread_timeout_add_seconds(guint interval, + GSourceFunc function, gpointer data) +{ + GSource *source = g_timeout_source_new_seconds(interval); + g_source_set_callback(source, function, data, NULL); + guint id = g_source_attach(source, io.context); + g_source_unref(source); + return id; +} + +struct call_data { + GThreadFunc function; + gpointer data; + bool done; + gpointer result; +}; + +static gboolean +io_thread_call_func(gpointer _data) +{ + struct call_data *data = _data; + + gpointer result = data->function(data->data); + + g_mutex_lock(io.mutex); + data->done = true; + data->result = result; + g_cond_broadcast(io.cond); + g_mutex_unlock(io.mutex); + + return false; +} + +gpointer +io_thread_call(GThreadFunc function, gpointer _data) +{ + assert(io.thread != NULL); + + if (io_thread_inside()) + /* we're already in the I/O thread - no + synchronization needed */ + return function(_data); + + struct call_data data = { + .function = function, + .data = _data, + .done = false, + }; + + io_thread_idle_add(io_thread_call_func, &data); + + g_mutex_lock(io.mutex); + while (!data.done) + g_cond_wait(io.cond, io.mutex); + g_mutex_unlock(io.mutex); + + return data.result; +} diff --git a/src/io_thread.h b/src/io_thread.h new file mode 100644 index 000000000..c91597225 --- /dev/null +++ b/src/io_thread.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_IO_THREAD_H +#define MPD_IO_THREAD_H + +#include <glib.h> +#include <stdbool.h> + +void +io_thread_init(void); + +bool +io_thread_start(GError **error_r); + +/** + * Run the I/O event loop synchronously in the current thread. This + * can be called instead of io_thread_start(). For testing purposes + * only. + */ +void +io_thread_run(void); + +/** + * Ask the I/O thread to quit, but does not wait for it. Usually, you + * don't need to call this function, because io_thread_deinit() + * includes this. + */ +void +io_thread_quit(void); + +void +io_thread_deinit(void); + +G_GNUC_PURE +GMainContext * +io_thread_context(void); + +/** + * Is the current thread the I/O thread? + */ +G_GNUC_PURE +bool +io_thread_inside(void); + +guint +io_thread_idle_add(GSourceFunc function, gpointer data); + +guint +io_thread_timeout_add_seconds(guint interval, + GSourceFunc function, gpointer data); + +/** + * Call a function synchronously in the I/O thread. + */ +gpointer +io_thread_call(GThreadFunc function, gpointer data); + +#endif diff --git a/src/listen.c b/src/listen.c index da2e79909..5c958507d 100644 --- a/src/listen.c +++ b/src/listen.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,7 @@ #include "client.h" #include "conf.h" #include "glib_compat.h" +#include "main.h" #include <string.h> #include <assert.h> @@ -39,7 +40,7 @@ static void listen_callback(int fd, const struct sockaddr *address, size_t address_length, int uid, G_GNUC_UNUSED void *ctx) { - client_new(fd, address, address_length, uid); + client_new(global_player_control, fd, address, address_length, uid); } static bool diff --git a/src/listen.h b/src/listen.h index 449b5ebae..246e83706 100644 --- a/src/listen.h +++ b/src/listen.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/locate.c b/src/locate.c index e27858a0e..8ad61fa02 100644 --- a/src/locate.c +++ b/src/locate.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/locate.h b/src/locate.h index 0283f551b..d5c6f9f56 100644 --- a/src/locate.h +++ b/src/locate.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,7 +32,7 @@ * connected by IPC socket. */ static const char *remoteUrlPrefixes[] = { -#ifdef ENABLE_CURL +#if defined(ENABLE_CURL) || defined(ENABLE_SOUP) "http://", #endif #ifdef ENABLE_MMS @@ -49,6 +49,12 @@ static const char *remoteUrlPrefixes[] = { "rtmpt://", "rtmps://", #endif +#ifdef ENABLE_CDIO_PARANOIA + "cdda://", +#endif +#ifdef ENABLE_DESPOTIFY + "spt://", +#endif NULL }; @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/main.c b/src/main.c index a500e2934..52b1331c4 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,9 @@ #include "config.h" #include "main.h" #include "daemon.h" +#include "io_thread.h" #include "client.h" +#include "client_idle.h" #include "idle.h" #include "command.h" #include "playlist.h" @@ -94,6 +96,8 @@ GMainLoop *main_loop; GCond *main_cond; +struct player_control *global_player_control; + static void glue_daemonize_init(const struct options *options) { @@ -183,7 +187,8 @@ glue_sticker_init(void) static void glue_state_file_init(void) { - state_file_init(config_get_path(CONF_STATE_FILE)); + state_file_init(config_get_path(CONF_STATE_FILE), + global_player_control); } /** @@ -254,7 +259,7 @@ initialize_decoder_and_player(void) if (buffered_before_play > buffered_chunks) buffered_before_play = buffered_chunks; - pc_init(buffered_chunks, buffered_before_play); + global_player_control = pc_new(buffered_chunks, buffered_before_play); } /** @@ -308,6 +313,7 @@ int mpd_main(int argc, char *argv[]) /* enable GLib's thread safety code */ g_thread_init(NULL); + io_thread_init(); winsock_init(); idle_init(); dirvec_init(); @@ -364,7 +370,7 @@ int mpd_main(int argc, char *argv[]) initialize_decoder_and_player(); volume_init(); initAudioConfig(); - audio_output_all_init(); + audio_output_all_init(global_player_control); client_manager_init(); replay_gain_global_init(); @@ -382,9 +388,15 @@ int mpd_main(int argc, char *argv[]) initSigHandlers(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + initZeroconf(); - player_create(); + player_create(global_player_control); if (create_db) { /* the database failed to load: recreate the @@ -410,7 +422,7 @@ int mpd_main(int argc, char *argv[]) /* enable all audio outputs (if not already done by playlist_state_restore() */ - pc_update_audio(); + pc_update_audio(global_player_control); #ifdef WIN32 win32_app_started(); @@ -431,8 +443,8 @@ int mpd_main(int argc, char *argv[]) mpd_inotify_finish(); #endif - state_file_finish(); - pc_kill(); + state_file_finish(global_player_control); + pc_kill(global_player_control); finishZeroconf(); client_manager_deinit(); listen_global_finish(); @@ -457,7 +469,7 @@ int mpd_main(int argc, char *argv[]) mapper_finish(); path_global_finish(); finishPermissions(); - pc_deinit(); + pc_free(global_player_control); command_finish(); update_global_finish(); decoder_plugin_deinit_all(); @@ -470,6 +482,7 @@ int mpd_main(int argc, char *argv[]) dirvec_deinit(); idle_deinit(); stats_global_finish(); + io_thread_deinit(); daemonize_finish(); #ifdef WIN32 WSACleanup(); diff --git a/src/main.h b/src/main.h index 9b9cba018..2a7d75910 100644 --- a/src/main.h +++ b/src/main.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,6 +28,8 @@ extern GMainLoop *main_loop; extern GCond *main_cond; +extern struct player_control *global_player_control; + /** * A entry point for application. * On non-Windows platforms this is called directly from main() diff --git a/src/main_win32.c b/src/main_win32.c index 543d8ba81..edac8a8dc 100644 --- a/src/main_win32.c +++ b/src/main_win32.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mapper.c b/src/mapper.c index 108de9531..091db50b0 100644 --- a/src/mapper.c +++ b/src/mapper.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mapper.h b/src/mapper.h index 9f84f96fe..8249a229f 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer/alsa_mixer_plugin.c b/src/mixer/alsa_mixer_plugin.c index 38f36cb8f..756ae3ee5 100644 --- a/src/mixer/alsa_mixer_plugin.c +++ b/src/mixer/alsa_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer/oss_mixer_plugin.c b/src/mixer/oss_mixer_plugin.c index 418068ac2..608f1f9b8 100644 --- a/src/mixer/oss_mixer_plugin.c +++ b/src/mixer/oss_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c index 2be0b8266..8fbba4c5a 100644 --- a/src/mixer/pulse_mixer_plugin.c +++ b/src/mixer/pulse_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/pulse_mixer_plugin.h index be199f688..461633d37 100644 --- a/src/mixer/pulse_mixer_plugin.h +++ b/src/mixer/pulse_mixer_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer/raop_mixer_plugin.c b/src/mixer/raop_mixer_plugin.c new file mode 100644 index 000000000..b05671212 --- /dev/null +++ b/src/mixer/raop_mixer_plugin.c @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../output/raop_output_plugin.h" +#include "output_plugin.h" +#include "mixer_api.h" + +struct raop_mixer_plugin { + struct mixer base; + struct raop_data *rd; +}; + +static struct mixer * +raop_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct raop_mixer_plugin *rm = g_new(struct raop_mixer_plugin, 1); + rm->rd = (struct raop_data *) ao; + mixer_init(&rm->base, &raop_mixer_plugin); + + return &rm->base; +} + +static void +raop_mixer_finish(struct mixer *data) +{ + struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *) data; + + g_free(rm); +} + +static int +raop_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +{ + struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *)mixer; + return raop_get_volume(rm->rd); +} + +static bool +raop_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) +{ + struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *)mixer; + return raop_set_volume(rm->rd, volume, error_r); +} + +const struct mixer_plugin raop_mixer_plugin = { + .init = raop_mixer_init, + .finish = raop_mixer_finish, + .get_volume = raop_mixer_get_volume, + .set_volume = raop_mixer_set_volume, +}; diff --git a/src/mixer/roar_mixer_plugin.c b/src/mixer/roar_mixer_plugin.c new file mode 100644 index 000000000..636a9c00e --- /dev/null +++ b/src/mixer/roar_mixer_plugin.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#include "config.h" +#include "mixer_api.h" +#include "output_api.h" +#include "output/roar_output_plugin.h" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <unistd.h> + +typedef struct roar_mpd_mixer +{ + /** the base mixer class */ + struct mixer base; + roar_t *self; +} roar_mixer_t; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +roar_mixer_quark(void) +{ + return g_quark_from_static_string("roar_mixer"); +} + +static struct mixer * +roar_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + roar_mixer_t *self = g_new(roar_mixer_t, 1); + self->self = ao; + + mixer_init(&self->base, &roar_mixer_plugin); + + return &self->base; +} + +static void +roar_mixer_finish(struct mixer *data) +{ + roar_mixer_t *self = (roar_mixer_t *) data; + + g_free(self); +} + +static void +roar_mixer_close(G_GNUC_UNUSED struct mixer *data) +{ +} + +static bool +roar_mixer_open(G_GNUC_UNUSED struct mixer *data, + G_GNUC_UNUSED GError **error_r) +{ + return true; +} + +static int +roar_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +{ + roar_mixer_t *self = (roar_mixer_t *)mixer; + g_mutex_lock(self->self->lock); + if (self->self->vss && self->self->alive) + { + float l, r; + int error; + roar_vs_volume_get(self->self->vss, &l, &r, &error); + g_mutex_unlock(self->self->lock); + return (l + r) * 50; + } + else + { + g_mutex_unlock(self->self->lock); + return 0; + } +} + +static bool +roar_mixer_set_volume(struct mixer *mixer, unsigned volume, + G_GNUC_UNUSED GError **error_r) +{ + roar_mixer_t *self = (roar_mixer_t *)mixer; + g_mutex_lock(self->self->lock); + if (self->self->vss && self->self->alive) + { + assert(volume <= 100); + + int error; + float level = volume / 100.0; + + roar_vs_volume_mono(self->self->vss, level, &error); + g_mutex_unlock(self->self->lock); + return true; + } + else + { + g_mutex_unlock(self->self->lock); + return false; + } +} + +const struct mixer_plugin roar_mixer_plugin = { + .init = roar_mixer_init, + .finish = roar_mixer_finish, + .open = roar_mixer_open, + .close = roar_mixer_close, + .get_volume = roar_mixer_get_volume, + .set_volume = roar_mixer_set_volume, + .global = false, +}; diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c index 93802e977..0206c3b99 100644 --- a/src/mixer/software_mixer_plugin.c +++ b/src/mixer/software_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer/software_mixer_plugin.h b/src/mixer/software_mixer_plugin.h index 3bd07ac62..ee2b2023c 100644 --- a/src/mixer/software_mixer_plugin.h +++ b/src/mixer/software_mixer_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer/winmm_mixer_plugin.c b/src/mixer/winmm_mixer_plugin.c index 5ab3e7525..ceddf6afd 100644 --- a/src/mixer/winmm_mixer_plugin.c +++ b/src/mixer/winmm_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer_all.c b/src/mixer_all.c index ffe610b91..95ba90793 100644 --- a/src/mixer_all.c +++ b/src/mixer_all.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer_all.h b/src/mixer_all.h index cece23292..fe873e713 100644 --- a/src/mixer_all.h +++ b/src/mixer_all.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer_api.c b/src/mixer_api.c index 4c8959fb8..c85916c94 100644 --- a/src/mixer_api.c +++ b/src/mixer_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer_api.h b/src/mixer_api.h index 26c001703..29c1e00ca 100644 --- a/src/mixer_api.h +++ b/src/mixer_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer_control.c b/src/mixer_control.c index 458b3abc1..3e984dd04 100644 --- a/src/mixer_control.c +++ b/src/mixer_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer_control.h b/src/mixer_control.h index 1f48e8ca5..6c3468aca 100644 --- a/src/mixer_control.h +++ b/src/mixer_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer_list.h b/src/mixer_list.h index a472c8807..95ded5c23 100644 --- a/src/mixer_list.h +++ b/src/mixer_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,9 @@ extern const struct mixer_plugin software_mixer_plugin; extern const struct mixer_plugin alsa_mixer_plugin; extern const struct mixer_plugin oss_mixer_plugin; +extern const struct mixer_plugin roar_mixer_plugin; extern const struct mixer_plugin pulse_mixer_plugin; +extern const struct mixer_plugin raop_mixer_plugin; extern const struct mixer_plugin winmm_mixer_plugin; #endif diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h index 0915a03f3..9532b95cb 100644 --- a/src/mixer_plugin.h +++ b/src/mixer_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -41,7 +41,7 @@ struct mixer_plugin { * @param ao the pointer returned by audio_output_plugin.init * @param param the configuration section, or NULL if there is * no configuration - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, or * NULL to ignore errors * @return a mixer object, or NULL on error */ @@ -56,7 +56,7 @@ struct mixer_plugin { /** * Open mixer device * - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, or * NULL to ignore errors * @return true on success, false on error */ @@ -70,7 +70,7 @@ struct mixer_plugin { /** * Reads the current volume. * - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, or * NULL to ignore errors * @return the current volume (0..100 including) or -1 if * unavailable or on error (error_r set, mixer will be closed) @@ -80,7 +80,7 @@ struct mixer_plugin { /** * Sets the volume. * - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, or * NULL to ignore errors * @param volume the new volume (0..100 including) * @return true on success, false on error diff --git a/src/mixer_type.c b/src/mixer_type.c index 4f347dd94..a479caf16 100644 --- a/src/mixer_type.c +++ b/src/mixer_type.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer_type.h b/src/mixer_type.h index fd1c5576c..15d136b5b 100644 --- a/src/mixer_type.h +++ b/src/mixer_type.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mpd_error.h b/src/mpd_error.h index 47618d03c..219738ced 100644 --- a/src/mpd_error.h +++ b/src/mpd_error.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/notify.c b/src/notify.c index d148a4bfc..3c0112c91 100644 --- a/src/notify.c +++ b/src/notify.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/notify.h b/src/notify.h index 0c657f2fb..40821690c 100644 --- a/src/notify.h +++ b/src/notify.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/ntp_server.c b/src/ntp_server.c new file mode 100644 index 000000000..c33c0a933 --- /dev/null +++ b/src/ntp_server.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ntp_server.h" +#include "udp_server.h" + +#include <glib.h> +#include <assert.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <sys/time.h> + +#ifdef WIN32 +#define WINVER 0x0501 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#endif + +/* + * Calculate the current NTP time, store it in the buffer. + */ +static void +fill_int(unsigned char *buffer, uint32_t value) +{ + uint32_t be = GINT32_TO_BE(value); + memcpy(buffer, &be, sizeof(be)); +} + +/* + * Store time in the NTP format in the buffer + */ +static void +fill_time_buffer_with_time(unsigned char *buffer, struct timeval *tout) +{ + unsigned long secs_to_baseline = 964697997; + double fraction; + unsigned long long_fraction; + unsigned long secs; + + fraction = ((double) tout->tv_usec) / 1000000.0; + long_fraction = (unsigned long) (fraction * 256.0 * 256.0 * 256.0 * 256.0); + secs = secs_to_baseline + tout->tv_sec; + fill_int(buffer, secs); + fill_int(buffer + 4, long_fraction); +} + +/* + * Calculate the current NTP time, store it in the buffer. + */ +static void +fill_time_buffer(unsigned char *buffer) +{ + struct timeval current_time; + + gettimeofday(¤t_time,NULL); + fill_time_buffer_with_time(buffer, ¤t_time); +} + +static void +ntp_server_datagram(int fd, const void *data, size_t num_bytes, + const struct sockaddr *source_address, + size_t source_address_length, G_GNUC_UNUSED void *ctx) +{ + unsigned char buf[32]; + int iter; + + if (num_bytes > sizeof(buf)) + num_bytes = sizeof(buf); + memcpy(buf, data, num_bytes); + + fill_time_buffer(buf + 16); + // set to response + buf[1] = 0xd3; + // copy request + for (iter = 0; iter < 8; iter++) { + buf[8 + iter] = buf[24 + iter]; + } + fill_time_buffer(buf + 24); + + sendto(fd, (void *)buf, num_bytes, 0, + source_address, source_address_length); +} + +static const struct udp_server_handler ntp_server_handler = { + .datagram = ntp_server_datagram, +}; + +void +ntp_server_init(struct ntp_server *ntp) +{ + ntp->port = 6002; + ntp->udp = NULL; +} + +bool +ntp_server_open(struct ntp_server *ntp, GError **error_r) +{ + assert(ntp->udp == NULL); + + ntp->udp = udp_server_new(ntp->port, &ntp_server_handler, ntp, + error_r); + return ntp->udp != NULL; +} + +void +ntp_server_close(struct ntp_server *ntp) +{ + if (ntp->udp != NULL) { + udp_server_free(ntp->udp); + ntp->udp = NULL; + } +} diff --git a/src/ntp_server.h b/src/ntp_server.h new file mode 100644 index 000000000..fe6f8083b --- /dev/null +++ b/src/ntp_server.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NTP_SERVER_H +#define MPD_NTP_SERVER_H + +#include <glib.h> + +#include <stdbool.h> + +struct timeval; + +struct ntp_server { + unsigned short port; + + struct udp_server *udp; +}; + +void +ntp_server_init(struct ntp_server *ntp); + +bool +ntp_server_open(struct ntp_server *ntp, GError **error_r); + +void +ntp_server_close(struct ntp_server *ntp); + +#endif diff --git a/src/open.h b/src/open.h index e39c64a97..1fe245dfa 100644 --- a/src/open.h +++ b/src/open.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c index 422264f53..0bbe231fd 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/ao_plugin.c b/src/output/ao_plugin.c index 42ece5a3a..33366d3b8 100644 --- a/src/output/ao_plugin.c +++ b/src/output/ao_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c index 723698ed0..1a4ec3919 100644 --- a/src/output/ffado_output_plugin.c +++ b/src/output/ffado_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c index f4217ec4d..8f2234dd7 100644 --- a/src/output/fifo_output_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -42,7 +42,7 @@ struct fifo_data { int input; int output; bool created; - Timer *timer; + struct timer *timer; }; /** diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c index 1119a7834..e2c49c6c8 100644 --- a/src/output/httpd_client.c +++ b/src/output/httpd_client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h index 7ebd0bbc0..739163f42 100644 --- a/src/output/httpd_client.h +++ b/src/output/httpd_client.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h index 277e70f11..3e6e9768d 100644 --- a/src/output/httpd_internal.h +++ b/src/output/httpd_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -65,10 +65,10 @@ struct httpd_output { GMutex *mutex; /** - * A #Timer object to synchronize this output with the + * A #timer object to synchronize this output with the * wallclock. */ - Timer *timer; + struct timer *timer; /** * The listener socket. diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c index 40ad05c3d..3f570c7b9 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/httpd_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c index 2767d4eb8..4df84fd23 100644 --- a/src/output/jack_output_plugin.c +++ b/src/output/jack_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/mvp_plugin.c b/src/output/mvp_plugin.c index 6cc8fa34e..be4c8dbc0 100644 --- a/src/output/mvp_plugin.c +++ b/src/output/mvp_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/null_plugin.c b/src/output/null_plugin.c index 89abbd91f..f572959a0 100644 --- a/src/output/null_plugin.c +++ b/src/output/null_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ struct null_data { bool sync; - Timer *timer; + struct timer *timer; }; static void * @@ -82,7 +82,7 @@ null_play(void *data, G_GNUC_UNUSED const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { struct null_data *nd = data; - Timer *timer = nd->timer; + struct timer *timer = nd->timer; if (!nd->sync) return size; diff --git a/src/output/openal_plugin.c b/src/output/openal_plugin.c index 767b3eb17..23641c6c5 100644 --- a/src/output/openal_plugin.c +++ b/src/output/openal_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -41,7 +41,7 @@ struct openal_data { const char *device_name; ALCdevice *device; ALCcontext *context; - Timer *timer; + struct timer *timer; ALuint buffers[NUM_BUFFERS]; int filled; ALuint source; diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c index 9261b423c..d7df594d3 100644 --- a/src/output/oss_plugin.c +++ b/src/output/oss_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/osx_plugin.c b/src/output/osx_plugin.c index 7639f3bd9..8091660ab 100644 --- a/src/output/osx_plugin.c +++ b/src/output/osx_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,6 +28,11 @@ #define G_LOG_DOMAIN "osx" struct osx_output { + /* configuration settings */ + OSType component_subtype; + /* only applicable with kAudioUnitSubType_HALOutput */ + const char *device_name; + AudioUnit au; GMutex *mutex; GCond *condition; @@ -54,6 +59,26 @@ osx_output_test_default_device(void) return true; } +static void +osx_output_configure(struct osx_output *oo, const struct config_param *param) +{ + const char *device = config_get_block_string(param, "device", NULL); + + if (device == NULL || 0 == strcmp(device, "default")) { + oo->component_subtype = kAudioUnitSubType_DefaultOutput; + oo->device_name = NULL; + } + else if (0 == strcmp(device, "system")) { + oo->component_subtype = kAudioUnitSubType_SystemOutput; + oo->device_name = NULL; + } + else { + oo->component_subtype = kAudioUnitSubType_HALOutput; + /* XXX am I supposed to g_strdup() this? */ + oo->device_name = device; + } +} + static void * osx_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, G_GNUC_UNUSED const struct config_param *param, @@ -61,6 +86,7 @@ osx_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, { struct osx_output *oo = g_new(struct osx_output, 1); + osx_output_configure(oo, param); oo->mutex = g_mutex_new(); oo->condition = g_cond_new(); @@ -150,6 +176,95 @@ osx_render(void *vdata, } static bool +osx_output_set_device(struct osx_output *oo, GError **error) +{ + bool ret = true; + OSStatus status; + UInt32 size, numdevices; + AudioDeviceID *deviceids = NULL; + char name[256]; + unsigned int i; + + if (oo->component_subtype != kAudioUnitSubType_HALOutput) + goto done; + + /* how many audio devices are there? */ + status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, + &size, + NULL); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine number of OS X audio devices: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* what are the available audio device IDs? */ + numdevices = size / sizeof(AudioDeviceID); + deviceids = g_malloc(size); + status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, + &size, + deviceids); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine OS X audio device IDs: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* which audio device matches oo->device_name? */ + for (i = 0; i < numdevices; i++) { + size = sizeof(name); + status = AudioDeviceGetProperty(deviceids[i], 0, false, + kAudioDevicePropertyDeviceName, + &size, name); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine OS X device name " + "(device %u): %s", + (unsigned int) deviceids[i], + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + if (strcmp(oo->device_name, name) == 0) { + g_debug("found matching device: ID=%u, name=%s", + (unsigned int) deviceids[i], name); + break; + } + } + if (i == numdevices) { + g_warning("Found no audio device with name '%s' " + "(will use default audio device)", + oo->device_name); + goto done; + } + + status = AudioUnitSetProperty(oo->au, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &(deviceids[i]), + sizeof(AudioDeviceID)); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to set OS X audio output device: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + g_debug("set OS X audio output device ID=%u, name=%s", + (unsigned int) deviceids[i], name); + +done: + if (deviceids != NULL) + g_free(deviceids); + return ret; +} + +static bool osx_output_open(void *data, struct audio_format *audio_format, GError **error) { struct osx_output *od = data; @@ -161,7 +276,7 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) ComponentResult result; desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentSubType = od->component_subtype; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; @@ -175,7 +290,7 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) status = OpenAComponent(comp, &od->au); if (status != noErr) { - g_set_error(error, osx_output_quark(), 0, + g_set_error(error, osx_output_quark(), status, "Unable to open OS X component: %s", GetMacOSStatusCommentString(status)); return false; @@ -184,12 +299,15 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) status = AudioUnitInitialize(od->au); if (status != noErr) { CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, + g_set_error(error, osx_output_quark(), status, "Unable to initialize OS X audio unit: %s", GetMacOSStatusCommentString(status)); return false; } + if (!osx_output_set_device(od, error)) + return false; + callback.inputProc = osx_render; callback.inputProcRefCon = od; @@ -200,7 +318,7 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) if (result != noErr) { AudioUnitUninitialize(od->au); CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, + g_set_error(error, osx_output_quark(), result, "unable to set callback for OS X audio unit"); return false; } @@ -241,7 +359,7 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) if (result != noErr) { AudioUnitUninitialize(od->au); CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, + g_set_error(error, osx_output_quark(), result, "Unable to set format on OS X device"); return false; } @@ -256,7 +374,7 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) status = AudioOutputUnitStart(od->au); if (status != 0) { - g_set_error(error, osx_output_quark(), 0, + g_set_error(error, osx_output_quark(), status, "unable to start audio output: %s", GetMacOSStatusCommentString(status)); return false; diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c index 1d1aec7b1..111c654b9 100644 --- a/src/output/pipe_output_plugin.c +++ b/src/output/pipe_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c index 34d736546..9825785ee 100644 --- a/src/output/pulse_output_plugin.c +++ b/src/output/pulse_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h index 06e3aec43..2261175d5 100644 --- a/src/output/pulse_output_plugin.h +++ b/src/output/pulse_output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/raop_output_plugin.c b/src/output/raop_output_plugin.c new file mode 100644 index 000000000..735aea976 --- /dev/null +++ b/src/output/raop_output_plugin.c @@ -0,0 +1,950 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "raop_output_plugin.h" +#include "output_api.h" +#include "mixer_list.h" +#include "raop_output_plugin.h" +#include "rtsp_client.h" +#include "glib_compat.h" + +#include <glib.h> +#include <unistd.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/rsa.h> +#include <openssl/engine.h> + +#ifndef WIN32 +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netdb.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "raop" + +static struct raop_session_data *raop_session = NULL; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +raop_output_quark(void) +{ + return g_quark_from_static_string("raop_output"); +} + +static void +raop_session_free(struct raop_session_data *session) +{ + assert(session != NULL); + assert(session->raop_list == NULL); + + ntp_server_close(&session->ntp); + + if (session->data_mutex != NULL) + g_mutex_free(session->data_mutex); + + if (session->list_mutex != NULL) + g_mutex_free(session->list_mutex); + + if (raop_session->data_fd >= 0) + close(raop_session->data_fd); + + if (raop_session->ctrl.fd >= 0) + close(raop_session->ctrl.fd); + + g_free(session); +} + +static struct raop_session_data * +raop_session_new(GError **error_r) +{ + struct raop_session_data *session = g_new(struct raop_session_data, 1); + session->raop_list = NULL; + + session->data_mutex = g_mutex_new(); + session->list_mutex = g_mutex_new(); + + ntp_server_init(&session->ntp); + session->ctrl.port = 6001; + session->ctrl.fd = -1; + session->play_state.playing = false; + session->play_state.seq_num = (short) g_random_int(); + session->play_state.rtptime = g_random_int(); + session->play_state.sync_src = g_random_int(); + session->play_state.last_send.tv_sec = 0; + session->play_state.last_send.tv_usec = 0; + session->data_fd = -1; + + if (!RAND_bytes(session->encrypt.iv, sizeof(session->encrypt.iv)) || + !RAND_bytes(session->encrypt.key, sizeof(session->encrypt.key))) { + raop_session_free(session); + g_set_error(error_r, raop_output_quark(), 0, + "RAND_bytes error code=%ld", ERR_get_error()); + return NULL; + } + memcpy(session->encrypt.nv, session->encrypt.iv, sizeof(session->encrypt.nv)); + for (unsigned i = 0; i < 16; i++) { + printf("0x%x ", session->encrypt.key[i]); + } + printf("\n"); + AES_set_encrypt_key(session->encrypt.key, 128, &session->encrypt.ctx); + + memset(session->buffer, 0, RAOP_BUFFER_SIZE); + session->bufferSize = 0; + + return session; +} + +static struct raop_data * +new_raop_data(GError **error_r) +{ + struct raop_data *ret = g_new(struct raop_data, 1); + + ret->control_mutex = g_mutex_new(); + + ret->next = NULL; + ret->is_master = 0; + ret->started = 0; + ret->paused = 0; + + if (raop_session == NULL && + (raop_session = raop_session_new(error_r)) == NULL) { + g_mutex_free(ret->control_mutex); + g_free(ret); + return NULL; + } + + return ret; +} + +/* + * remove one character from a string + * return the number of deleted characters + */ +static int +remove_char_from_string(char *str, char c) +{ + char *src, *dst; + + /* skip all characters that don't need to be copied */ + src = strchr(str, c); + if (!src) + return 0; + + for (dst = src; *src; src++) + if (*src != c) + *(dst++) = *src; + + *dst = '\0'; + + return src - dst; +} + +/* bind an opened socket to specified hostname and port. + * if hostname=NULL, use INADDR_ANY. + * if *port=0, use dynamically assigned port + */ +static int bind_host(int sd, char *hostname, unsigned long ulAddr, + unsigned short *port, GError **error_r) +{ + struct sockaddr_in my_addr; + socklen_t nlen = sizeof(struct sockaddr); + struct hostent *h; + + memset(&my_addr, 0, sizeof(my_addr)); + /* use specified hostname */ + if (hostname) { + /* get server IP address (no check if input is IP address or DNS name) */ + h = gethostbyname(hostname); + if (h == NULL) { + if (strstr(hostname, "255.255.255.255") == hostname) { + my_addr.sin_addr.s_addr=-1; + } else { + if ((my_addr.sin_addr.s_addr = inet_addr(hostname)) == 0xFFFFFFFF) { + g_set_error(error_r, raop_output_quark(), 0, + "failed to resolve host '%s'", + hostname); + return -1; + } + } + my_addr.sin_family = AF_INET; + } else { + my_addr.sin_family = h->h_addrtype; + memcpy((char *) &my_addr.sin_addr.s_addr, + h->h_addr_list[0], h->h_length); + } + } else { + // if hostname=NULL, use INADDR_ANY + if (ulAddr) + my_addr.sin_addr.s_addr = ulAddr; + else + my_addr.sin_addr.s_addr = htonl(INADDR_ANY); + my_addr.sin_family = AF_INET; + } + + /* bind a specified port */ + my_addr.sin_port = htons(*port); + + if (bind(sd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to bind socket: %s", + g_strerror(errno)); + return -1; + } + + if (*port == 0) { + getsockname(sd, (struct sockaddr *) &my_addr, &nlen); + *port = ntohs(my_addr.sin_port); + } + + return 0; +} + +/* + * open udp port + */ +static int +open_udp_socket(char *hostname, unsigned short *port, + GError **error_r) +{ + int sd; + int size = 30000; + + /* socket creation */ + sd = socket(PF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to create UDP socket: %s", + g_strerror(errno)); + return -1; + } + if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (void *) &size, sizeof(size)) < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to set UDP buffer size: %s", + g_strerror(errno)); + return -1; + } + if (bind_host(sd, hostname, 0, port, error_r)) { + close(sd); + return -1; + } + + return sd; +} + +static bool +get_sockaddr_by_host(const char *host, short destport, + struct sockaddr_in *addr, + GError **error_r) +{ + struct hostent *h; + + h = gethostbyname(host); + if (h) { + addr->sin_family = h->h_addrtype; + memcpy((char *) &addr->sin_addr.s_addr, h->h_addr_list[0], h->h_length); + } else { + addr->sin_family = AF_INET; + if ((addr->sin_addr.s_addr=inet_addr(host))==0xFFFFFFFF) { + g_set_error(error_r, rtsp_client_quark(), 0, + "failed to resolve host '%s'", host); + return false; + } + } + addr->sin_port = htons(destport); + return true; +} + +/* + * Calculate the current NTP time, store it in the buffer. + */ +static void +fill_int(unsigned char *buffer, uint32_t value) +{ + uint32_t be = GINT32_TO_BE(value); + memcpy(buffer, &be, sizeof(be)); +} + +/* + * Store time in the NTP format in the buffer + */ +static void +fill_time_buffer_with_time(unsigned char *buffer, struct timeval *tout) +{ + unsigned long secs_to_baseline = 964697997; + double fraction; + unsigned long long_fraction; + unsigned long secs; + + fraction = ((double) tout->tv_usec) / 1000000.0; + long_fraction = (unsigned long) (fraction * 256.0 * 256.0 * 256.0 * 256.0); + secs = secs_to_baseline + tout->tv_sec; + fill_int(buffer, secs); + fill_int(buffer + 4, long_fraction); +} + +static void +get_time_for_rtp(struct play_state *state, struct timeval *tout) +{ + unsigned long rtp_diff = state->rtptime - state->start_rtptime; + unsigned long add_secs = rtp_diff / 44100; + unsigned long add_usecs = (((rtp_diff % 44100) * 10000) / 441) % 1000000; + tout->tv_sec = state->start_time.tv_sec + add_secs; + tout->tv_usec = state->start_time.tv_usec + add_usecs; + if (tout->tv_usec >= 1000000) { + tout->tv_sec++; + tout->tv_usec = tout->tv_usec % 1000000; + } +} + +/* + * Send a control command + */ +static bool +send_control_command(struct control_data *ctrl, struct raop_data *rd, + struct play_state *state, + GError **error_r) +{ + unsigned char buf[20]; + int diff; + int num_bytes; + struct timeval ctrl_time; + + diff = 88200; + if (rd->started) { + buf[0] = 0x80; + diff += NUMSAMPLES; + } else { + buf[0] = 0x90; + state->playing = true; + state->start_rtptime = state->rtptime; + } + buf[1] = 0xd4; + buf[2] = 0x00; + buf[3] = 0x07; + fill_int(buf + 4, state->rtptime - diff); + get_time_for_rtp(state, &ctrl_time); + fill_time_buffer_with_time(buf + 8, &ctrl_time); + fill_int(buf + 16, state->rtptime); + + num_bytes = sendto(ctrl->fd, (const void *)buf, sizeof(buf), 0, + (struct sockaddr *)&rd->ctrl_addr, + sizeof(rd->ctrl_addr)); + if (num_bytes < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "Unable to send control command: %s", + g_strerror(errno)); + return false; + } + + return true; +} + +static int rsa_encrypt(const unsigned char *text, int len, unsigned char *res) +{ + RSA *rsa; + gsize usize; + unsigned char *modulus; + unsigned char *exponent; + int size; + + char n[] = + "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" + "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" + "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" + "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" + "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" + "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; + char e[] = "AQAB"; + + rsa = RSA_new(); + + modulus = g_base64_decode(n, &usize); + rsa->n = BN_bin2bn(modulus, usize, NULL); + exponent = g_base64_decode(e, &usize); + rsa->e = BN_bin2bn(exponent, usize, NULL); + g_free(modulus); + g_free(exponent); + size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); + + RSA_free(rsa); + return size; +} + +static int +raop_encrypt(struct encrypt_data *encryp, unsigned char *data, int size) +{ + // any bytes that fall beyond the last 16 byte page should be sent + // in the clear + int alt_size = size - (size % 16); + + memcpy(encryp->nv, encryp->iv, 16); + + AES_cbc_encrypt(data, data, alt_size, &encryp->ctx, encryp->nv, 1); + + return size; +} + +/* write bits filed data, *bpos=0 for msb, *bpos=7 for lsb + d=data, blen=length of bits field +*/ +static inline void +bits_write(unsigned char **p, unsigned char d, int blen, int *bpos) +{ + int lb, rb, bd; + lb =7 - *bpos; + rb = lb - blen + 1; + if (rb >= 0) { + bd = d << rb; + if (*bpos) + **p |= bd; + else + **p = bd; + *bpos += blen; + } else { + bd = d >> -rb; + **p |= bd; + *p += 1; + **p = d << (8 + rb); + *bpos = -rb; + } +} + +static bool +wrap_pcm(unsigned char *buffer, int bsize, int *size, unsigned char *inData, int inSize) +{ + unsigned char one[4]; + int count = 0; + int bpos = 0; + unsigned char *bp = buffer; + int i, nodata = 0; + bits_write(&bp, 1, 3, &bpos); // channel=1, stereo + bits_write(&bp, 0, 4, &bpos); // unknown + bits_write(&bp, 0, 8, &bpos); // unknown + bits_write(&bp, 0, 4, &bpos); // unknown + if (bsize != 4096 && false) + bits_write(&bp, 1, 1, &bpos); // hassize + else + bits_write(&bp, 0, 1, &bpos); // hassize + bits_write(&bp, 0, 2, &bpos); // unused + bits_write(&bp, 1, 1, &bpos); // is-not-compressed + if (bsize != 4096 && false) { + // size of data, integer, big endian + bits_write(&bp, (bsize >> 24) & 0xff, 8, &bpos); + bits_write(&bp, (bsize >> 16) & 0xff, 8, &bpos); + bits_write(&bp, (bsize >> 8) & 0xff, 8, &bpos); + bits_write(&bp, bsize&0xff, 8, &bpos); + } + while (1) { + if (inSize <= count * 4) nodata = 1; + if (nodata) break; + one[0] = inData[count * 4]; + one[1] = inData[count * 4 + 1]; + one[2] = inData[count * 4 + 2]; + one[3] = inData[count * 4 + 3]; + +#if BYTE_ORDER == BIG_ENDIAN + bits_write(&bp, one[0], 8, &bpos); + bits_write(&bp, one[1], 8, &bpos); + bits_write(&bp, one[2], 8, &bpos); + bits_write(&bp, one[3], 8, &bpos); +#else + bits_write(&bp, one[1], 8, &bpos); + bits_write(&bp, one[0], 8, &bpos); + bits_write(&bp, one[3], 8, &bpos); + bits_write(&bp, one[2], 8, &bpos); +#endif + + if (++count == bsize) break; + } + if (!count) return false; // when no data at all, it should stop playing + /* when readable size is less than bsize, fill 0 at the bottom */ + for(i = 0; i < (bsize - count) * 4; i++) { + bits_write(&bp, 0, 8, &bpos); + } + *size = (int)(bp - buffer); + if (bpos) *size += 1; + return true; +} + +static bool +raopcl_connect(struct raop_data *rd, GError **error_r) +{ + unsigned char buf[4 + 8 + 16]; + char sid[16]; + char sci[24]; + char act_r[17]; + char *sac=NULL, *key = NULL, *iv = NULL; + char sdp[1024]; + int rval = false; + unsigned char rsakey[512]; + struct timeval current_time; + unsigned int sessionNum; + int i; + + + gettimeofday(¤t_time,NULL); + sessionNum = current_time.tv_sec + 2082844804; + + RAND_bytes(buf, sizeof(buf)); + sprintf(act_r, "%u", (unsigned int) g_random_int()); + sprintf(sid, "%u", sessionNum); + sprintf(sci, "%08x%08x", *((int *)(buf + 4)), *((int *)(buf + 8))); + sac = g_base64_encode(buf + 12, 16); + rd->rtspcl = rtspcl_open(); + rtspcl_set_useragent(rd->rtspcl, "iTunes/8.1.1 (Macintosh; U; PPC Mac OS X 10.4)"); + rtspcl_add_exthds(rd->rtspcl, "Client-Instance", sci); + rtspcl_add_exthds(rd->rtspcl, "DACP-ID", sci); + rtspcl_add_exthds(rd->rtspcl, "Active-Remote", act_r); + if (!rtspcl_connect(rd->rtspcl, rd->addr, rd->rtsp_port, sid, error_r)) + goto erexit; + + i = rsa_encrypt(raop_session->encrypt.key, 16, rsakey); + key = g_base64_encode(rsakey, i); + remove_char_from_string(key, '='); + iv = g_base64_encode(raop_session->encrypt.iv, 16); + remove_char_from_string(iv, '='); + sprintf(sdp, + "v=0\r\n" + "o=iTunes %s 0 IN IP4 %s\r\n" + "s=iTunes\r\n" + "c=IN IP4 %s\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 96\r\n" + "a=rtpmap:96 AppleLossless\r\n" + "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n" + "a=rsaaeskey:%s\r\n" + "a=aesiv:%s\r\n", + sid, rtspcl_local_ip(rd->rtspcl), rd->addr, NUMSAMPLES, key, iv); + remove_char_from_string(sac, '='); + // rtspcl_add_exthds(rd->rtspcl, "Apple-Challenge", sac); + if (!rtspcl_announce_sdp(rd->rtspcl, sdp, error_r)) + goto erexit; + // if (!rtspcl_mark_del_exthds(rd->rtspcl, "Apple-Challenge")) goto erexit; + if (!rtspcl_setup(rd->rtspcl, NULL, + raop_session->ctrl.port, raop_session->ntp.port, + error_r)) + goto erexit; + + if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->control_port, + &rd->ctrl_addr, error_r)) + goto erexit; + + if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->server_port, + &rd->data_addr, error_r)) + goto erexit; + + if (!rtspcl_record(rd->rtspcl, + raop_session->play_state.seq_num, + raop_session->play_state.rtptime, + error_r)) + goto erexit; + + rval = true; + + erexit: + g_free(sac); + g_free(key); + g_free(iv); + return rval; +} + +static int +difference (struct timeval *t1, struct timeval *t2) +{ + int ret = 150000000; + if (t1->tv_sec - t2->tv_sec < 150) { + ret = (t1->tv_sec - t2->tv_sec) * 1000000; + ret += t1->tv_usec - t2->tv_usec; + } + return ret; +} + +/* + * With airtunes version 2, we don't get responses back when we send audio + * data. The only requests we get from the airtunes device are timing + * requests. + */ +static bool +send_audio_data(int fd, GError **error_r) +{ + int i = 0; + struct timeval current_time, rtp_time; + struct raop_data *rd = raop_session->raop_list; + + get_time_for_rtp(&raop_session->play_state, &rtp_time); + gettimeofday(¤t_time, NULL); + int diff = difference(&rtp_time, ¤t_time); + if (diff > 0) + g_usleep(diff); + + gettimeofday(&raop_session->play_state.last_send, NULL); + while (rd) { + if (rd->started) { + raop_session->data[1] = 0x60; + } else { + rd->started = true; + raop_session->data[1] = 0xe0; + } + i = sendto(fd, (const void *)(raop_session->data + raop_session->wblk_wsize), + raop_session->wblk_remsize, 0, (struct sockaddr *) &rd->data_addr, + sizeof(rd->data_addr)); + if (i < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "write error: %s", + g_strerror(errno)); + return false; + } + if (i == 0) { + g_set_error_literal(error_r, raop_output_quark(), 0, + "disconnected on the other end"); + return false; + } + rd = rd->next; + } + raop_session->wblk_wsize += i; + raop_session->wblk_remsize -= i; + + return true; +} + +static void * +raop_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, + G_GNUC_UNUSED const struct config_param *param, + GError **error_r) +{ + const char *host = config_get_block_string(param, "host", NULL); + if (host == NULL) { + g_set_error_literal(error_r, raop_output_quark(), 0, + "missing option 'host'"); + return NULL; + } + + struct raop_data *rd; + + rd = new_raop_data(error_r); + if (rd == NULL) + return NULL; + + rd->addr = host; + rd->rtsp_port = config_get_block_unsigned(param, "port", 5000); + rd->volume = config_get_block_unsigned(param, "volume", 75); + return rd; +} + +static bool +raop_set_volume_local(struct raop_data *rd, int volume, GError **error_r) +{ + char vol_str[128]; + sprintf(vol_str, "volume: %d.000000\r\n", volume); + return rtspcl_set_parameter(rd->rtspcl, vol_str, error_r); +} + + +static void +raop_output_finish(void *data) +{ + struct raop_data *rd = data; + + if (rd->rtspcl) + rtspcl_close(rd->rtspcl); + + g_mutex_free(rd->control_mutex); + g_free(rd); +} + +#define RAOP_VOLUME_MIN -30 +#define RAOP_VOLUME_MAX 0 + +int +raop_get_volume(struct raop_data *rd) +{ + return rd->volume; +} + +bool +raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r) +{ + int raop_volume; + bool rval; + + //set parameter volume + if (volume == 0) { + raop_volume = -144; + } else { + raop_volume = RAOP_VOLUME_MIN + + (RAOP_VOLUME_MAX - RAOP_VOLUME_MIN) * volume / 100; + } + g_mutex_lock(rd->control_mutex); + rval = raop_set_volume_local(rd, raop_volume, error_r); + if (rval) rd->volume = volume; + g_mutex_unlock(rd->control_mutex); + + return rval; +} + +static void +raop_output_cancel(void *data) +{ + //flush + struct key_data kd; + struct raop_data *rd = (struct raop_data *) data; + int flush_diff = 1; + + rd->started = 0; + if (rd->is_master) { + raop_session->play_state.playing = false; + } + if (rd->paused) { + return; + } + + g_mutex_lock(rd->control_mutex); + static char rtp_key[] = "RTP-Info"; + kd.key = rtp_key; + char buf[128]; + sprintf(buf, "seq=%d; rtptime=%d", raop_session->play_state.seq_num + flush_diff, raop_session->play_state.rtptime + NUMSAMPLES * flush_diff); + kd.data = buf; + kd.next = NULL; + exec_request(rd->rtspcl, "FLUSH", NULL, NULL, 1, + &kd, NULL, NULL); + g_mutex_unlock(rd->control_mutex); +} + +static bool +raop_output_pause(void *data) +{ + struct raop_data *rd = (struct raop_data *) data; + + rd->paused = true; + return true; +} + +/** + * Remove the output from the session's list. Caller must not lock + * the list_mutex. + */ +static void +raop_output_remove(struct raop_data *rd) +{ + struct raop_data *iter = raop_session->raop_list; + struct raop_data *prev = NULL; + + g_mutex_lock(raop_session->list_mutex); + while (iter) { + if (iter == rd) { + if (prev != NULL) { + prev->next = rd->next; + } else { + raop_session->raop_list = rd->next; + } + if (rd->is_master && raop_session->raop_list != NULL) { + raop_session->raop_list->is_master = true; + } + rd->next = NULL; + rd->is_master = false; + break; + } + prev = iter; + iter = iter->next; + } + g_mutex_unlock(raop_session->list_mutex); + + if (raop_session->raop_list == NULL) { + raop_session_free(raop_session); + raop_session = NULL; + } +} + +static void +raop_output_close(void *data) +{ + //teardown + struct raop_data *rd = data; + + raop_output_remove(rd); + + g_mutex_lock(rd->control_mutex); + exec_request(rd->rtspcl, "TEARDOWN", NULL, NULL, 0, + NULL, NULL, NULL); + g_mutex_unlock(rd->control_mutex); + + rd->started = 0; +} + + +static bool +raop_output_open(void *data, struct audio_format *audio_format, GError **error_r) +{ + //setup, etc. + struct raop_data *rd = data; + + g_mutex_lock(raop_session->list_mutex); + if (raop_session->raop_list == NULL) { + // first raop, need to initialize session data + unsigned short myport = 0; + raop_session->raop_list = rd; + rd->is_master = true; + + raop_session->data_fd = open_udp_socket(NULL, &myport, + error_r); + if (raop_session->data_fd < 0) + return false; + + if (!ntp_server_open(&raop_session->ntp, error_r)) + return false; + + raop_session->ctrl.fd = + open_udp_socket(NULL, &raop_session->ctrl.port, + error_r); + if (raop_session->ctrl.fd < 0) { + ntp_server_close(&raop_session->ntp); + raop_session->ctrl.fd = -1; + g_mutex_unlock(raop_session->list_mutex); + return false; + } + } + g_mutex_unlock(raop_session->list_mutex); + + audio_format->format = SAMPLE_FORMAT_S16; + if (!raopcl_connect(rd, error_r)) { + raop_output_remove(rd); + return false; + } + + if (!raop_set_volume(rd, rd->volume, error_r)) { + raop_output_remove(rd); + return false; + } + + g_mutex_lock(raop_session->list_mutex); + if (!rd->is_master) { + rd->next = raop_session->raop_list; + raop_session->raop_list = rd; + } + g_mutex_unlock(raop_session->list_mutex); + return true; +} + +static size_t +raop_output_play(void *data, const void *chunk, size_t size, + GError **error_r) +{ + //raopcl_send_sample + struct raop_data *rd = data; + size_t rval = 0, orig_size = size; + + rd->paused = false; + if (!rd->is_master) { + // only process data for the master raop + return size; + } + + g_mutex_lock(raop_session->data_mutex); + + if (raop_session->play_state.rtptime <= NUMSAMPLES) { + // looped over, need new reference point to calculate correct times + raop_session->play_state.playing = false; + } + + while (raop_session->bufferSize + size >= RAOP_BUFFER_SIZE) { + // ntp header + unsigned char header[] = { + 0x80, 0x60, 0x00, 0x00, + // rtptime + 0x00, 0x00, 0x00, 0x00, + // device + 0x7e, 0xad, 0xd2, 0xd3, + }; + + + int count = 0; + int copyBytes = RAOP_BUFFER_SIZE - raop_session->bufferSize; + + if (!raop_session->play_state.playing || + raop_session->play_state.seq_num % (44100 / NUMSAMPLES + 1) == 0) { + struct raop_data *iter; + g_mutex_lock(raop_session->list_mutex); + if (!raop_session->play_state.playing) { + gettimeofday(&raop_session->play_state.start_time,NULL); + } + iter = raop_session->raop_list; + while (iter) { + if (!send_control_command(&raop_session->ctrl, iter, + &raop_session->play_state, + error_r)) + goto erexit; + + iter = iter->next; + } + g_mutex_unlock(raop_session->list_mutex); + } + + fill_int(header + 8, raop_session->play_state.sync_src); + + memcpy(raop_session->buffer + raop_session->bufferSize, chunk, copyBytes); + raop_session->bufferSize += copyBytes; + chunk = ((const char *)chunk) + copyBytes; + size -= copyBytes; + + if (!wrap_pcm(raop_session->data + RAOP_HEADER_SIZE, NUMSAMPLES, &count, raop_session->buffer, RAOP_BUFFER_SIZE)) { + g_warning("unable to encode %d bytes properly\n", RAOP_BUFFER_SIZE); + } + + memcpy(raop_session->data, header, RAOP_HEADER_SIZE); + raop_session->data[2] = raop_session->play_state.seq_num >> 8; + raop_session->data[3] = raop_session->play_state.seq_num & 0xff; + raop_session->play_state.seq_num ++; + + fill_int(raop_session->data + 4, raop_session->play_state.rtptime); + raop_session->play_state.rtptime += NUMSAMPLES; + + raop_encrypt(&raop_session->encrypt, raop_session->data + RAOP_HEADER_SIZE, count); + raop_session->wblk_remsize = count + RAOP_HEADER_SIZE; + raop_session->wblk_wsize = 0; + + if (!send_audio_data(raop_session->data_fd, error_r)) + goto erexit; + + raop_session->bufferSize = 0; + } + if (size > 0) { + memcpy(raop_session->buffer + raop_session->bufferSize, chunk, size); + raop_session->bufferSize += size; + } + rval = orig_size; + erexit: + g_mutex_unlock(raop_session->data_mutex); + return rval; +} + +const struct audio_output_plugin raopPlugin = { + .name = "raop", + .init = raop_output_init, + .finish = raop_output_finish, + .open = raop_output_open, + .play = raop_output_play, + .cancel = raop_output_cancel, + .pause = raop_output_pause, + .close = raop_output_close, + .mixer_plugin = &raop_mixer_plugin, +}; diff --git a/src/output/raop_output_plugin.h b/src/output/raop_output_plugin.h new file mode 100644 index 000000000..210237179 --- /dev/null +++ b/src/output/raop_output_plugin.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_RAOP_PLUGIN_H +#define MPD_OUTPUT_RAOP_PLUGIN_H + +#include "ntp_server.h" +#include "rtsp_client.h" + +#include <glib.h> +#include <stdbool.h> +#include <sys/time.h> +#include <openssl/aes.h> + +struct play_state { + bool playing; + unsigned short seq_num; + unsigned int rtptime; + unsigned int sync_src; + unsigned int start_rtptime; + struct timeval start_time; + struct timeval last_send; +}; + +/*********************************************************************/ + +enum pause_state { + NO_PAUSE = 0, + OP_PAUSE, + NODATA_PAUSE, +}; + +#define MINIMUM_SAMPLE_SIZE 32 + +#define RAOP_FD_READ (1<<0) +#define RAOP_FD_WRITE (1<<1) + +/*********************************************************************/ + +struct encrypt_data { + AES_KEY ctx; + unsigned char iv[16]; // initialization vector for aes-cbc + unsigned char nv[16]; // next vector for aes-cbc + unsigned char key[16]; // key for aes-cbc +}; + +/*********************************************************************/ + +struct raop_data { + struct rtspcl_data *rtspcl; + const char *addr; // target host address + short rtsp_port; + struct sockaddr_in ctrl_addr; + struct sockaddr_in data_addr; + + bool is_master; + struct raop_data *next; + + unsigned volume; + + GMutex *control_mutex; + + bool started; + bool paused; +}; + +/*********************************************************************/ + +struct control_data { + unsigned short port; + int fd; +}; + +/*********************************************************************/ + +#define NUMSAMPLES 352 +#define RAOP_BUFFER_SIZE NUMSAMPLES * 4 +#define RAOP_HEADER_SIZE 12 +#define ALAC_MAX_HEADER_SIZE 8 +#define RAOP_MAX_PACKET_SIZE RAOP_BUFFER_SIZE + RAOP_HEADER_SIZE + ALAC_MAX_HEADER_SIZE + +// session +struct raop_session_data { + struct raop_data *raop_list; + struct ntp_server ntp; + struct control_data ctrl; + struct encrypt_data encrypt; + struct play_state play_state; + + int data_fd; + + unsigned char buffer[RAOP_BUFFER_SIZE]; + size_t bufferSize; + + unsigned char data[RAOP_MAX_PACKET_SIZE]; + int wblk_wsize; + int wblk_remsize; + + GMutex *data_mutex; + GMutex *list_mutex; +}; + +//static struct raop_session_data *raop_session; + +/*********************************************************************/ + +bool +raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r); + +int +raop_get_volume(struct raop_data *rd); + +#endif diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c index 10d64106c..070ef4b08 100644 --- a/src/output/recorder_output_plugin.c +++ b/src/output/recorder_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/roar_output_plugin.h b/src/output/roar_output_plugin.h new file mode 100644 index 000000000..08e272007 --- /dev/null +++ b/src/output/roar_output_plugin.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#ifndef __ROAR_OUTPUT_H +#define __ROAR_OUTPUT_H + +#include <roaraudio.h> +#include <glib.h> + +typedef struct roar +{ + roar_vs_t * vss; + int err; + char *host; + char *name; + int role; + struct roar_connection con; + struct roar_audio_info info; + GMutex *lock; + volatile bool alive; +} roar_t; + +#endif diff --git a/src/output/roar_plugin.c b/src/output/roar_plugin.c new file mode 100644 index 000000000..f9d44a3d8 --- /dev/null +++ b/src/output/roar_plugin.c @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "mixer_list.h" +#include "roar_output_plugin.h" + +#include <glib.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "roaraudio" + +static inline GQuark +roar_output_quark(void) +{ + return g_quark_from_static_string("roar_output"); +} + +static void +roar_configure(struct roar * self, const struct config_param *param) +{ + self->host = config_dup_block_string(param, "server", NULL); + self->name = config_dup_block_string(param, "name", "MPD"); + char *role = config_dup_block_string(param, "role", "music"); + if (role != NULL) + { + self->role = roar_str2role(role); + g_free(role); + } + else + self->role = ROAR_ROLE_MUSIC; +} + +static void * +roar_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + GMutex *lock = g_mutex_new(); + + roar_t * self = roar_mm_calloc(1, sizeof(*self)); + if (self == NULL) + { + g_set_error(error, roar_output_quark(), 0, "Failed to allocate memory"); + return NULL; + } + + self->lock = lock; + self->err = ROAR_ERROR_NONE; + roar_configure(self, param); + return self; +} + +static void +roar_close(void *data) +{ + roar_t * self = data; + g_mutex_lock(self->lock); + self->alive = false; + + if (self->vss != NULL) + roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err)); + self->vss = NULL; + roar_disconnect(&(self->con)); + g_mutex_unlock(self->lock); +} + +static void +roar_finish(void *data) +{ + roar_t * self = data; + + g_free(self->host); + g_free(self->name); + g_mutex_free(self->lock); + + roar_mm_free(data); +} + +static bool +roar_open(void *data, struct audio_format *audio_format, GError **error) +{ + roar_t * self = data; + g_mutex_lock(self->lock); + + if (roar_simple_connect(&(self->con), self->host, self->name) < 0) + { + g_set_error(error, roar_output_quark(), 0, + "Failed to connect to Roar server"); + g_mutex_unlock(self->lock); + return false; + } + + self->vss = roar_vs_new_from_con(&(self->con), &(self->err)); + + if (self->vss == NULL || self->err != ROAR_ERROR_NONE) + { + g_set_error(error, roar_output_quark(), 0, + "Failed to connect to server"); + g_mutex_unlock(self->lock); + return false; + } + + self->info.rate = audio_format->sample_rate; + self->info.channels = audio_format->channels; + self->info.codec = ROAR_CODEC_PCM_S; + + switch (audio_format->format) + { + case SAMPLE_FORMAT_S8: + self->info.bits = 8; + break; + case SAMPLE_FORMAT_S16: + self->info.bits = 16; + break; + case SAMPLE_FORMAT_S24: + self->info.bits = 24; + break; + case SAMPLE_FORMAT_S24_P32: + self->info.bits = 32; + audio_format->format = SAMPLE_FORMAT_S32; + break; + case SAMPLE_FORMAT_S32: + self->info.bits = 32; + break; + default: + self->info.bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; + } + audio_format->reverse_endian = 0; + + if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY, + &(self->err)) < 0) + { + g_set_error(error, roar_output_quark(), 0, "Failed to start stream"); + g_mutex_unlock(self->lock); + return false; + } + roar_vs_role(self->vss, self->role, &(self->err)); + self->alive = true; + + g_mutex_unlock(self->lock); + return true; +} + +static void +roar_cancel(void *data) +{ + roar_t * self = data; + + g_mutex_lock(self->lock); + if (self->vss != NULL) + { + roar_vs_t *vss = self->vss; + self->vss = NULL; + roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); + self->alive = false; + + vss = roar_vs_new_from_con(&(self->con), &(self->err)); + if (vss) + { + roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, &(self->err)); + roar_vs_role(vss, self->role, &(self->err)); + self->vss = vss; + self->alive = true; + } + } + g_mutex_unlock(self->lock); +} + +static size_t +roar_play(void *data, const void *chunk, size_t size, GError **error) +{ + struct roar * self = data; + ssize_t rc; + + if (self->vss == NULL) + { + g_set_error(error, roar_output_quark(), 0, "Connection is invalid"); + return 0; + } + + rc = roar_vs_write(self->vss, chunk, size, &(self->err)); + if ( rc <= 0 ) + { + g_set_error(error, roar_output_quark(), 0, "Failed to play data"); + return 0; + } + + return rc; +} + +static const char* +roar_tag_convert(enum tag_type type, bool *is_uuid) +{ + *is_uuid = false; + switch (type) + { + case TAG_ARTIST: + case TAG_ALBUM_ARTIST: + return "AUTHOR"; + case TAG_ALBUM: + return "ALBUM"; + case TAG_TITLE: + return "TITLE"; + case TAG_TRACK: + return "TRACK"; + case TAG_NAME: + return "NAME"; + case TAG_GENRE: + return "GENRE"; + case TAG_DATE: + return "DATE"; + case TAG_PERFORMER: + return "PERFORMER"; + case TAG_COMMENT: + return "COMMENT"; + case TAG_DISC: + return "DISCID"; + case TAG_COMPOSER: +#ifdef ROAR_META_TYPE_COMPOSER + return "COMPOSER"; +#else + return "AUTHOR"; +#endif + case TAG_MUSICBRAINZ_ARTISTID: + case TAG_MUSICBRAINZ_ALBUMID: + case TAG_MUSICBRAINZ_ALBUMARTISTID: + case TAG_MUSICBRAINZ_TRACKID: + *is_uuid = true; + return "HASH"; + + default: + return NULL; + } +} + +static void +roar_send_tag(void *data, const struct tag *meta) +{ + struct roar * self = data; + + if (self->vss == NULL) + return; + + g_mutex_lock(self->lock); + size_t cnt = 1; + struct roar_keyval vals[32]; + memset(vals, 0, sizeof(vals)); + char uuid_buf[32][64]; + + char timebuf[16]; + snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", + meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60); + + vals[0].key = g_strdup("LENGTH"); + vals[0].value = timebuf; + + for (unsigned i = 0; i < meta->num_items && cnt < 32; i++) + { + bool is_uuid = false; + const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid); + if (key != NULL) + { + if (is_uuid) + { + snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", + meta->items[i]->value); + vals[cnt].key = g_strdup(key); + vals[cnt].value = uuid_buf[cnt]; + } + else + { + vals[cnt].key = g_strdup(key); + vals[cnt].value = meta->items[i]->value; + } + cnt++; + } + } + + roar_vs_meta(self->vss, vals, cnt, &(self->err)); + + for (unsigned i = 0; i < 32; i++) + g_free(vals[i].key); + + g_mutex_unlock(self->lock); +} + +const struct audio_output_plugin roar_output_plugin = { + .name = "roar", + .init = roar_init, + .finish = roar_finish, + .open = roar_open, + .play = roar_play, + .cancel = roar_cancel, + .close = roar_close, + .send_tag = roar_send_tag, + + .mixer_plugin = &roar_mixer_plugin +}; diff --git a/src/output/shout_plugin.c b/src/output/shout_plugin.c index 35efd9fc7..7a4d70e70 100644 --- a/src/output/shout_plugin.c +++ b/src/output/shout_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -277,6 +277,13 @@ my_shout_init_driver(const struct audio_format *audio_format, goto failure; } + value = config_get_block_string(param, "url", NULL); + if (value != NULL && shout_set_url(sd->shout_conn, value)) { + g_set_error(error, shout_output_quark(), 0, + "%s", shout_get_error(sd->shout_conn)); + goto failure; + } + { char temp[11]; memset(temp, 0, sizeof(temp)); diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c index 22c583805..c0d38defb 100644 --- a/src/output/solaris_output_plugin.c +++ b/src/output/solaris_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c index b9687874d..e93a9f6c5 100644 --- a/src/output/winmm_output_plugin.c +++ b/src/output/winmm_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h index 39507275a..10f0bb6b1 100644 --- a/src/output/winmm_output_plugin.h +++ b/src/output/winmm_output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_all.c b/src/output_all.c index 4e0b2eb22..7f4694a8b 100644 --- a/src/output_all.c +++ b/src/output_all.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,6 +27,7 @@ #include "buffer.h" #include "player_control.h" #include "mpd_error.h" +#include "notify.h" #ifndef NDEBUG #include "chunk.h" @@ -100,7 +101,7 @@ audio_output_config_count(void) } void -audio_output_all_init(void) +audio_output_all_init(struct player_control *pc) { const struct config_param *param = NULL; unsigned int i; @@ -121,7 +122,7 @@ audio_output_all_init(void) /* only allow param to be NULL if there just one audioOutput */ assert(param || (num_audio_outputs == 1)); - if (!audio_output_init(output, param, &error)) { + if (!audio_output_init(output, param, pc, &error)) { if (param != NULL) MPD_ERROR("line %i: %s", param->line, error->message); @@ -460,17 +461,17 @@ audio_output_all_check(void) } bool -audio_output_all_wait(unsigned threshold) +audio_output_all_wait(struct player_control *pc, unsigned threshold) { - player_lock(); + player_lock(pc); if (audio_output_all_check() < threshold) { - player_unlock(); + player_unlock(pc); return true; } - player_wait(); - player_unlock(); + player_wait(pc); + player_unlock(pc); return audio_output_all_check() < threshold; } diff --git a/src/output_all.h b/src/output_all.h index a579bf5f1..4eeb94f13 100644 --- a/src/output_all.h +++ b/src/output_all.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,13 +32,14 @@ struct audio_format; struct music_buffer; struct music_chunk; +struct player_control; /** * Global initialization: load audio outputs from the configuration * file and initialize them. */ void -audio_output_all_init(void); +audio_output_all_init(struct player_control *pc); /** * Global finalization: free memory occupied by audio outputs. All @@ -127,7 +128,7 @@ audio_output_all_check(void); * @return true if there are less than #threshold chunks in the pipe */ bool -audio_output_all_wait(unsigned threshold); +audio_output_all_wait(struct player_control *pc, unsigned threshold); /** * Puts all audio outputs into pause mode. Most implementations will diff --git a/src/output_api.h b/src/output_api.h index 8e002dd48..302a0cdc9 100644 --- a/src/output_api.h +++ b/src/output_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_command.c b/src/output_command.c index 825884e8e..3988f350a 100644 --- a/src/output_command.c +++ b/src/output_command.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -50,7 +50,7 @@ audio_output_enable_index(unsigned idx) ao->enabled = true; idle_add(IDLE_OUTPUT); - pc_update_audio(); + pc_update_audio(ao->player_control); ++audio_output_state_version; @@ -79,7 +79,7 @@ audio_output_disable_index(unsigned idx) idle_add(IDLE_MIXER); } - pc_update_audio(); + pc_update_audio(ao->player_control); ++audio_output_state_version; diff --git a/src/output_command.h b/src/output_command.h index fab015c3f..eda30acc8 100644 --- a/src/output_command.h +++ b/src/output_command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_control.c b/src/output_control.c index f8c5cd873..7ddcb8b19 100644 --- a/src/output_control.c +++ b/src/output_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -38,6 +38,11 @@ enum { struct notify audio_output_client_notify; +/** + * Waits for command completion. + * + * @param ao the #audio_output instance; must be locked + */ static void ao_command_wait(struct audio_output *ao) { while (ao->command != AO_COMMAND_NONE) { @@ -47,20 +52,43 @@ static void ao_command_wait(struct audio_output *ao) } } -static void ao_command(struct audio_output *ao, enum audio_output_command cmd) +/** + * Sends a command to the #audio_output object, but does not wait for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void ao_command_async(struct audio_output *ao, + enum audio_output_command cmd) { assert(ao->command == AO_COMMAND_NONE); ao->command = cmd; g_cond_signal(ao->cond); +} + +/** + * Sends a command to the #audio_output object and waits for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void +ao_command(struct audio_output *ao, enum audio_output_command cmd) +{ + ao_command_async(ao, cmd); ao_command_wait(ao); } -static void ao_command_async(struct audio_output *ao, - enum audio_output_command cmd) +/** + * Lock the #audio_output object and execute the command + * synchronously. + */ +static void +ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) { - assert(ao->command == AO_COMMAND_NONE); - ao->command = cmd; - g_cond_signal(ao->cond); + g_mutex_lock(ao->mutex); + ao_command(ao, cmd); + g_mutex_unlock(ao->mutex); } void @@ -78,9 +106,7 @@ audio_output_enable(struct audio_output *ao) audio_output_thread_start(ao); } - g_mutex_lock(ao->mutex); - ao_command(ao, AO_COMMAND_ENABLE); - g_mutex_unlock(ao->mutex); + ao_lock_command(ao, AO_COMMAND_ENABLE); } void @@ -97,9 +123,7 @@ audio_output_disable(struct audio_output *ao) return; } - g_mutex_lock(ao->mutex); - ao_command(ao, AO_COMMAND_DISABLE); - g_mutex_unlock(ao->mutex); + ao_lock_command(ao, AO_COMMAND_DISABLE); } static void @@ -305,28 +329,11 @@ void audio_output_finish(struct audio_output *ao) assert(ao->fail_timer == NULL); if (ao->thread != NULL) { - g_mutex_lock(ao->mutex); assert(ao->allow_play); - ao_command(ao, AO_COMMAND_KILL); - g_mutex_unlock(ao->mutex); + ao_lock_command(ao, AO_COMMAND_KILL); g_thread_join(ao->thread); + ao->thread = NULL; } - if (ao->mixer != NULL) - mixer_free(ao->mixer); - - ao_plugin_finish(ao->plugin, ao->data); - - g_cond_free(ao->cond); - g_mutex_free(ao->mutex); - - if (ao->replay_gain_filter != NULL) - filter_free(ao->replay_gain_filter); - - if (ao->other_replay_gain_filter != NULL) - filter_free(ao->other_replay_gain_filter); - - filter_free(ao->filter); - - pcm_buffer_deinit(&ao->cross_fade_buffer); + audio_output_destruct(ao); } diff --git a/src/output_control.h b/src/output_control.h index f0e317d6e..f58a113e6 100644 --- a/src/output_control.h +++ b/src/output_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ struct audio_output; struct audio_format; struct config_param; struct music_pipe; +struct player_control; static inline GQuark audio_output_quark(void) @@ -38,6 +39,7 @@ audio_output_quark(void) bool audio_output_init(struct audio_output *ao, const struct config_param *param, + struct player_control *pc, GError **error_r); /** diff --git a/src/output_finish.c b/src/output_finish.c new file mode 100644 index 000000000..ac7f7e0c2 --- /dev/null +++ b/src/output_finish.c @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_internal.h" +#include "output_plugin.h" +#include "mixer_control.h" +#include "filter_plugin.h" + +#include <assert.h> + +void +audio_output_destruct(struct audio_output *ao) +{ + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->thread == NULL); + + if (ao->mixer != NULL) + mixer_free(ao->mixer); + + ao_plugin_finish(ao->plugin, ao->data); + + g_cond_free(ao->cond); + g_mutex_free(ao->mutex); + + if (ao->replay_gain_filter != NULL) + filter_free(ao->replay_gain_filter); + + if (ao->other_replay_gain_filter != NULL) + filter_free(ao->other_replay_gain_filter); + + filter_free(ao->filter); + + pcm_buffer_deinit(&ao->cross_fade_buffer); +} diff --git a/src/output_init.c b/src/output_init.c index 96f87f512..c52dcc8cd 100644 --- a/src/output_init.c +++ b/src/output_init.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -127,8 +127,12 @@ audio_output_load_mixer(void *ao, const struct config_param *param, bool audio_output_init(struct audio_output *ao, const struct config_param *param, + struct player_control *pc, GError **error_r) { + assert(ao != NULL); + assert(pc != NULL); + const struct audio_output_plugin *plugin = NULL; GError *error = NULL; @@ -250,6 +254,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, ao->command = AO_COMMAND_NONE; ao->mutex = g_mutex_new(); ao->cond = g_cond_new(); + ao->player_control = pc; ao->data = ao_plugin_init(plugin, &ao->config_audio_format, diff --git a/src/output_internal.h b/src/output_internal.h index 7102ea5cd..eba3aed91 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -217,6 +217,12 @@ struct audio_output { GCond *cond; /** + * The player_control object which "owns" this output. This + * object is needed to signal command completion. + */ + struct player_control *player_control; + + /** * The #music_chunk which is currently being played. All * chunks before this one may be returned to the * #music_buffer, because they are not going to be used by @@ -248,4 +254,7 @@ audio_output_command_is_finished(const struct audio_output *ao) return ao->command == AO_COMMAND_NONE; } +void +audio_output_destruct(struct audio_output *ao); + #endif diff --git a/src/output_list.c b/src/output_list.c index 8238f581b..75d24d63b 100644 --- a/src/output_list.c +++ b/src/output_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,10 +26,12 @@ extern const struct audio_output_plugin null_output_plugin; extern const struct audio_output_plugin fifo_output_plugin; extern const struct audio_output_plugin pipe_output_plugin; extern const struct audio_output_plugin alsaPlugin; +extern const struct audio_output_plugin roar_output_plugin; extern const struct audio_output_plugin ao_output_plugin; extern const struct audio_output_plugin oss_output_plugin; extern const struct audio_output_plugin openal_output_plugin; extern const struct audio_output_plugin osxPlugin; +extern const struct audio_output_plugin raopPlugin; extern const struct audio_output_plugin solaris_output_plugin; extern const struct audio_output_plugin pulse_output_plugin; extern const struct audio_output_plugin mvp_output_plugin; @@ -53,6 +55,9 @@ const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_ALSA &alsaPlugin, #endif +#ifdef HAVE_ROAR + &roar_output_plugin, +#endif #ifdef HAVE_AO &ao_output_plugin, #endif @@ -65,6 +70,9 @@ const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_OSX &osxPlugin, #endif +#ifdef ENABLE_RAOP_OUTPUT + &raopPlugin, +#endif #ifdef ENABLE_SOLARIS_OUTPUT &solaris_output_plugin, #endif diff --git a/src/output_list.h b/src/output_list.h index d72bc224b..3deb31c00 100644 --- a/src/output_list.h +++ b/src/output_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_plugin.h b/src/output_plugin.h index 36e17ed1b..72b519d13 100644 --- a/src/output_plugin.h +++ b/src/output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -52,7 +52,7 @@ struct audio_output_plugin { * none is configured * @param param the configuration section, or NULL if there is * no configuration - * @param error location to store the error occuring, or NULL + * @param error location to store the error occurring, or NULL * to ignore errors * @return NULL on error, or an opaque pointer to the plugin's * data @@ -72,7 +72,7 @@ struct audio_output_plugin { * fail: if an error occurs during that, it should be reported * by the open() method. * - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, or * NULL to ignore errors * @return true on success, false on error */ @@ -89,7 +89,7 @@ struct audio_output_plugin { * * @param audio_format the audio format in which data is going * to be delivered; may be modified by the plugin - * @param error location to store the error occuring, or NULL + * @param error location to store the error occurring, or NULL * to ignore errors */ bool (*open)(void *data, struct audio_format *audio_format, @@ -119,7 +119,7 @@ struct audio_output_plugin { /** * Play a chunk of audio data. * - * @param error location to store the error occuring, or NULL + * @param error location to store the error occurring, or NULL * to ignore errors * @return the number of bytes played, or 0 on error */ diff --git a/src/output_print.c b/src/output_print.c index 7a747ad2f..483648ca2 100644 --- a/src/output_print.c +++ b/src/output_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_print.h b/src/output_print.h index 5ad7e34c7..e02f4e9f5 100644 --- a/src/output_print.h +++ b/src/output_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_state.c b/src/output_state.c index e1187b951..7bcafb36b 100644 --- a/src/output_state.c +++ b/src/output_state.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_state.h b/src/output_state.h index 962ccd97a..320a3520a 100644 --- a/src/output_state.h +++ b/src/output_state.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_thread.c b/src/output_thread.c index bf56ca971..c36ba5f4f 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ #include "filter/convert_filter_plugin.h" #include "filter/replay_gain_filter_plugin.h" #include "mpd_error.h" +#include "notify.h" #include <glib.h> @@ -535,7 +536,7 @@ ao_play(struct audio_output *ao) ao->chunk_finished = true; g_mutex_unlock(ao->mutex); - player_lock_signal(); + player_lock_signal(ao->player_control); g_mutex_lock(ao->mutex); return true; diff --git a/src/output_thread.h b/src/output_thread.h index 1ee0856f2..5ad9a7527 100644 --- a/src/output_thread.h +++ b/src/output_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/page.c b/src/page.c index 59369cb34..e2e22791f 100644 --- a/src/page.c +++ b/src/page.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/page.h b/src/page.h index 652c4ad6e..8a3aaf396 100644 --- a/src/page.h +++ b/src/page.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/path.c b/src/path.c index 5e39c1636..42e5a5c2a 100644 --- a/src/path.c +++ b/src/path.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/path.h b/src/path.h index 512cd13ea..00c368e70 100644 --- a/src/path.h +++ b/src/path.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_buffer.h b/src/pcm_buffer.h index 73959ea03..1013e87a6 100644 --- a/src/pcm_buffer.h +++ b/src/pcm_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_byteswap.c b/src/pcm_byteswap.c index 6577319d4..4e604c79a 100644 --- a/src/pcm_byteswap.c +++ b/src/pcm_byteswap.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_byteswap.h b/src/pcm_byteswap.h index 005e75ded..1098a2fe1 100644 --- a/src/pcm_byteswap.h +++ b/src/pcm_byteswap.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_channels.c b/src/pcm_channels.c index 34e72ca4e..05a65a62a 100644 --- a/src/pcm_channels.c +++ b/src/pcm_channels.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_channels.h b/src/pcm_channels.h index a23cbd364..ee27697b7 100644 --- a/src/pcm_channels.h +++ b/src/pcm_channels.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_convert.c b/src/pcm_convert.c index 5fe89b53a..59dc0d5c4 100644 --- a/src/pcm_convert.c +++ b/src/pcm_convert.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_convert.h b/src/pcm_convert.h index 01ba2c787..6dbd7541b 100644 --- a/src/pcm_convert.h +++ b/src/pcm_convert.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -75,7 +75,7 @@ void pcm_convert_deinit(struct pcm_convert_state *state); * @param src_size the size of #src in bytes * @param dest_format the requested destination audio format * @param dest_size_r returns the number of bytes of the destination buffer - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return the destination buffer, or NULL on error */ diff --git a/src/pcm_dither.c b/src/pcm_dither.c index 03388f0e0..a75b07e5d 100644 --- a/src/pcm_dither.c +++ b/src/pcm_dither.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_dither.h b/src/pcm_dither.h index dafae957f..5ba4d7bb4 100644 --- a/src/pcm_dither.h +++ b/src/pcm_dither.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_format.c b/src/pcm_format.c index 3fd76a987..4ca0b45b3 100644 --- a/src/pcm_format.c +++ b/src/pcm_format.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_format.h b/src/pcm_format.h index 3e96fc65f..54cc32020 100644 --- a/src/pcm_format.h +++ b/src/pcm_format.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_mix.c b/src/pcm_mix.c index 3145c07be..8cdad2c11 100644 --- a/src/pcm_mix.c +++ b/src/pcm_mix.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_mix.h b/src/pcm_mix.h index 086d5501e..9b576f40d 100644 --- a/src/pcm_mix.h +++ b/src/pcm_mix.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_pack.c b/src/pcm_pack.c index 9af0ab1ed..680d2a32f 100644 --- a/src/pcm_pack.c +++ b/src/pcm_pack.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_pack.h b/src/pcm_pack.h index 3c99eaa35..0055e91df 100644 --- a/src/pcm_pack.h +++ b/src/pcm_pack.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_prng.h b/src/pcm_prng.h index 186ed9d0e..457ba4b66 100644 --- a/src/pcm_prng.h +++ b/src/pcm_prng.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_resample.c b/src/pcm_resample.c index 4a7578e09..a1e4ee149 100644 --- a/src/pcm_resample.c +++ b/src/pcm_resample.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_resample.h b/src/pcm_resample.h index 24d17ff9b..167f15e8c 100644 --- a/src/pcm_resample.h +++ b/src/pcm_resample.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_resample_fallback.c b/src/pcm_resample_fallback.c index 0c75d8ba4..79c2f5176 100644 --- a/src/pcm_resample_fallback.c +++ b/src/pcm_resample_fallback.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_resample_internal.h b/src/pcm_resample_internal.h index 26acc809d..37aae96ef 100644 --- a/src/pcm_resample_internal.h +++ b/src/pcm_resample_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_resample_libsamplerate.c b/src/pcm_resample_libsamplerate.c index 99ca53da4..ebce488f4 100644 --- a/src/pcm_resample_libsamplerate.c +++ b/src/pcm_resample_libsamplerate.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_utils.h b/src/pcm_utils.h index 15f9e1b10..b6a6f3787 100644 --- a/src/pcm_utils.h +++ b/src/pcm_utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_volume.c b/src/pcm_volume.c index 240c779d8..69c239cb8 100644 --- a/src/pcm_volume.c +++ b/src/pcm_volume.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_volume.h b/src/pcm_volume.h index eb61e9526..b389dee3c 100644 --- a/src/pcm_volume.h +++ b/src/pcm_volume.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/permission.c b/src/permission.c index 17b443e0a..cd52b9c86 100644 --- a/src/permission.c +++ b/src/permission.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/permission.h b/src/permission.h index 9b3a60a66..6c3771362 100644 --- a/src/permission.h +++ b/src/permission.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pipe.c b/src/pipe.c index 2f5f70e43..d8131432f 100644 --- a/src/pipe.c +++ b/src/pipe.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pipe.h b/src/pipe.h index efa7a84f0..84b9869e0 100644 --- a/src/pipe.h +++ b/src/pipe.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/player_control.c b/src/player_control.c index a190bbd8b..51420a43f 100644 --- a/src/player_control.c +++ b/src/player_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,237 +32,247 @@ #include <stdio.h> #include <math.h> -struct player_control pc; - static void -pc_enqueue_song_locked(struct song *song); +pc_enqueue_song_locked(struct player_control *pc, struct song *song); -void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play) +struct player_control * +pc_new(unsigned buffer_chunks, unsigned int buffered_before_play) { - pc.buffer_chunks = buffer_chunks; - pc.buffered_before_play = buffered_before_play; - - pc.mutex = g_mutex_new(); - pc.cond = g_cond_new(); - - pc.command = PLAYER_COMMAND_NONE; - pc.error = PLAYER_ERROR_NOERROR; - pc.state = PLAYER_STATE_STOP; - pc.cross_fade_seconds = 0; - pc.mixramp_db = 0; - pc.mixramp_delay_seconds = nanf(""); + struct player_control *pc = g_new0(struct player_control, 1); + + pc->buffer_chunks = buffer_chunks; + pc->buffered_before_play = buffered_before_play; + + pc->mutex = g_mutex_new(); + pc->cond = g_cond_new(); + + pc->command = PLAYER_COMMAND_NONE; + pc->error = PLAYER_ERROR_NOERROR; + pc->state = PLAYER_STATE_STOP; + pc->cross_fade_seconds = 0; + pc->mixramp_db = 0; + pc->mixramp_delay_seconds = nanf(""); + + return pc; } -void pc_deinit(void) +void +pc_free(struct player_control *pc) { - g_cond_free(pc.cond); - g_mutex_free(pc.mutex); + g_cond_free(pc->cond); + g_mutex_free(pc->mutex); + g_free(pc); } void -player_wait_decoder(struct decoder_control *dc) +player_wait_decoder(struct player_control *pc, struct decoder_control *dc) { + assert(pc != NULL); + assert(dc != NULL); + assert(dc->client_cond == pc->cond); + /* during this function, the decoder lock is held, because we're waiting for the decoder thread */ - g_cond_wait(pc.cond, dc->mutex); + g_cond_wait(pc->cond, dc->mutex); } void -pc_song_deleted(const struct song *song) +pc_song_deleted(struct player_control *pc, const struct song *song) { - if (pc.errored_song == song) { - pc.error = PLAYER_ERROR_NOERROR; - pc.errored_song = NULL; + if (pc->errored_song == song) { + pc->error = PLAYER_ERROR_NOERROR; + pc->errored_song = NULL; } } static void -player_command_wait_locked(void) +player_command_wait_locked(struct player_control *pc) { - while (pc.command != PLAYER_COMMAND_NONE) - g_cond_wait(main_cond, pc.mutex); + while (pc->command != PLAYER_COMMAND_NONE) + g_cond_wait(main_cond, pc->mutex); } static void -player_command_locked(enum player_command cmd) +player_command_locked(struct player_control *pc, enum player_command cmd) { - assert(pc.command == PLAYER_COMMAND_NONE); + assert(pc->command == PLAYER_COMMAND_NONE); - pc.command = cmd; - player_signal(); - player_command_wait_locked(); + pc->command = cmd; + player_signal(pc); + player_command_wait_locked(pc); } static void -player_command(enum player_command cmd) +player_command(struct player_control *pc, enum player_command cmd) { - player_lock(); - player_command_locked(cmd); - player_unlock(); + player_lock(pc); + player_command_locked(pc, cmd); + player_unlock(pc); } void -pc_play(struct song *song) +pc_play(struct player_control *pc, struct song *song) { assert(song != NULL); - player_lock(); + player_lock(pc); - if (pc.state != PLAYER_STATE_STOP) - player_command_locked(PLAYER_COMMAND_STOP); + if (pc->state != PLAYER_STATE_STOP) + player_command_locked(pc, PLAYER_COMMAND_STOP); - assert(pc.next_song == NULL); + assert(pc->next_song == NULL); - pc_enqueue_song_locked(song); + pc_enqueue_song_locked(pc, song); - assert(pc.next_song == NULL); + assert(pc->next_song == NULL); - player_unlock(); + player_unlock(pc); idle_add(IDLE_PLAYER); } -void pc_cancel(void) +void +pc_cancel(struct player_control *pc) { - player_command(PLAYER_COMMAND_CANCEL); - assert(pc.next_song == NULL); + player_command(pc, PLAYER_COMMAND_CANCEL); + assert(pc->next_song == NULL); } void -pc_stop(void) +pc_stop(struct player_control *pc) { - player_command(PLAYER_COMMAND_CLOSE_AUDIO); - assert(pc.next_song == NULL); + player_command(pc, PLAYER_COMMAND_CLOSE_AUDIO); + assert(pc->next_song == NULL); idle_add(IDLE_PLAYER); } void -pc_update_audio(void) +pc_update_audio(struct player_control *pc) { - player_command(PLAYER_COMMAND_UPDATE_AUDIO); + player_command(pc, PLAYER_COMMAND_UPDATE_AUDIO); } void -pc_kill(void) +pc_kill(struct player_control *pc) { - assert(pc.thread != NULL); + assert(pc->thread != NULL); - player_command(PLAYER_COMMAND_EXIT); - g_thread_join(pc.thread); - pc.thread = NULL; + player_command(pc, PLAYER_COMMAND_EXIT); + g_thread_join(pc->thread); + pc->thread = NULL; idle_add(IDLE_PLAYER); } void -pc_pause(void) +pc_pause(struct player_control *pc) { - player_lock(); + player_lock(pc); - if (pc.state != PLAYER_STATE_STOP) { - player_command_locked(PLAYER_COMMAND_PAUSE); + if (pc->state != PLAYER_STATE_STOP) { + player_command_locked(pc, PLAYER_COMMAND_PAUSE); idle_add(IDLE_PLAYER); } - player_unlock(); + player_unlock(pc); } static void -pc_pause_locked(void) +pc_pause_locked(struct player_control *pc) { - if (pc.state != PLAYER_STATE_STOP) { - player_command_locked(PLAYER_COMMAND_PAUSE); + if (pc->state != PLAYER_STATE_STOP) { + player_command_locked(pc, PLAYER_COMMAND_PAUSE); idle_add(IDLE_PLAYER); } } void -pc_set_pause(bool pause_flag) +pc_set_pause(struct player_control *pc, bool pause_flag) { - player_lock(); + player_lock(pc); - switch (pc.state) { + switch (pc->state) { case PLAYER_STATE_STOP: break; case PLAYER_STATE_PLAY: if (pause_flag) - pc_pause_locked(); + pc_pause_locked(pc); break; case PLAYER_STATE_PAUSE: if (!pause_flag) - pc_pause_locked(); + pc_pause_locked(pc); break; } - player_unlock(); + player_unlock(pc); } void -pc_get_status(struct player_status *status) +pc_get_status(struct player_control *pc, struct player_status *status) { - player_lock(); - player_command_locked(PLAYER_COMMAND_REFRESH); + player_lock(pc); + player_command_locked(pc, PLAYER_COMMAND_REFRESH); - status->state = pc.state; + status->state = pc->state; - if (pc.state != PLAYER_STATE_STOP) { - status->bit_rate = pc.bit_rate; - status->audio_format = pc.audio_format; - status->total_time = pc.total_time; - status->elapsed_time = pc.elapsed_time; + if (pc->state != PLAYER_STATE_STOP) { + status->bit_rate = pc->bit_rate; + status->audio_format = pc->audio_format; + status->total_time = pc->total_time; + status->elapsed_time = pc->elapsed_time; } - player_unlock(); + player_unlock(pc); } enum player_state -pc_get_state(void) +pc_get_state(struct player_control *pc) { - return pc.state; + return pc->state; } void -pc_clear_error(void) +pc_clear_error(struct player_control *pc) { - player_lock(); - pc.error = PLAYER_ERROR_NOERROR; - pc.errored_song = NULL; - player_unlock(); + player_lock(pc); + pc->error = PLAYER_ERROR_NOERROR; + pc->errored_song = NULL; + player_unlock(pc); } enum player_error -pc_get_error(void) +pc_get_error(struct player_control *pc) { - return pc.error; + return pc->error; } static char * -pc_errored_song_uri(void) +pc_errored_song_uri(struct player_control *pc) { - return song_get_uri(pc.errored_song); + return song_get_uri(pc->errored_song); } char * -pc_get_error_message(void) +pc_get_error_message(struct player_control *pc) { char *error; char *uri; - switch (pc.error) { + switch (pc->error) { case PLAYER_ERROR_NOERROR: return NULL; case PLAYER_ERROR_FILENOTFOUND: - uri = pc_errored_song_uri(); + uri = pc_errored_song_uri(pc); error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri); g_free(uri); return error; case PLAYER_ERROR_FILE: - uri = pc_errored_song_uri(); + uri = pc_errored_song_uri(pc); error = g_strdup_printf("problems decoding \"%s\"", uri); g_free(uri); return error; @@ -271,10 +281,10 @@ pc_get_error_message(void) return g_strdup("problems opening audio device"); case PLAYER_ERROR_SYSTEM: - return g_strdup("system error occured"); + return g_strdup("system error occurred"); case PLAYER_ERROR_UNKTYPE: - uri = pc_errored_song_uri(); + uri = pc_errored_song_uri(pc); error = g_strdup_printf("file type of \"%s\" is unknown", uri); g_free(uri); return error; @@ -285,40 +295,40 @@ pc_get_error_message(void) } static void -pc_enqueue_song_locked(struct song *song) +pc_enqueue_song_locked(struct player_control *pc, struct song *song) { assert(song != NULL); - assert(pc.next_song == NULL); + assert(pc->next_song == NULL); - pc.next_song = song; - player_command_locked(PLAYER_COMMAND_QUEUE); + pc->next_song = song; + player_command_locked(pc, PLAYER_COMMAND_QUEUE); } void -pc_enqueue_song(struct song *song) +pc_enqueue_song(struct player_control *pc, struct song *song) { assert(song != NULL); - player_lock(); - pc_enqueue_song_locked(song); - player_unlock(); + player_lock(pc); + pc_enqueue_song_locked(pc, song); + player_unlock(pc); } bool -pc_seek(struct song *song, float seek_time) +pc_seek(struct player_control *pc, struct song *song, float seek_time) { assert(song != NULL); - if (pc.state == PLAYER_STATE_STOP) + if (pc->state == PLAYER_STATE_STOP) return false; - player_lock(); - pc.next_song = song; - pc.seek_where = seek_time; - player_command_locked(PLAYER_COMMAND_SEEK); - player_unlock(); + player_lock(pc); + pc->next_song = song; + pc->seek_where = seek_time; + player_command_locked(pc, PLAYER_COMMAND_SEEK); + player_unlock(pc); - assert(pc.next_song == NULL); + assert(pc->next_song == NULL); idle_add(IDLE_PLAYER); @@ -326,51 +336,51 @@ pc_seek(struct song *song, float seek_time) } float -pc_get_cross_fade(void) +pc_get_cross_fade(const struct player_control *pc) { - return pc.cross_fade_seconds; + return pc->cross_fade_seconds; } void -pc_set_cross_fade(float cross_fade_seconds) +pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds) { if (cross_fade_seconds < 0) cross_fade_seconds = 0; - pc.cross_fade_seconds = cross_fade_seconds; + pc->cross_fade_seconds = cross_fade_seconds; idle_add(IDLE_OPTIONS); } float -pc_get_mixramp_db(void) +pc_get_mixramp_db(const struct player_control *pc) { - return pc.mixramp_db; + return pc->mixramp_db; } void -pc_set_mixramp_db(float mixramp_db) +pc_set_mixramp_db(struct player_control *pc, float mixramp_db) { - pc.mixramp_db = mixramp_db; + pc->mixramp_db = mixramp_db; idle_add(IDLE_OPTIONS); } float -pc_get_mixramp_delay(void) +pc_get_mixramp_delay(const struct player_control *pc) { - return pc.mixramp_delay_seconds; + return pc->mixramp_delay_seconds; } void -pc_set_mixramp_delay(float mixramp_delay_seconds) +pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds) { - pc.mixramp_delay_seconds = mixramp_delay_seconds; + pc->mixramp_delay_seconds = mixramp_delay_seconds; idle_add(IDLE_OPTIONS); } double -pc_get_total_play_time(void) +pc_get_total_play_time(const struct player_control *pc) { - return pc.total_play_time; + return pc->total_play_time; } diff --git a/src/player_control.h b/src/player_control.h index 76c47609a..5a04ab0f9 100644 --- a/src/player_control.h +++ b/src/player_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,9 +20,10 @@ #ifndef MPD_PLAYER_H #define MPD_PLAYER_H -#include "notify.h" #include "audio_format.h" +#include <glib.h> + #include <stdint.h> struct decoder_control; @@ -116,28 +117,28 @@ struct player_control { double total_play_time; }; -extern struct player_control pc; - -void pc_init(unsigned buffer_chunks, unsigned buffered_before_play); +struct player_control * +pc_new(unsigned buffer_chunks, unsigned buffered_before_play); -void pc_deinit(void); +void +pc_free(struct player_control *pc); /** * Locks the #player_control object. */ static inline void -player_lock(void) +player_lock(struct player_control *pc) { - g_mutex_lock(pc.mutex); + g_mutex_lock(pc->mutex); } /** * Unlocks the #player_control object. */ static inline void -player_unlock(void) +player_unlock(struct player_control *pc) { - g_mutex_unlock(pc.mutex); + g_mutex_unlock(pc->mutex); } /** @@ -146,9 +147,9 @@ player_unlock(void) * to calling this function. */ static inline void -player_wait(void) +player_wait(struct player_control *pc) { - g_cond_wait(pc.cond, pc.mutex); + g_cond_wait(pc->cond, pc->mutex); } /** @@ -159,16 +160,16 @@ player_wait(void) * Note the small difference to the player_wait() function! */ void -player_wait_decoder(struct decoder_control *dc); +player_wait_decoder(struct player_control *pc, struct decoder_control *dc); /** * Signals the #player_control object. The object should be locked * prior to calling this function. */ static inline void -player_signal(void) +player_signal(struct player_control *pc) { - g_cond_signal(pc.cond); + g_cond_signal(pc->cond); } /** @@ -176,11 +177,11 @@ player_signal(void) * locked by this function. */ static inline void -player_lock_signal(void) +player_lock_signal(struct player_control *pc) { - player_lock(); - player_signal(); - player_unlock(); + player_lock(pc); + player_signal(pc); + player_unlock(pc); } /** @@ -189,33 +190,34 @@ player_lock_signal(void) * not point to an invalid pointer. */ void -pc_song_deleted(const struct song *song); +pc_song_deleted(struct player_control *pc, const struct song *song); void -pc_play(struct song *song); +pc_play(struct player_control *pc, struct song *song); /** * see PLAYER_COMMAND_CANCEL */ -void pc_cancel(void); +void +pc_cancel(struct player_control *pc); void -pc_set_pause(bool pause_flag); +pc_set_pause(struct player_control *pc, bool pause_flag); void -pc_pause(void); +pc_pause(struct player_control *pc); void -pc_kill(void); +pc_kill(struct player_control *pc); void -pc_get_status(struct player_status *status); +pc_get_status(struct player_control *pc, struct player_status *status); enum player_state -pc_get_state(void); +pc_get_state(struct player_control *pc); void -pc_clear_error(void); +pc_clear_error(struct player_control *pc); /** * Returns the human-readable message describing the last error during @@ -223,19 +225,19 @@ pc_clear_error(void); * returned string. */ char * -pc_get_error_message(void); +pc_get_error_message(struct player_control *pc); enum player_error -pc_get_error(void); +pc_get_error(struct player_control *pc); void -pc_stop(void); +pc_stop(struct player_control *pc); void -pc_update_audio(void); +pc_update_audio(struct player_control *pc); void -pc_enqueue_song(struct song *song); +pc_enqueue_song(struct player_control *pc, struct song *song); /** * Makes the player thread seek the specified song to a position. @@ -244,27 +246,27 @@ pc_enqueue_song(struct song *song); * playing currently) */ bool -pc_seek(struct song *song, float seek_time); +pc_seek(struct player_control *pc, struct song *song, float seek_time); void -pc_set_cross_fade(float cross_fade_seconds); +pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds); float -pc_get_cross_fade(void); +pc_get_cross_fade(const struct player_control *pc); void -pc_set_mixramp_db(float mixramp_db); +pc_set_mixramp_db(struct player_control *pc, float mixramp_db); float -pc_get_mixramp_db(void); +pc_get_mixramp_db(const struct player_control *pc); void -pc_set_mixramp_delay(float mixramp_delay_seconds); +pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds); float -pc_get_mixramp_delay(void); +pc_get_mixramp_delay(const struct player_control *pc); double -pc_get_total_play_time(void); +pc_get_total_play_time(const struct player_control *pc); #endif diff --git a/src/player_thread.c b/src/player_thread.c index a89e59908..58682f2ca 100644 --- a/src/player_thread.c +++ b/src/player_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -48,6 +48,8 @@ enum xfade_state { }; struct player { + struct player_control *pc; + struct decoder_control *dc; struct music_pipe *pipe; @@ -110,26 +112,28 @@ struct player { * output thread. This attribute is only used if * audio_output_all_get_elapsed_time() didn't return a usable * value; the output thread can estimate the elapsed time more - * precisly. + * precisely. */ float elapsed_time; }; static struct music_buffer *player_buffer; -static void player_command_finished_locked(void) +static void +player_command_finished_locked(struct player_control *pc) { - assert(pc.command != PLAYER_COMMAND_NONE); + assert(pc->command != PLAYER_COMMAND_NONE); - pc.command = PLAYER_COMMAND_NONE; + pc->command = PLAYER_COMMAND_NONE; g_cond_signal(main_cond); } -static void player_command_finished(void) +static void +player_command_finished(struct player_control *pc) { - player_lock(); - player_command_finished_locked(); - player_unlock(); + player_lock(pc); + player_command_finished_locked(pc); + player_unlock(pc); } /** @@ -140,12 +144,13 @@ static void player_command_finished(void) static void player_dc_start(struct player *player, struct music_pipe *pipe) { + struct player_control *pc = player->pc; struct decoder_control *dc = player->dc; - assert(player->queued || pc.command == PLAYER_COMMAND_SEEK); - assert(pc.next_song != NULL); + assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); + assert(pc->next_song != NULL); - dc_start(dc, pc.next_song, player_buffer, pipe); + dc_start(dc, pc->next_song, player_buffer, pipe); } /** @@ -208,41 +213,42 @@ player_dc_stop(struct player *player) static bool player_wait_for_decoder(struct player *player) { + struct player_control *pc = player->pc; struct decoder_control *dc = player->dc; - assert(player->queued || pc.command == PLAYER_COMMAND_SEEK); - assert(pc.next_song != NULL); + assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); + assert(pc->next_song != NULL); player->queued = false; if (decoder_lock_has_failed(dc)) { - player_lock(); - pc.errored_song = dc->song; - pc.error = PLAYER_ERROR_FILE; - pc.next_song = NULL; - player_unlock(); + player_lock(pc); + pc->errored_song = dc->song; + pc->error = PLAYER_ERROR_FILE; + pc->next_song = NULL; + player_unlock(pc); return false; } - player->song = pc.next_song; + player->song = pc->next_song; player->elapsed_time = 0.0; /* set the "starting" flag, which will be cleared by player_check_decoder_startup() */ player->decoder_starting = true; - player_lock(); + player_lock(pc); /* update player_control's song information */ - pc.total_time = song_get_duration(pc.next_song); - pc.bit_rate = 0; - audio_format_clear(&pc.audio_format); + pc->total_time = song_get_duration(pc->next_song); + pc->bit_rate = 0; + audio_format_clear(&pc->audio_format); /* clear the queued song */ - pc.next_song = NULL; + pc->next_song = NULL; - player_unlock(); + player_unlock(pc); /* call syncPlaylistWithQueue() in the main thread */ event_pipe_emit(PIPE_EVENT_PLAYLIST); @@ -280,6 +286,7 @@ real_song_duration(const struct song *song, double decoder_duration) static bool player_check_decoder_startup(struct player *player) { + struct player_control *pc = player->pc; struct decoder_control *dc = player->dc; assert(player->decoder_starting); @@ -290,10 +297,10 @@ player_check_decoder_startup(struct player *player) /* the decoder failed */ decoder_unlock(dc); - player_lock(); - pc.errored_song = dc->song; - pc.error = PLAYER_ERROR_FILE; - player_unlock(); + player_lock(pc); + pc->errored_song = dc->song; + pc->error = PLAYER_ERROR_FILE; + player_unlock(pc); return false; } else if (!decoder_is_starting(dc)) { @@ -302,15 +309,15 @@ player_check_decoder_startup(struct player *player) decoder_unlock(dc); if (audio_format_defined(&player->play_audio_format) && - !audio_output_all_wait(1)) + !audio_output_all_wait(pc, 1)) /* the output devices havn't finished playing all chunks yet - wait for that */ return true; - player_lock(); - pc.total_time = real_song_duration(dc->song, dc->total_time); - pc.audio_format = dc->in_audio_format; - player_unlock(); + player_lock(pc); + pc->total_time = real_song_duration(dc->song, dc->total_time); + pc->audio_format = dc->in_audio_format; + player_unlock(pc); player->play_audio_format = dc->out_audio_format; player->decoder_starting = false; @@ -323,13 +330,13 @@ player_check_decoder_startup(struct player *player) "while playing \"%s\"", uri); g_free(uri); - player_lock(); - pc.error = PLAYER_ERROR_AUDIO; + player_lock(pc); + pc->error = PLAYER_ERROR_AUDIO; /* pause: the user may resume playback as soon as an audio output becomes available */ - pc.state = PLAYER_STATE_PAUSE; - player_unlock(); + pc->state = PLAYER_STATE_PAUSE; + player_unlock(pc); player->paused = true; return true; @@ -339,7 +346,7 @@ player_check_decoder_startup(struct player *player) } else { /* the decoder is not yet ready; wait some more */ - player_wait_decoder(dc); + player_wait_decoder(pc, dc); decoder_unlock(dc); return true; @@ -393,10 +400,11 @@ player_send_silence(struct player *player) */ static bool player_seek_decoder(struct player *player) { - struct song *song = pc.next_song; + struct player_control *pc = player->pc; + struct song *song = pc->next_song; struct decoder_control *dc = player->dc; - assert(pc.next_song != NULL); + assert(pc->next_song != NULL); if (decoder_current_song(dc) != song) { /* the decoder is already decoding the "next" song - @@ -412,7 +420,7 @@ static bool player_seek_decoder(struct player *player) player_dc_start(player, player->pipe); if (!player_wait_for_decoder(player)) { /* decoder failure */ - player_command_finished(); + player_command_finished(pc); return false; } } else { @@ -424,7 +432,7 @@ static bool player_seek_decoder(struct player *player) player->pipe = dc->pipe; } - pc.next_song = NULL; + pc->next_song = NULL; player->queued = false; } @@ -433,28 +441,28 @@ static bool player_seek_decoder(struct player *player) while (player->decoder_starting) { if (!player_check_decoder_startup(player)) { /* decoder failure */ - player_command_finished(); + player_command_finished(pc); return false; } } /* send the SEEK command */ - double where = pc.seek_where; - if (where > pc.total_time) - where = pc.total_time - 0.1; + double where = pc->seek_where; + if (where > pc->total_time) + where = pc->total_time - 0.1; if (where < 0.0) where = 0.0; if (!dc_seek(dc, where + song->start_ms / 1000.0)) { /* decoder failure */ - player_command_finished(); + player_command_finished(pc); return false; } player->elapsed_time = where; - player_command_finished(); + player_command_finished(pc); player->xfade = XFADE_UNKNOWN; @@ -471,9 +479,10 @@ static bool player_seek_decoder(struct player *player) */ static void player_process_command(struct player *player) { + struct player_control *pc = player->pc; G_GNUC_UNUSED struct decoder_control *dc = player->dc; - switch (pc.command) { + switch (pc->command) { case PLAYER_COMMAND_NONE: case PLAYER_COMMAND_STOP: case PLAYER_COMMAND_EXIT: @@ -481,95 +490,95 @@ static void player_process_command(struct player *player) break; case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(); + player_unlock(pc); audio_output_all_enable_disable(); - player_lock(); - player_command_finished_locked(); + player_lock(pc); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_QUEUE: - assert(pc.next_song != NULL); + assert(pc->next_song != NULL); assert(!player->queued); assert(!player_dc_at_next_song(player)); player->queued = true; - player_command_finished_locked(); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_PAUSE: - player_unlock(); + player_unlock(pc); player->paused = !player->paused; if (player->paused) { audio_output_all_pause(); - player_lock(); + player_lock(pc); - pc.state = PLAYER_STATE_PAUSE; + pc->state = PLAYER_STATE_PAUSE; } else if (!audio_format_defined(&player->play_audio_format)) { /* the decoder hasn't provided an audio format yet - don't open the audio device yet */ - player_lock(); + player_lock(pc); - pc.state = PLAYER_STATE_PLAY; + pc->state = PLAYER_STATE_PLAY; } else if (audio_output_all_open(&player->play_audio_format, player_buffer)) { /* unpaused, continue playing */ - player_lock(); + player_lock(pc); - pc.state = PLAYER_STATE_PLAY; + pc->state = PLAYER_STATE_PLAY; } else { /* the audio device has failed - rollback to pause mode */ - pc.error = PLAYER_ERROR_AUDIO; + pc->error = PLAYER_ERROR_AUDIO; player->paused = true; - player_lock(); + player_lock(pc); } - player_command_finished_locked(); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_SEEK: - player_unlock(); + player_unlock(pc); player_seek_decoder(player); - player_lock(); + player_lock(pc); break; case PLAYER_COMMAND_CANCEL: - if (pc.next_song == NULL) { + if (pc->next_song == NULL) { /* the cancel request arrived too late, we're already playing the queued song... stop everything now */ - pc.command = PLAYER_COMMAND_STOP; + pc->command = PLAYER_COMMAND_STOP; return; } if (player_dc_at_next_song(player)) { /* the decoder is already decoding the song - stop it and reset the position */ - player_unlock(); + player_unlock(pc); player_dc_stop(player); - player_lock(); + player_lock(pc); } - pc.next_song = NULL; + pc->next_song = NULL; player->queued = false; - player_command_finished_locked(); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_REFRESH: if (audio_format_defined(&player->play_audio_format) && !player->paused) { - player_unlock(); + player_unlock(pc); audio_output_all_check(); - player_lock(); + player_lock(pc); } - pc.elapsed_time = audio_output_all_get_elapsed_time(); - if (pc.elapsed_time < 0.0) - pc.elapsed_time = player->elapsed_time; + pc->elapsed_time = audio_output_all_get_elapsed_time(); + if (pc->elapsed_time < 0.0) + pc->elapsed_time = player->elapsed_time; - player_command_finished_locked(); + player_command_finished_locked(pc); break; } } @@ -605,7 +614,8 @@ update_song_tag(struct song *song, const struct tag *new_tag) * Player lock is not held. */ static bool -play_chunk(struct song *song, struct music_chunk *chunk, +play_chunk(struct player_control *pc, + struct song *song, struct music_chunk *chunk, const struct audio_format *format) { assert(music_chunk_check_format(chunk, format)); @@ -618,16 +628,16 @@ play_chunk(struct song *song, struct music_chunk *chunk, return true; } - player_lock(); - pc.bit_rate = chunk->bit_rate; - player_unlock(); + player_lock(pc); + pc->bit_rate = chunk->bit_rate; + player_unlock(pc); /* send the chunk to the audio outputs */ if (!audio_output_all_play(chunk)) return false; - pc.total_play_time += (double)chunk->length / + pc->total_play_time += (double)chunk->length / audio_format_time_to_size(format); return true; } @@ -641,9 +651,10 @@ play_chunk(struct song *song, struct music_chunk *chunk, static bool play_next_chunk(struct player *player) { + struct player_control *pc = player->pc; struct decoder_control *dc = player->dc; - if (!audio_output_all_wait(64)) + if (!audio_output_all_wait(pc, 64)) /* the output pipe is still large enough, don't send another chunk */ return true; @@ -680,7 +691,7 @@ play_next_chunk(struct player *player) other_chunk->tag); other_chunk->tag = NULL; - if (isnan(pc.mixramp_delay_seconds)) { + if (isnan(pc->mixramp_delay_seconds)) { chunk->mix_ratio = ((float)cross_fade_position) / player->cross_fade_chunks; } else { @@ -715,7 +726,7 @@ play_next_chunk(struct player *player) } else { /* wait for the decoder */ decoder_signal(dc); - player_wait_decoder(dc); + player_wait_decoder(pc, dc); decoder_unlock(dc); return true; @@ -738,19 +749,20 @@ play_next_chunk(struct player *player) /* play the current chunk */ - if (!play_chunk(player->song, chunk, &player->play_audio_format)) { + if (!play_chunk(player->pc, player->song, chunk, + &player->play_audio_format)) { music_buffer_return(player_buffer, chunk); - player_lock(); + player_lock(pc); - pc.error = PLAYER_ERROR_AUDIO; + pc->error = PLAYER_ERROR_AUDIO; /* pause: the user may resume playback as soon as an audio output becomes available */ - pc.state = PLAYER_STATE_PAUSE; + pc->state = PLAYER_STATE_PAUSE; player->paused = true; - player_unlock(); + player_unlock(pc); return false; } @@ -760,7 +772,7 @@ play_next_chunk(struct player *player) larger block at a time */ decoder_lock(dc); if (!decoder_is_idle(dc) && - music_pipe_size(dc->pipe) <= (pc.buffered_before_play + + music_pipe_size(dc->pipe) <= (pc->buffered_before_play + music_buffer_size(player_buffer) * 3) / 4) decoder_signal(dc); decoder_unlock(dc); @@ -802,9 +814,10 @@ player_song_border(struct player *player) * basically a state machine, which multiplexes data between the * decoder thread and the output threads. */ -static void do_play(struct decoder_control *dc) +static void do_play(struct player_control *pc, struct decoder_control *dc) { struct player player = { + .pc = pc, .dc = dc, .buffering = true, .decoder_starting = false, @@ -818,42 +831,42 @@ static void do_play(struct decoder_control *dc) .elapsed_time = 0.0, }; - player_unlock(); + player_unlock(pc); player.pipe = music_pipe_new(); player_dc_start(&player, player.pipe); if (!player_wait_for_decoder(&player)) { player_dc_stop(&player); - player_command_finished(); + player_command_finished(pc); music_pipe_free(player.pipe); event_pipe_emit(PIPE_EVENT_PLAYLIST); - player_lock(); + player_lock(pc); return; } - player_lock(); - pc.state = PLAYER_STATE_PLAY; - player_command_finished_locked(); + player_lock(pc); + pc->state = PLAYER_STATE_PLAY; + player_command_finished_locked(pc); while (true) { player_process_command(&player); - if (pc.command == PLAYER_COMMAND_STOP || - pc.command == PLAYER_COMMAND_EXIT || - pc.command == PLAYER_COMMAND_CLOSE_AUDIO) { - player_unlock(); + if (pc->command == PLAYER_COMMAND_STOP || + pc->command == PLAYER_COMMAND_EXIT || + pc->command == PLAYER_COMMAND_CLOSE_AUDIO) { + player_unlock(pc); audio_output_all_cancel(); break; } - player_unlock(); + player_unlock(pc); if (player.buffering) { /* buffering at the start of the song - wait until the buffer is large enough, to prevent stuttering on slow machines */ - if (music_pipe_size(player.pipe) < pc.buffered_before_play && + if (music_pipe_size(player.pipe) < pc->buffered_before_play && !decoder_lock_is_idle(dc)) { /* not enough decoded buffer space yet */ @@ -865,9 +878,9 @@ static void do_play(struct decoder_control *dc) decoder_lock(dc); /* XXX race condition: check decoder again */ - player_wait_decoder(dc); + player_wait_decoder(pc, dc); decoder_unlock(dc); - player_lock(); + player_lock(pc); continue; } else { /* buffering is complete */ @@ -891,7 +904,7 @@ static void do_play(struct decoder_control *dc) !dc_seek(dc, song->start_ms / 1000.0)) player_dc_stop(&player); - player_lock(); + player_lock(pc); continue; } @@ -920,9 +933,9 @@ static void do_play(struct decoder_control *dc) calculate how many chunks will be required for it */ player.cross_fade_chunks = - cross_fade_calc(pc.cross_fade_seconds, dc->total_time, - pc.mixramp_db, - pc.mixramp_delay_seconds, + cross_fade_calc(pc->cross_fade_seconds, dc->total_time, + pc->mixramp_db, + pc->mixramp_delay_seconds, dc->replay_gain_db, dc->replay_gain_prev_db, dc->mixramp_start, @@ -930,7 +943,7 @@ static void do_play(struct decoder_control *dc) &dc->out_audio_format, &player.play_audio_format, music_buffer_size(player_buffer) - - pc.buffered_before_play); + pc->buffered_before_play); if (player.cross_fade_chunks > 0) { player.xfade = XFADE_ENABLED; player.cross_fading = false; @@ -941,10 +954,10 @@ static void do_play(struct decoder_control *dc) } if (player.paused) { - player_lock(); + player_lock(pc); - if (pc.command == PLAYER_COMMAND_NONE) - player_wait(); + if (pc->command == PLAYER_COMMAND_NONE) + player_wait(pc); continue; } else if (!music_pipe_empty(player.pipe)) { /* at least one music chunk is ready - send it @@ -981,7 +994,7 @@ static void do_play(struct decoder_control *dc) break; } - player_lock(); + player_lock(pc); } player_dc_stop(&player); @@ -992,113 +1005,115 @@ static void do_play(struct decoder_control *dc) if (player.cross_fade_tag != NULL) tag_free(player.cross_fade_tag); - player_lock(); + player_lock(pc); if (player.queued) { - assert(pc.next_song != NULL); - pc.next_song = NULL; + assert(pc->next_song != NULL); + pc->next_song = NULL; } - pc.state = PLAYER_STATE_STOP; + pc->state = PLAYER_STATE_STOP; - player_unlock(); + player_unlock(pc); event_pipe_emit(PIPE_EVENT_PLAYLIST); - player_lock(); + player_lock(pc); } -static gpointer player_task(G_GNUC_UNUSED gpointer arg) +static gpointer +player_task(gpointer arg) { - struct decoder_control dc; + struct player_control *pc = arg; - dc_init(&dc); - decoder_thread_start(&dc); + struct decoder_control *dc = dc_new(pc->cond); + decoder_thread_start(dc); - player_buffer = music_buffer_new(pc.buffer_chunks); + player_buffer = music_buffer_new(pc->buffer_chunks); - player_lock(); + player_lock(pc); while (1) { - switch (pc.command) { + switch (pc->command) { case PLAYER_COMMAND_QUEUE: - assert(pc.next_song != NULL); + assert(pc->next_song != NULL); - do_play(&dc); + do_play(pc, dc); break; case PLAYER_COMMAND_STOP: - player_unlock(); + player_unlock(pc); audio_output_all_cancel(); - player_lock(); + player_lock(pc); /* fall through */ case PLAYER_COMMAND_SEEK: case PLAYER_COMMAND_PAUSE: - pc.next_song = NULL; - player_command_finished_locked(); + pc->next_song = NULL; + player_command_finished_locked(pc); break; case PLAYER_COMMAND_CLOSE_AUDIO: - player_unlock(); + player_unlock(pc); audio_output_all_release(); - player_lock(); - player_command_finished_locked(); + player_lock(pc); + player_command_finished_locked(pc); #ifndef NDEBUG /* in the DEBUG build, check for leaked music_chunk objects by freeing the music_buffer */ music_buffer_free(player_buffer); - player_buffer = music_buffer_new(pc.buffer_chunks); + player_buffer = music_buffer_new(pc->buffer_chunks); #endif break; case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(); + player_unlock(pc); audio_output_all_enable_disable(); - player_lock(); - player_command_finished_locked(); + player_lock(pc); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_EXIT: - player_unlock(); + player_unlock(pc); - dc_quit(&dc); - dc_deinit(&dc); + dc_quit(dc); + dc_free(dc); audio_output_all_close(); music_buffer_free(player_buffer); - player_command_finished(); + player_command_finished(pc); return NULL; case PLAYER_COMMAND_CANCEL: - pc.next_song = NULL; - player_command_finished_locked(); + pc->next_song = NULL; + player_command_finished_locked(pc); break; case PLAYER_COMMAND_REFRESH: /* no-op when not playing */ - player_command_finished_locked(); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_NONE: - player_wait(); + player_wait(pc); break; } } } -void player_create(void) +void +player_create(struct player_control *pc) { - assert(pc.thread == NULL); + assert(pc->thread == NULL); GError *e = NULL; - pc.thread = g_thread_create(player_task, NULL, true, &e); - if (pc.thread == NULL) + pc->thread = g_thread_create(player_task, pc, true, &e); + if (pc->thread == NULL) MPD_ERROR("Failed to spawn player task: %s", e->message); } diff --git a/src/player_thread.h b/src/player_thread.h index e645b1d09..7373eb438 100644 --- a/src/player_thread.h +++ b/src/player_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -37,6 +37,9 @@ #ifndef MPD_PLAYER_THREAD_H #define MPD_PLAYER_THREAD_H -void player_create(void); +struct player_control; + +void +player_create(struct player_control *pc); #endif diff --git a/src/playlist.c b/src/playlist.c index 4a1e54814..0c9eea92d 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -75,7 +75,8 @@ playlist_finish(struct playlist *playlist) * Queue a song, addressed by its order number. */ static void -playlist_queue_song_order(struct playlist *playlist, unsigned order) +playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, + unsigned order) { struct song *song; char *uri; @@ -89,16 +90,16 @@ playlist_queue_song_order(struct playlist *playlist, unsigned order) g_debug("queue song %i:\"%s\"", playlist->queued, uri); g_free(uri); - pc_enqueue_song(song); + pc_enqueue_song(pc, song); } /** * Called if the player thread has started playing the "queued" song. */ static void -playlist_song_started(struct playlist *playlist) +playlist_song_started(struct playlist *playlist, struct player_control *pc) { - assert(pc.next_song == NULL); + assert(pc->next_song == NULL); assert(playlist->queued >= -1); /* queued song has started: copy queued to current, @@ -110,11 +111,13 @@ playlist_song_started(struct playlist *playlist) /* Pause if we are in single mode. */ if(playlist->queue.single && !playlist->queue.repeat) { - pc_set_pause(true); + pc_set_pause(pc, true); } if(playlist->queue.consume) - playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); + playlist_delete(playlist, pc, + queue_order_to_position(&playlist->queue, + current)); idle_add(IDLE_PLAYER); } @@ -129,7 +132,9 @@ playlist_get_queued_song(struct playlist *playlist) } void -playlist_update_queued_song(struct playlist *playlist, const struct song *prev) +playlist_update_queued_song(struct playlist *playlist, + struct player_control *pc, + const struct song *prev) { int next_order; const struct song *next_song; @@ -170,20 +175,21 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev) if (prev != NULL && next_song != prev) { /* clear the currently queued song */ - pc_cancel(); + pc_cancel(pc); playlist->queued = -1; } if (next_order >= 0) { if (next_song != prev) - playlist_queue_song_order(playlist, next_order); + playlist_queue_song_order(playlist, pc, next_order); else playlist->queued = next_order; } } void -playlist_play_order(struct playlist *playlist, int orderNum) +playlist_play_order(struct playlist *playlist, struct player_control *pc, + int orderNum) { struct song *song; char *uri; @@ -197,46 +203,46 @@ playlist_play_order(struct playlist *playlist, int orderNum) g_debug("play %i:\"%s\"", orderNum, uri); g_free(uri); - pc_play(song); + pc_play(pc, song); playlist->current = orderNum; } static void -playlist_resume_playback(struct playlist *playlist); +playlist_resume_playback(struct playlist *playlist, struct player_control *pc); /** * This is the "PLAYLIST" event handler. It is invoked by the player * thread whenever it requests a new queued song, or when it exits. */ void -playlist_sync(struct playlist *playlist) +playlist_sync(struct playlist *playlist, struct player_control *pc) { if (!playlist->playing) /* this event has reached us out of sync: we aren't playing anymore; ignore the event */ return; - player_lock(); - enum player_state pc_state = pc_get_state(); - const struct song *pc_next_song = pc.next_song; - player_unlock(); + player_lock(pc); + enum player_state pc_state = pc_get_state(pc); + const struct song *pc_next_song = pc->next_song; + player_unlock(pc); if (pc_state == PLAYER_STATE_STOP) /* the player thread has stopped: check if playback should be restarted with the next song. That can happen if the playlist isn't filling the queue fast enough */ - playlist_resume_playback(playlist); + playlist_resume_playback(playlist, pc); else { /* check if the player thread has already started playing the queued song */ if (pc_next_song == NULL && playlist->queued != -1) - playlist_song_started(playlist); + playlist_song_started(playlist, pc); /* make sure the queued song is always set (if possible) */ - if (pc.next_song == NULL && playlist->queued < 0) - playlist_update_queued_song(playlist, NULL); + if (pc->next_song == NULL && playlist->queued < 0) + playlist_update_queued_song(playlist, pc, NULL); } } @@ -245,14 +251,14 @@ playlist_sync(struct playlist *playlist) * decide whether to re-start playback */ static void -playlist_resume_playback(struct playlist *playlist) +playlist_resume_playback(struct playlist *playlist, struct player_control *pc) { enum player_error error; assert(playlist->playing); - assert(pc_get_state() == PLAYER_STATE_STOP); + assert(pc_get_state(pc) == PLAYER_STATE_STOP); - error = pc_get_error(); + error = pc_get_error(pc); if (error == PLAYER_ERROR_NOERROR) playlist->error_count = 0; else @@ -263,10 +269,10 @@ playlist_resume_playback(struct playlist *playlist) playlist->error_count >= queue_length(&playlist->queue)) /* too many errors, or critical error: stop playback */ - playlist_stop(playlist); + playlist_stop(playlist, pc); else /* continue playback at the next song */ - playlist_next(playlist); + playlist_next(playlist, pc); } bool @@ -294,7 +300,8 @@ playlist_get_consume(const struct playlist *playlist) } void -playlist_set_repeat(struct playlist *playlist, bool status) +playlist_set_repeat(struct playlist *playlist, struct player_control *pc, + bool status) { if (status == playlist->queue.repeat) return; @@ -303,7 +310,7 @@ playlist_set_repeat(struct playlist *playlist, bool status) /* if the last song is currently being played, the "next song" might change when repeat mode is toggled */ - playlist_update_queued_song(playlist, + playlist_update_queued_song(playlist, pc, playlist_get_queued_song(playlist)); idle_add(IDLE_OPTIONS); @@ -321,7 +328,8 @@ playlist_order(struct playlist *playlist) } void -playlist_set_single(struct playlist *playlist, bool status) +playlist_set_single(struct playlist *playlist, struct player_control *pc, + bool status) { if (status == playlist->queue.single) return; @@ -330,7 +338,7 @@ playlist_set_single(struct playlist *playlist, bool status) /* if the last song is currently being played, the "next song" might change when single mode is toggled */ - playlist_update_queued_song(playlist, + playlist_update_queued_song(playlist, pc, playlist_get_queued_song(playlist)); idle_add(IDLE_OPTIONS); @@ -347,7 +355,8 @@ playlist_set_consume(struct playlist *playlist, bool status) } void -playlist_set_random(struct playlist *playlist, bool status) +playlist_set_random(struct playlist *playlist, struct player_control *pc, + bool status) { const struct song *queued; @@ -384,7 +393,7 @@ playlist_set_random(struct playlist *playlist, bool status) } else playlist_order(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); idle_add(IDLE_OPTIONS); } diff --git a/src/playlist.h b/src/playlist.h index 3ba90ff91..569db7bc3 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,6 +26,8 @@ #define PLAYLIST_COMMENT '#' +struct player_control; + enum playlist_result { PLAYLIST_RESULT_SUCCESS, PLAYLIST_RESULT_ERRNO, @@ -111,7 +113,7 @@ playlist_get_queue(const struct playlist *playlist) } void -playlist_clear(struct playlist *playlist); +playlist_clear(struct playlist *playlist, struct player_control *pc); #ifndef WIN32 /** @@ -119,20 +121,21 @@ playlist_clear(struct playlist *playlist); * but only if the file's owner is equal to the specified uid. */ enum playlist_result -playlist_append_file(struct playlist *playlist, const char *path, int uid, - unsigned *added_id); +playlist_append_file(struct playlist *playlist, struct player_control *pc, + const char *path, int uid, unsigned *added_id); #endif enum playlist_result -playlist_append_uri(struct playlist *playlist, const char *file, - unsigned *added_id); +playlist_append_uri(struct playlist *playlist, struct player_control *pc, + const char *file, unsigned *added_id); enum playlist_result -playlist_append_song(struct playlist *playlist, +playlist_append_song(struct playlist *playlist, struct player_control *pc, struct song *song, unsigned *added_id); enum playlist_result -playlist_delete(struct playlist *playlist, unsigned song); +playlist_delete(struct playlist *playlist, struct player_control *pc, + unsigned song); /** * Deletes a range of songs from the playlist. @@ -141,64 +144,86 @@ playlist_delete(struct playlist *playlist, unsigned song); * @param end the position after the last song to delete */ enum playlist_result -playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end); +playlist_delete_range(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end); enum playlist_result -playlist_delete_id(struct playlist *playlist, unsigned song); +playlist_delete_id(struct playlist *playlist, struct player_control *pc, + unsigned song); void -playlist_stop(struct playlist *playlist); +playlist_stop(struct playlist *playlist, struct player_control *pc); enum playlist_result -playlist_play(struct playlist *playlist, int song); +playlist_play(struct playlist *playlist, struct player_control *pc, + int song); enum playlist_result -playlist_play_id(struct playlist *playlist, int song); +playlist_play_id(struct playlist *playlist, struct player_control *pc, + int song); void -playlist_next(struct playlist *playlist); +playlist_next(struct playlist *playlist, struct player_control *pc); void -playlist_sync(struct playlist *playlist); +playlist_sync(struct playlist *playlist, struct player_control *pc); void -playlist_previous(struct playlist *playlist); +playlist_previous(struct playlist *playlist, struct player_control *pc); void -playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end); +playlist_shuffle(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end); void -playlist_delete_song(struct playlist *playlist, const struct song *song); +playlist_delete_song(struct playlist *playlist, struct player_control *pc, + const struct song *song); + +enum playlist_result +playlist_move_range(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end, int to); + +enum playlist_result +playlist_move_id(struct playlist *playlist, struct player_control *pc, + unsigned id, int to); enum playlist_result -playlist_move_range(struct playlist *playlist, unsigned start, unsigned end, int to); +playlist_swap_songs(struct playlist *playlist, struct player_control *pc, + unsigned song1, unsigned song2); enum playlist_result -playlist_move_id(struct playlist *playlist, unsigned id, int to); +playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, + unsigned id1, unsigned id2); enum playlist_result -playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2); +playlist_set_priority(struct playlist *playlist, struct player_control *pc, + unsigned start_position, unsigned end_position, + uint8_t priority); enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2); +playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, + unsigned song_id, uint8_t priority); bool playlist_get_repeat(const struct playlist *playlist); void -playlist_set_repeat(struct playlist *playlist, bool status); +playlist_set_repeat(struct playlist *playlist, struct player_control *pc, + bool status); bool playlist_get_random(const struct playlist *playlist); void -playlist_set_random(struct playlist *playlist, bool status); +playlist_set_random(struct playlist *playlist, struct player_control *pc, + bool status); bool playlist_get_single(const struct playlist *playlist); void -playlist_set_single(struct playlist *playlist, bool status); +playlist_set_single(struct playlist *playlist, struct player_control *pc, + bool status); bool playlist_get_consume(const struct playlist *playlist); @@ -222,10 +247,11 @@ unsigned long playlist_get_version(const struct playlist *playlist); enum playlist_result -playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time); +playlist_seek_song(struct playlist *playlist, struct player_control *pc, + unsigned song, float seek_time); enum playlist_result -playlist_seek_song_id(struct playlist *playlist, +playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, unsigned id, float seek_time); void diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c index 39513e710..b711f83f3 100644 --- a/src/playlist/asx_playlist_plugin.c +++ b/src/playlist/asx_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h index 7ce91aa41..6c01c1209 100644 --- a/src/playlist/asx_playlist_plugin.h +++ b/src/playlist/asx_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/cue_playlist_plugin.c b/src/playlist/cue_playlist_plugin.c index b22712bc7..e3619a284 100644 --- a/src/playlist/cue_playlist_plugin.c +++ b/src/playlist/cue_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/cue_playlist_plugin.h b/src/playlist/cue_playlist_plugin.h index c89ec55c5..c02e2235a 100644 --- a/src/playlist/cue_playlist_plugin.h +++ b/src/playlist/cue_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/despotify_playlist_plugin.c b/src/playlist/despotify_playlist_plugin.c new file mode 100644 index 000000000..39448e01f --- /dev/null +++ b/src/playlist/despotify_playlist_plugin.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "config.h" +#include "playlist/despotify_playlist_plugin.h" +#include "playlist_plugin.h" +#include "playlist_list.h" +#include "conf.h" +#include "uri.h" +#include "tag.h" +#include "song.h" +#include "input_stream.h" +#include "glib_compat.h" +#include "despotify_utils.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <despotify.h> + +struct despotify_playlist { + struct playlist_provider base; + + struct despotify_session *session; + GSList *list; +}; + +static void +add_song(struct despotify_playlist *ctx, struct ds_track *track) +{ + const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; + struct song *song; + char uri[128]; + char *ds_uri; + + /* Create a spt://... URI for MPD */ + g_snprintf(uri, sizeof(uri), "%s://", dsp_scheme); + ds_uri = uri + strlen(dsp_scheme) + 3; + + if (despotify_track_to_uri(track, ds_uri) != ds_uri) { + /* Should never really fail, but let's be sure */ + g_debug("Can't add track %s\n", track->title); + return; + } + + song = song_remote_new(uri); + song->tag = mpd_despotify_tag_from_track(track); + + ctx->list = g_slist_prepend(ctx->list, song); +} + +static bool +parse_track(struct despotify_playlist *ctx, + struct ds_link *link) +{ + struct ds_track *track; + + track = despotify_link_get_track(ctx->session, link); + if (!track) + return false; + add_song(ctx, track); + + return true; +} + +static bool +parse_playlist(struct despotify_playlist *ctx, + struct ds_link *link) +{ + struct ds_playlist *playlist; + struct ds_track *track; + + playlist = despotify_link_get_playlist(ctx->session, link); + if (!playlist) + return false; + + for (track = playlist->tracks; track; track = track->next) + add_song(ctx, track); + + return true; +} + +static bool +despotify_playlist_init(G_GNUC_UNUSED const struct config_param *param) +{ + return true; +} + +static void +despotify_playlist_finish(void) +{ +} + + +static struct playlist_provider * +despotify_playlist_open_uri(const char *url) +{ + struct despotify_playlist *ctx; + struct despotify_session *session; + struct ds_link *link; + bool parse_result; + + session = mpd_despotify_get_session(); + if (!session) + goto clean_none; + + /* Get link without spt:// */ + link = despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3); + if (!link) { + g_debug("Can't find %s\n", url); + goto clean_none; + } + + ctx = g_new(struct despotify_playlist, 1); + + ctx->list = NULL; + ctx->session = session; + playlist_provider_init(&ctx->base, &despotify_playlist_plugin); + + switch (link->type) + { + case LINK_TYPE_TRACK: + parse_result = parse_track(ctx, link); + break; + case LINK_TYPE_PLAYLIST: + parse_result = parse_playlist(ctx, link); + break; + default: + parse_result = false; + break; + } + despotify_free_link(link); + if (!parse_result) + goto clean_playlist; + + ctx->list = g_slist_reverse(ctx->list); + + return &ctx->base; + +clean_playlist: + g_slist_free(ctx->list); +clean_none: + + return NULL; +} + +static void +track_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = (struct song *)data; + + song_free(song); +} + +static void +despotify_playlist_close(struct playlist_provider *_playlist) +{ + struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist; + + g_slist_foreach(ctx->list, track_free_callback, NULL); + g_slist_free(ctx->list); + + g_free(ctx); +} + + +static struct song * +despotify_playlist_read(struct playlist_provider *_playlist) +{ + struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist; + struct song *out; + + if (!ctx->list) + return NULL; + + /* Remove the current track */ + out = ctx->list->data; + ctx->list = g_slist_remove(ctx->list, out); + + return out; +} + + +static const char *const despotify_schemes[] = { + "spt", + NULL +}; + +const struct playlist_plugin despotify_playlist_plugin = { + .name = "despotify", + + .init = despotify_playlist_init, + .finish = despotify_playlist_finish, + .open_uri = despotify_playlist_open_uri, + .read = despotify_playlist_read, + .close = despotify_playlist_close, + + .schemes = despotify_schemes, +}; diff --git a/src/playlist/despotify_playlist_plugin.h b/src/playlist/despotify_playlist_plugin.h new file mode 100644 index 000000000..f8ee20de0 --- /dev/null +++ b/src/playlist/despotify_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin despotify_playlist_plugin; + +#endif diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c index 9a04aa066..19be8d1c4 100644 --- a/src/playlist/extm3u_playlist_plugin.c +++ b/src/playlist/extm3u_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "uri.h" #include "song.h" #include "tag.h" +#include "string_util.h" #include <glib.h> @@ -89,7 +90,7 @@ extm3u_parse_tag(const char *line) /* 0 means unknown duration */ duration = 0; - name = g_strchug(endptr + 1); + name = strchug_fast_c(endptr + 1); if (*name == 0 && duration == 0) /* no information available; don't allocate a tag object */ diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h index fa726c5f6..5f611ac9c 100644 --- a/src/playlist/extm3u_playlist_plugin.h +++ b/src/playlist/extm3u_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/flac_playlist_plugin.c b/src/playlist/flac_playlist_plugin.c index 9d66fb331..8adf694ed 100644 --- a/src/playlist/flac_playlist_plugin.c +++ b/src/playlist/flac_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/flac_playlist_plugin.h b/src/playlist/flac_playlist_plugin.h index 7b141264f..231d90e4a 100644 --- a/src/playlist/flac_playlist_plugin.h +++ b/src/playlist/flac_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c index afb3979d9..5c803cd99 100644 --- a/src/playlist/lastfm_playlist_plugin.c +++ b/src/playlist/lastfm_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -139,7 +139,7 @@ lastfm_get(const char *url) * Ini-style value fetcher. * @param response data through which to search. * @param name name of value to search for. - * @return value for param name in param reponse or NULL on error. Free with g_free. + * @return value for param name in param response or NULL on error. Free with g_free. */ static char * lastfm_find(const char *response, const char *name) diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h index 363377c21..46a8b0caf 100644 --- a/src/playlist/lastfm_playlist_plugin.h +++ b/src/playlist/lastfm_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c index 221c27277..45b70d2b1 100644 --- a/src/playlist/m3u_playlist_plugin.c +++ b/src/playlist/m3u_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h index 98dcc4729..3890a5fc2 100644 --- a/src/playlist/m3u_playlist_plugin.h +++ b/src/playlist/m3u_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c index 2a36f12f5..937f02791 100644 --- a/src/playlist/pls_playlist_plugin.c +++ b/src/playlist/pls_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h index c3bcf3f05..d03435f6d 100644 --- a/src/playlist/pls_playlist_plugin.h +++ b/src/playlist/pls_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/rss_playlist_plugin.c b/src/playlist/rss_playlist_plugin.c index b5787bb68..9ce3c6abe 100644 --- a/src/playlist/rss_playlist_plugin.c +++ b/src/playlist/rss_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/rss_playlist_plugin.h b/src/playlist/rss_playlist_plugin.h index d8992f2e5..3b376de79 100644 --- a/src/playlist/rss_playlist_plugin.h +++ b/src/playlist/rss_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c index 50f6bd1e7..89a4a08a4 100644 --- a/src/playlist/xspf_playlist_plugin.c +++ b/src/playlist/xspf_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h index ea832207d..4636d7e83 100644 --- a/src/playlist/xspf_playlist_plugin.h +++ b/src/playlist/xspf_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_any.c b/src/playlist_any.c index 39e21b178..bed12206a 100644 --- a/src/playlist_any.c +++ b/src/playlist_any.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_any.h b/src/playlist_any.h index 6fed97d15..7c13df718 100644 --- a/src/playlist_any.h +++ b/src/playlist_any.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_control.c b/src/playlist_control.c index 76066d274..998294845 100644 --- a/src/playlist_control.c +++ b/src/playlist_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,7 +32,8 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "playlist" -void playlist_stop(struct playlist *playlist) +void +playlist_stop(struct playlist *playlist, struct player_control *pc) { if (!playlist->playing) return; @@ -40,7 +41,7 @@ void playlist_stop(struct playlist *playlist) assert(playlist->current >= 0); g_debug("stop"); - pc_stop(); + pc_stop(pc); playlist->queued = -1; playlist->playing = false; @@ -62,11 +63,13 @@ void playlist_stop(struct playlist *playlist) } } -enum playlist_result playlist_play(struct playlist *playlist, int song) +enum playlist_result +playlist_play(struct playlist *playlist, struct player_control *pc, + int song) { unsigned i = song; - pc_clear_error(); + pc_clear_error(pc); if (song == -1) { /* play any song ("current" song, or the first song */ @@ -77,7 +80,7 @@ enum playlist_result playlist_play(struct playlist *playlist, int song) if (playlist->playing) { /* already playing: unpause playback, just in case it was paused, and return */ - pc_set_pause(false); + pc_set_pause(pc, false); return PLAYLIST_RESULT_SUCCESS; } @@ -109,28 +112,29 @@ enum playlist_result playlist_play(struct playlist *playlist, int song) playlist->stop_on_error = false; playlist->error_count = 0; - playlist_play_order(playlist, i); + playlist_play_order(playlist, pc, i); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_play_id(struct playlist *playlist, int id) +playlist_play_id(struct playlist *playlist, struct player_control *pc, + int id) { int song; if (id == -1) { - return playlist_play(playlist, id); + return playlist_play(playlist, pc, id); } song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_play(playlist, song); + return playlist_play(playlist, pc, song); } void -playlist_next(struct playlist *playlist) +playlist_next(struct playlist *playlist, struct player_control *pc) { int next_order; int current; @@ -149,7 +153,7 @@ playlist_next(struct playlist *playlist) next_order = queue_next_order(&playlist->queue, playlist->current); if (next_order < 0) { /* no song after this one: stop playback */ - playlist_stop(playlist); + playlist_stop(playlist, pc); /* reset "current song" */ playlist->current = -1; @@ -170,15 +174,18 @@ playlist_next(struct playlist *playlist) discard them anyway */ } - playlist_play_order(playlist, next_order); + playlist_play_order(playlist, pc, next_order); } /* Consume mode removes each played songs. */ if(playlist->queue.consume) - playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); + playlist_delete(playlist, pc, + queue_order_to_position(&playlist->queue, + current)); } -void playlist_previous(struct playlist *playlist) +void +playlist_previous(struct playlist *playlist, struct player_control *pc) { if (!playlist->playing) return; @@ -187,21 +194,22 @@ void playlist_previous(struct playlist *playlist) if (playlist->current > 0) { /* play the preceding song */ - playlist_play_order(playlist, + playlist_play_order(playlist, pc, playlist->current - 1); } else if (playlist->queue.repeat) { /* play the last song in "repeat" mode */ - playlist_play_order(playlist, + playlist_play_order(playlist, pc, queue_length(&playlist->queue) - 1); } else { /* re-start playing the current song if it's the first one */ - playlist_play_order(playlist, playlist->current); + playlist_play_order(playlist, pc, playlist->current); } } enum playlist_result -playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time) +playlist_seek_song(struct playlist *playlist, struct player_control *pc, + unsigned song, float seek_time) { const struct song *queued; unsigned i; @@ -217,7 +225,7 @@ playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time) else i = song; - pc_clear_error(); + pc_clear_error(pc); playlist->stop_on_error = true; playlist->error_count = 0; @@ -231,25 +239,26 @@ playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time) queued = NULL; } - success = pc_seek(queue_get_order(&playlist->queue, i), seek_time); + success = pc_seek(pc, queue_get_order(&playlist->queue, i), seek_time); if (!success) { - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); return PLAYLIST_RESULT_NOT_PLAYING; } playlist->queued = -1; - playlist_update_queued_song(playlist, NULL); + playlist_update_queued_song(playlist, pc, NULL); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time) +playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, + unsigned id, float seek_time) { int song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_seek_song(playlist, song, seek_time); + return playlist_seek_song(playlist, pc, song, seek_time); } diff --git a/src/playlist_database.c b/src/playlist_database.c index 0a8a6f139..2ad913d00 100644 --- a/src/playlist_database.c +++ b/src/playlist_database.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ #include "playlist_database.h" #include "playlist_vector.h" #include "text_file.h" +#include "string_util.h" #include <string.h> #include <stdlib.h> @@ -62,7 +63,7 @@ playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name, } *colon++ = 0; - value = g_strchug(colon); + value = strchug_fast_c(colon); if (strcmp(line, "mtime") == 0) pm.mtime = strtol(value, NULL, 10); diff --git a/src/playlist_database.h b/src/playlist_database.h index 7e114abdd..f80ebdaff 100644 --- a/src/playlist_database.h +++ b/src/playlist_database.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_edit.c b/src/playlist_edit.c index c54b72750..92c3d44b0 100644 --- a/src/playlist_edit.c +++ b/src/playlist_edit.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -43,16 +43,17 @@ static void playlist_increment_version(struct playlist *playlist) idle_add(IDLE_PLAYLIST); } -void playlist_clear(struct playlist *playlist) +void +playlist_clear(struct playlist *playlist, struct player_control *pc) { - playlist_stop(playlist); + playlist_stop(playlist, pc); /* make sure there are no references to allocated songs anymore */ for (unsigned i = 0; i < queue_length(&playlist->queue); i++) { const struct song *song = queue_get(&playlist->queue, i); if (!song_in_database(song)) - pc_song_deleted(song); + pc_song_deleted(pc, song); } queue_clear(&playlist->queue); @@ -64,8 +65,8 @@ void playlist_clear(struct playlist *playlist) #ifndef WIN32 enum playlist_result -playlist_append_file(struct playlist *playlist, const char *path, int uid, - unsigned *added_id) +playlist_append_file(struct playlist *playlist, struct player_control *pc, + const char *path, int uid, unsigned *added_id) { int ret; struct stat st; @@ -87,12 +88,12 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid, if (song == NULL) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_append_song(playlist, song, added_id); + return playlist_append_song(playlist, pc, song, added_id); } #endif enum playlist_result -playlist_append_song(struct playlist *playlist, +playlist_append_song(struct playlist *playlist, struct player_control *pc, struct song *song, unsigned *added_id) { const struct song *queued; @@ -121,7 +122,7 @@ playlist_append_song(struct playlist *playlist, playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); if (added_id) *added_id = id; @@ -145,8 +146,8 @@ song_by_uri(const char *uri) } enum playlist_result -playlist_append_uri(struct playlist *playlist, const char *uri, - unsigned *added_id) +playlist_append_uri(struct playlist *playlist, struct player_control *pc, + const char *uri, unsigned *added_id) { struct song *song; @@ -156,11 +157,12 @@ playlist_append_uri(struct playlist *playlist, const char *uri, if (song == NULL) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_append_song(playlist, song, added_id); + return playlist_append_song(playlist, pc, song, added_id); } enum playlist_result -playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2) +playlist_swap_songs(struct playlist *playlist, struct player_control *pc, + unsigned song1, unsigned song2) { const struct song *queued; @@ -192,13 +194,14 @@ playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2) playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2) +playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, + unsigned id1, unsigned id2) { int song1 = queue_id_to_position(&playlist->queue, id1); int song2 = queue_id_to_position(&playlist->queue, id2); @@ -206,12 +209,67 @@ playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2) if (song1 < 0 || song2 < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_swap_songs(playlist, song1, song2); + return playlist_swap_songs(playlist, pc, song1, song2); +} + +enum playlist_result +playlist_set_priority(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end, + uint8_t priority) +{ + if (start >= queue_length(&playlist->queue)) + return PLAYLIST_RESULT_BAD_RANGE; + + if (end > queue_length(&playlist->queue)) + end = queue_length(&playlist->queue); + + if (start >= end) + return PLAYLIST_RESULT_SUCCESS; + + /* remember "current" and "queued" */ + + int current_position = playlist->current >= 0 + ? (int)queue_order_to_position(&playlist->queue, + playlist->current) + : -1; + + const struct song *queued = playlist_get_queued_song(playlist); + + /* apply the priority changes */ + + queue_set_priority_range(&playlist->queue, start, end, priority, + playlist->current); + + playlist_increment_version(playlist); + + /* restore "current" and choose a new "queued" */ + + if (current_position >= 0) + playlist->current = queue_position_to_order(&playlist->queue, + current_position); + + playlist_update_queued_song(playlist, pc, queued); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, + unsigned song_id, uint8_t priority) +{ + int song_position = queue_id_to_position(&playlist->queue, song_id); + if (song_position < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return playlist_set_priority(playlist, pc, + song_position, song_position + 1, + priority); + } static void -playlist_delete_internal(struct playlist *playlist, unsigned song, - const struct song **queued_p) +playlist_delete_internal(struct playlist *playlist, struct player_control *pc, + unsigned song, const struct song **queued_p) { unsigned songOrder; @@ -220,11 +278,11 @@ playlist_delete_internal(struct playlist *playlist, unsigned song, songOrder = queue_position_to_order(&playlist->queue, song); if (playlist->playing && playlist->current == (int)songOrder) { - bool paused = pc_get_state() == PLAYER_STATE_PAUSE; + bool paused = pc_get_state(pc) == PLAYER_STATE_PAUSE; /* the current song is going to be deleted: stop the player */ - pc_stop(); + pc_stop(pc); playlist->playing = false; /* see which song is going to be played instead */ @@ -236,11 +294,11 @@ playlist_delete_internal(struct playlist *playlist, unsigned song, if (playlist->current >= 0 && !paused) /* play the song after the deleted one */ - playlist_play_order(playlist, playlist->current); + playlist_play_order(playlist, pc, playlist->current); else /* no songs left to play, stop playback completely */ - playlist_stop(playlist); + playlist_stop(playlist, pc); *queued_p = NULL; } else if (playlist->current == (int)songOrder) @@ -251,7 +309,7 @@ playlist_delete_internal(struct playlist *playlist, unsigned song, /* now do it: remove the song */ if (!song_in_database(queue_get(&playlist->queue, song))) - pc_song_deleted(queue_get(&playlist->queue, song)); + pc_song_deleted(pc, queue_get(&playlist->queue, song)); queue_delete(&playlist->queue, song); @@ -263,7 +321,8 @@ playlist_delete_internal(struct playlist *playlist, unsigned song, } enum playlist_result -playlist_delete(struct playlist *playlist, unsigned song) +playlist_delete(struct playlist *playlist, struct player_control *pc, + unsigned song) { const struct song *queued; @@ -272,16 +331,17 @@ playlist_delete(struct playlist *playlist, unsigned song) queued = playlist_get_queued_song(playlist); - playlist_delete_internal(playlist, song, &queued); + playlist_delete_internal(playlist, pc, song, &queued); playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end) +playlist_delete_range(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end) { const struct song *queued; @@ -297,37 +357,39 @@ playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end) queued = playlist_get_queued_song(playlist); do { - playlist_delete_internal(playlist, --end, &queued); + playlist_delete_internal(playlist, pc, --end, &queued); } while (end != start); playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_delete_id(struct playlist *playlist, unsigned id) +playlist_delete_id(struct playlist *playlist, struct player_control *pc, + unsigned id) { int song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_delete(playlist, song); + return playlist_delete(playlist, pc, song); } void -playlist_delete_song(struct playlist *playlist, const struct song *song) +playlist_delete_song(struct playlist *playlist, struct player_control *pc, + const struct song *song) { for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i) if (song == queue_get(&playlist->queue, i)) - playlist_delete(playlist, i); + playlist_delete(playlist, pc, i); - pc_song_deleted(song); + pc_song_deleted(pc, song); } enum playlist_result -playlist_move_range(struct playlist *playlist, +playlist_move_range(struct playlist *playlist, struct player_control *pc, unsigned start, unsigned end, int to) { const struct song *queued; @@ -382,23 +444,25 @@ playlist_move_range(struct playlist *playlist, playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_move_id(struct playlist *playlist, unsigned id1, int to) +playlist_move_id(struct playlist *playlist, struct player_control *pc, + unsigned id1, int to) { int song = queue_id_to_position(&playlist->queue, id1); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_move_range(playlist, song, song+1, to); + return playlist_move_range(playlist, pc, song, song+1, to); } void -playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end) +playlist_shuffle(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end) { const struct song *queued; @@ -440,5 +504,5 @@ playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end) playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); } diff --git a/src/playlist_global.c b/src/playlist_global.c index 2833b62ed..650b88bb8 100644 --- a/src/playlist_global.c +++ b/src/playlist_global.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,6 +26,7 @@ #include "playlist.h" #include "playlist_state.h" #include "event_pipe.h" +#include "main.h" struct playlist g_playlist; @@ -38,7 +39,7 @@ playlist_tag_event(void) static void playlist_event(void) { - playlist_sync(&g_playlist); + playlist_sync(&g_playlist, global_player_control); } void diff --git a/src/playlist_internal.h b/src/playlist_internal.h index 9d205188f..81b175176 100644 --- a/src/playlist_internal.h +++ b/src/playlist_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,6 +27,8 @@ #include "playlist.h" +struct player_control; + /** * Returns the song object which is currently queued. Returns none if * there is none (yet?) or if MPD isn't playing. @@ -44,9 +46,11 @@ playlist_get_queued_song(struct playlist *playlist); */ void playlist_update_queued_song(struct playlist *playlist, + struct player_control *pc, const struct song *prev); void -playlist_play_order(struct playlist *playlist, int orderNum); +playlist_play_order(struct playlist *playlist, struct player_control *pc, + int orderNum); #endif diff --git a/src/playlist_list.c b/src/playlist_list.c index 019654bfc..04c3fa292 100644 --- a/src/playlist_list.c +++ b/src/playlist_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "playlist/m3u_playlist_plugin.h" #include "playlist/xspf_playlist_plugin.h" #include "playlist/lastfm_playlist_plugin.h" +#include "playlist/despotify_playlist_plugin.h" #include "playlist/pls_playlist_plugin.h" #include "playlist/asx_playlist_plugin.h" #include "playlist/rss_playlist_plugin.h" @@ -31,7 +32,7 @@ #include "playlist/flac_playlist_plugin.h" #include "input_stream.h" #include "uri.h" -#include "utils.h" +#include "string_util.h" #include "conf.h" #include "glib_compat.h" #include "mpd_error.h" @@ -47,6 +48,9 @@ static const struct playlist_plugin *const playlist_plugins[] = { &pls_playlist_plugin, &asx_playlist_plugin, &rss_playlist_plugin, +#ifdef ENABLE_DESPOTIFY + &despotify_playlist_plugin, +#endif #ifdef ENABLE_LASTFM &lastfm_playlist_plugin, #endif diff --git a/src/playlist_list.h b/src/playlist_list.h index 3710589a2..69eb26b17 100644 --- a/src/playlist_list.h +++ b/src/playlist_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_mapper.c b/src/playlist_mapper.c index 99b322073..824b40e0e 100644 --- a/src/playlist_mapper.c +++ b/src/playlist_mapper.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_mapper.h b/src/playlist_mapper.h index b98af1b13..ab8ba982c 100644 --- a/src/playlist_mapper.h +++ b/src/playlist_mapper.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h index 3d840573e..d7d7c7769 100644 --- a/src/playlist_plugin.h +++ b/src/playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_print.c b/src/playlist_print.c index 89ab2e5ab..041005a55 100644 --- a/src/playlist_print.c +++ b/src/playlist_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -153,7 +153,7 @@ playlist_provider_print(struct client *client, const char *uri, char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; while ((song = playlist_plugin_read(playlist)) != NULL) { - song = playlist_check_translate_song(song, base_uri); + song = playlist_check_translate_song(song, base_uri, false); if (song == NULL) continue; diff --git a/src/playlist_print.h b/src/playlist_print.h index b3a0446ed..7b520db93 100644 --- a/src/playlist_print.h +++ b/src/playlist_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_queue.c b/src/playlist_queue.c index 635e23a28..41df882b4 100644 --- a/src/playlist_queue.c +++ b/src/playlist_queue.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,18 +27,19 @@ enum playlist_result playlist_load_into_queue(const char *uri, struct playlist_provider *source, - struct playlist *dest) + struct playlist *dest, struct player_control *pc, + bool secure) { enum playlist_result result; struct song *song; char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; while ((song = playlist_plugin_read(source)) != NULL) { - song = playlist_check_translate_song(song, base_uri); + song = playlist_check_translate_song(song, base_uri, secure); if (song == NULL) continue; - result = playlist_append_song(dest, song, NULL); + result = playlist_append_song(dest, pc, song, NULL); if (result != PLAYLIST_RESULT_SUCCESS) { if (!song_in_database(song)) song_free(song); @@ -53,7 +54,9 @@ playlist_load_into_queue(const char *uri, struct playlist_provider *source, } enum playlist_result -playlist_open_into_queue(const char *uri, struct playlist *dest) +playlist_open_into_queue(const char *uri, + struct playlist *dest, struct player_control *pc, + bool secure) { struct input_stream *is; struct playlist_provider *playlist = playlist_open_any(uri, &is); @@ -61,7 +64,7 @@ playlist_open_into_queue(const char *uri, struct playlist *dest) return PLAYLIST_RESULT_NO_SUCH_LIST; enum playlist_result result = - playlist_load_into_queue(uri, playlist, dest); + playlist_load_into_queue(uri, playlist, dest, pc, secure); playlist_plugin_close(playlist); if (is != NULL) diff --git a/src/playlist_queue.h b/src/playlist_queue.h index 530d4b4be..0429f26b6 100644 --- a/src/playlist_queue.h +++ b/src/playlist_queue.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,6 +26,8 @@ #include "playlist.h" +#include <stdbool.h> + struct playlist_provider; struct playlist; @@ -38,14 +40,17 @@ struct playlist; */ enum playlist_result playlist_load_into_queue(const char *uri, struct playlist_provider *source, - struct playlist *dest); + struct playlist *dest, struct player_control *pc, + bool secure); /** * Opens a playlist with a playlist plugin and append to the specified * play queue. */ enum playlist_result -playlist_open_into_queue(const char *uri, struct playlist *dest); +playlist_open_into_queue(const char *uri, + struct playlist *dest, struct player_control *pc, + bool secure); #endif diff --git a/src/playlist_save.c b/src/playlist_save.c index 8ddc93ec9..04818173c 100644 --- a/src/playlist_save.c +++ b/src/playlist_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -109,7 +109,8 @@ spl_save_playlist(const char *name_utf8, const struct playlist *playlist) } enum playlist_result -playlist_load_spl(struct playlist *playlist, const char *name_utf8) +playlist_load_spl(struct playlist *playlist, struct player_control *pc, + const char *name_utf8) { GPtrArray *list; @@ -119,7 +120,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8) for (unsigned i = 0; i < list->len; ++i) { const char *temp = g_ptr_array_index(list, i); - if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { /* for windows compatibility, convert slashes */ char *temp2 = g_strdup(temp); char *p = temp2; @@ -128,7 +129,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8) *p = '/'; p++; } - if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { g_warning("can't add file \"%s\"", temp2); } g_free(temp2); diff --git a/src/playlist_save.h b/src/playlist_save.h index a0131cf7f..cf911b3e4 100644 --- a/src/playlist_save.h +++ b/src/playlist_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -49,6 +49,7 @@ spl_save_playlist(const char *name_utf8, const struct playlist *playlist); * playlist. */ enum playlist_result -playlist_load_spl(struct playlist *playlist, const char *name_utf8); +playlist_load_spl(struct playlist *playlist, struct player_control *pc, + const char *name_utf8); #endif diff --git a/src/playlist_song.c b/src/playlist_song.c index 1a543a0b8..8c966d549 100644 --- a/src/playlist_song.c +++ b/src/playlist_song.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -84,7 +84,8 @@ apply_song_metadata(struct song *dest, const struct song *src) } struct song * -playlist_check_translate_song(struct song *song, const char *base_uri) +playlist_check_translate_song(struct song *song, const char *base_uri, + bool secure) { struct song *dest; @@ -118,16 +119,17 @@ playlist_check_translate_song(struct song *song, const char *base_uri) ? map_uri_fs(base_uri) : map_directory_fs(db_get_root()); - if (prefix == NULL || !g_str_has_prefix(uri, prefix) || - uri[strlen(prefix)] != '/') { + if (prefix != NULL && g_str_has_prefix(uri, prefix) && + uri[strlen(prefix)] == '/') + uri += strlen(prefix) + 1; + else if (!secure) { /* local files must be relative to the music - directory */ + directory when "secure" is enabled */ g_free(prefix); song_free(song); return NULL; } - uri += strlen(prefix) + 1; g_free(prefix); } diff --git a/src/playlist_song.h b/src/playlist_song.h index 5a2e4c2b0..ea8786912 100644 --- a/src/playlist_song.h +++ b/src/playlist_song.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,12 +20,18 @@ #ifndef MPD_PLAYLIST_SONG_H #define MPD_PLAYLIST_SONG_H +#include <stdbool.h> + /** * Verifies the song, returns NULL if it is unsafe. Translate the * song to a new song object within the database, if it is a local * file. The old song object is freed. + * + * @param secure if true, then local files are only allowed if they + * are relative to base_uri */ struct song * -playlist_check_translate_song(struct song *song, const char *base_uri); +playlist_check_translate_song(struct song *song, const char *base_uri, + bool secure); #endif diff --git a/src/playlist_state.c b/src/playlist_state.c index bb9897e01..4aa2c2c92 100644 --- a/src/playlist_state.c +++ b/src/playlist_state.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ #include "queue_save.h" #include "path.h" #include "text_file.h" +#include "conf.h" #include <string.h> #include <stdlib.h> @@ -53,11 +54,12 @@ #define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX void -playlist_state_save(FILE *fp, const struct playlist *playlist) +playlist_state_save(FILE *fp, const struct playlist *playlist, + struct player_control *pc) { struct player_status player_status; - pc_get_status(&player_status); + pc_get_status(pc, &player_status); fputs(PLAYLIST_STATE_FILE_STATE, fp); @@ -89,10 +91,11 @@ playlist_state_save(FILE *fp, const struct playlist *playlist) fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist->queue.consume); fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", - (int)(pc_get_cross_fade())); - fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", pc_get_mixramp_db()); + (int)(pc_get_cross_fade(pc))); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", + pc_get_mixramp_db(pc)); fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", - pc_get_mixramp_delay()); + pc_get_mixramp_delay(pc)); fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); queue_save(fp, &playlist->queue); fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); @@ -123,11 +126,11 @@ playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist) bool playlist_state_restore(const char *line, FILE *fp, GString *buffer, - struct playlist *playlist) + struct playlist *playlist, struct player_control *pc) { int current = -1; int seek_time = 0; - int state = PLAYER_STATE_STOP; + enum player_state state = PLAYER_STATE_STOP; bool random_mode = false; if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) @@ -148,16 +151,16 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, if (strcmp (&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), "1") == 0) { - playlist_set_repeat(playlist, true); + playlist_set_repeat(playlist, pc, true); } else - playlist_set_repeat(playlist, false); + playlist_set_repeat(playlist, pc, false); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { if (strcmp (&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), "1") == 0) { - playlist_set_single(playlist, true); + playlist_set_single(playlist, pc, true); } else - playlist_set_single(playlist, false); + playlist_set_single(playlist, pc, false); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { if (strcmp (&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), @@ -166,11 +169,14 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, } else playlist_set_consume(playlist, false); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) { - pc_set_cross_fade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); + pc_set_cross_fade(pc, + atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { - pc_set_mixramp_db(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); + pc_set_mixramp_db(pc, + atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { - pc_set_mixramp_delay(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); + pc_set_mixramp_delay(pc, + atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { random_mode = strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), @@ -185,38 +191,46 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, } } - playlist_set_random(playlist, random_mode); + playlist_set_random(playlist, pc, random_mode); if (!queue_is_empty(&playlist->queue)) { if (!queue_valid_position(&playlist->queue, current)) current = 0; + if (state == PLAYER_STATE_PLAY && + config_get_bool("restore_paused", false)) + /* the user doesn't want MPD to auto-start + playback after startup; fall back to + "pause" */ + state = PLAYER_STATE_PAUSE; + /* enable all devices for the first time; this must be called here, after the audio output states were restored, before playback begins */ if (state != PLAYER_STATE_STOP) - pc_update_audio(); + pc_update_audio(pc); if (state == PLAYER_STATE_STOP /* && config_option */) playlist->current = current; else if (seek_time == 0) - playlist_play(playlist, current); + playlist_play(playlist, pc, current); else - playlist_seek_song(playlist, current, seek_time); + playlist_seek_song(playlist, pc, current, seek_time); if (state == PLAYER_STATE_PAUSE) - pc_pause(); + pc_pause(pc); } return true; } unsigned -playlist_state_get_hash(const struct playlist *playlist) +playlist_state_get_hash(const struct playlist *playlist, + struct player_control *pc) { struct player_status player_status; - pc_get_status(&player_status); + pc_get_status(pc, &player_status); return playlist->queue.version ^ (player_status.state != PLAYER_STATE_STOP @@ -226,7 +240,7 @@ playlist_state_get_hash(const struct playlist *playlist) ? (queue_order_to_position(&playlist->queue, playlist->current) << 16) : 0) ^ - ((int)pc_get_cross_fade() << 20) ^ + ((int)pc_get_cross_fade(pc) << 20) ^ (player_status.state << 24) ^ (playlist->queue.random << 27) ^ (playlist->queue.repeat << 28) ^ diff --git a/src/playlist_state.h b/src/playlist_state.h index 8ca3657f2..f67d01d2c 100644 --- a/src/playlist_state.h +++ b/src/playlist_state.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -30,13 +30,15 @@ #include <stdio.h> struct playlist; +struct player_control; void -playlist_state_save(FILE *fp, const struct playlist *playlist); +playlist_state_save(FILE *fp, const struct playlist *playlist, + struct player_control *pc); bool playlist_state_restore(const char *line, FILE *fp, GString *buffer, - struct playlist *playlist); + struct playlist *playlist, struct player_control *pc); /** * Generates a hash number for the current state of the playlist and @@ -45,6 +47,7 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, * be saved. */ unsigned -playlist_state_get_hash(const struct playlist *playlist); +playlist_state_get_hash(const struct playlist *playlist, + struct player_control *pc); #endif diff --git a/src/playlist_vector.c b/src/playlist_vector.c index 7c1765a98..cfbe8939e 100644 --- a/src/playlist_vector.c +++ b/src/playlist_vector.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_vector.h b/src/playlist_vector.h index 62861ae49..8aa19a4e0 100644 --- a/src/playlist_vector.h +++ b/src/playlist_vector.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/poison.h b/src/poison.h index 3654f2e9c..c95b5d005 100644 --- a/src/poison.h +++ b/src/poison.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/queue.c b/src/queue.c index dd0b48cb5..cd932875e 100644 --- a/src/queue.c +++ b/src/queue.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,8 @@ #include "queue.h" #include "song.h" +#include <stdlib.h> + /** * Generate a non-existing id number. */ @@ -104,6 +106,7 @@ queue_append(struct queue *queue, struct song *song) .song = song, .id = id, .version = queue->version, + .priority = 0, }; queue->order[queue->length] = queue->length; @@ -220,6 +223,30 @@ queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to) } } +/** + * Moves a song to a new position in the "order" list. + */ +static void +queue_move_order(struct queue *queue, unsigned from_order, unsigned to_order) +{ + assert(queue != NULL); + assert(from_order < queue->length); + assert(to_order <= queue->length); + + const unsigned from_position = + queue_order_to_position(queue, from_order); + + if (from_order < to_order) { + for (unsigned i = from_order; i < to_order; ++i) + queue->order[i] = queue->order[i + 1]; + } else { + for (unsigned i = from_order; i > to_order; --i) + queue->order[i] = queue->order[i - 1]; + } + + queue->order[to_order] = from_position; +} + void queue_delete(struct queue *queue, unsigned position) { @@ -308,15 +335,123 @@ queue_finish(struct queue *queue) g_rand_free(queue->rand); } -void -queue_shuffle_order(struct queue *queue) +static const struct queue_item * +queue_get_order_item_const(const struct queue *queue, unsigned order) +{ + assert(queue != NULL); + assert(order < queue->length); + + return &queue->items[queue->order[order]]; +} + +static uint8_t +queue_get_order_priority(const struct queue *queue, unsigned order) +{ + return queue_get_order_item_const(queue, order)->priority; +} + +static gint +queue_item_compare_order_priority(gconstpointer av, gconstpointer bv, + gpointer user_data) +{ + const struct queue *queue = user_data; + const unsigned *const ap = av; + const unsigned *const bp = bv; + assert(ap >= queue->order && ap < queue->order + queue->length); + assert(bp >= queue->order && bp < queue->order + queue->length); + uint8_t a = queue->items[*ap].priority; + uint8_t b = queue->items[*bp].priority; + + if (G_LIKELY(a == b)) + return 0; + else if (a > b) + return -1; + else + return 1; +} + +static void +queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) { + assert(queue != NULL); assert(queue->random); + assert(start <= end); + assert(end <= queue->length); - for (unsigned i = 0; i < queue->length; i++) + g_qsort_with_data(&queue->order[start], end - start, + sizeof(queue->order[0]), + queue_item_compare_order_priority, + queue); +} + +/** + * Shuffle the order of items in the specified range, ignoring their + * priorities. + */ +static void +queue_shuffle_order_range(struct queue *queue, unsigned start, unsigned end) +{ + assert(queue != NULL); + assert(queue->random); + assert(start <= end); + assert(end <= queue->length); + + for (unsigned i = start; i < end; ++i) queue_swap_order(queue, i, - g_rand_int_range(queue->rand, i, - queue->length)); + g_rand_int_range(queue->rand, i, end)); +} + +/** + * Sort the "order" of items by priority, and then shuffle each + * priority group. + */ +void +queue_shuffle_order_range_with_priority(struct queue *queue, + unsigned start, unsigned end) +{ + assert(queue != NULL); + assert(queue->random); + assert(start <= end); + assert(end <= queue->length); + + if (start == end) + return; + + /* first group the range by priority */ + queue_sort_order_by_priority(queue, start, end); + + /* now shuffle each priority group */ + unsigned group_start = start; + uint8_t group_priority = queue_get_order_priority(queue, start); + + for (unsigned i = start + 1; i < end; ++i) { + uint8_t priority = queue_get_order_priority(queue, i); + assert(priority <= group_priority); + + if (priority != group_priority) { + /* start of a new group - shuffle the one that + has just ended */ + queue_shuffle_order_range(queue, group_start, i); + group_start = i; + group_priority = priority; + } + } + + /* shuffle the last group */ + queue_shuffle_order_range(queue, group_start, end); +} + +void +queue_shuffle_order(struct queue *queue) +{ + queue_shuffle_order_range_with_priority(queue, 0, queue->length); +} + +static void +queue_shuffle_order_first(struct queue *queue, unsigned start, unsigned end) +{ + queue_swap_order(queue, start, + g_rand_int_range(queue->rand, start, end)); } void @@ -337,3 +472,132 @@ queue_shuffle_range(struct queue *queue, unsigned start, unsigned end) queue_swap(queue, i, ri); } } + +/** + * Find the first item that has this specified priority or higher. + */ +G_GNUC_PURE +static unsigned +queue_find_priority_order(const struct queue *queue, unsigned start_order, + uint8_t priority, unsigned exclude_order) +{ + assert(queue != NULL); + assert(queue->random); + assert(start_order <= queue->length); + + for (unsigned order = start_order; order < queue->length; ++order) { + const unsigned position = queue_order_to_position(queue, order); + const struct queue_item *item = &queue->items[position]; + if (item->priority <= priority && order != exclude_order) + return order; + } + + return queue->length; +} + +G_GNUC_PURE +static unsigned +queue_count_same_priority(const struct queue *queue, unsigned start_order, + uint8_t priority) +{ + assert(queue != NULL); + assert(queue->random); + assert(start_order <= queue->length); + + for (unsigned order = start_order; order < queue->length; ++order) { + const unsigned position = queue_order_to_position(queue, order); + const struct queue_item *item = &queue->items[position]; + if (item->priority != priority) + return order - start_order; + } + + return queue->length - start_order; +} + +bool +queue_set_priority(struct queue *queue, unsigned position, uint8_t priority, + int after_order) +{ + assert(queue != NULL); + assert(position < queue->length); + + struct queue_item *item = &queue->items[position]; + uint8_t old_priority = item->priority; + if (old_priority == priority) + return false; + + item->version = queue->version; + item->priority = priority; + + if (!queue->random) + /* don't reorder if not in random mode */ + return true; + + unsigned order = queue_position_to_order(queue, position); + if (after_order >= 0) { + if (order == (unsigned)after_order) + /* don't reorder the current song */ + return true; + + if (order < (unsigned)after_order) { + /* the specified song has been played already + - enqueue it only if its priority has just + become bigger than the current one's */ + + const unsigned after_position = + queue_order_to_position(queue, after_order); + const struct queue_item *after_item = + &queue->items[after_position]; + if (old_priority > after_item->priority || + priority <= after_item->priority) + /* priority hasn't become bigger */ + return true; + } + } + + /* move the item to the beginning of the priority group (or + create a new priority group) */ + + const unsigned before_order = + queue_find_priority_order(queue, after_order + 1, priority, + order); + const unsigned new_order = before_order > order + ? before_order - 1 + : before_order; + queue_move_order(queue, order, new_order); + + /* shuffle the song within that priority group */ + + const unsigned priority_count = + queue_count_same_priority(queue, new_order, priority); + assert(priority_count >= 1); + queue_shuffle_order_first(queue, new_order, + new_order + priority_count); + + return true; +} + +bool +queue_set_priority_range(struct queue *queue, + unsigned start_position, unsigned end_position, + uint8_t priority, int after_order) +{ + assert(queue != NULL); + assert(start_position <= end_position); + assert(end_position <= queue->length); + + bool modified = false; + int after_position = after_order >= 0 + ? (int)queue_order_to_position(queue, after_order) + : -1; + for (unsigned i = start_position; i < end_position; ++i) { + after_order = after_position >= 0 + ? (int)queue_position_to_order(queue, after_position) + : -1; + + modified |= queue_set_priority(queue, i, priority, + after_order); + } + + return modified; +} diff --git a/src/queue.h b/src/queue.h index 05eeafa22..5cb5c196b 100644 --- a/src/queue.h +++ b/src/queue.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -46,6 +46,13 @@ struct queue_item { /** when was this item last changed? */ uint32_t version; + + /** + * The priority of this item, between 0 and 255. High + * priority value means that this song gets played first in + * "random" mode. + */ + uint8_t priority; }; /** @@ -181,6 +188,15 @@ queue_position_to_order(const struct queue *queue, unsigned position) } } +G_GNUC_PURE +static inline uint8_t +queue_get_priority_at_position(const struct queue *queue, unsigned position) +{ + assert(position < queue->length); + + return queue->items[position].priority; +} + /** * Returns the song at the specified position. */ @@ -320,6 +336,14 @@ queue_restore_order(struct queue *queue) } /** + * Shuffle the order of items in the specified range, taking their + * priorities into account. + */ +void +queue_shuffle_order_range_with_priority(struct queue *queue, + unsigned start, unsigned end); + +/** * Shuffles the virtual order of songs, but does not move them * physically. This is used in random mode. */ @@ -341,4 +365,13 @@ queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end); void queue_shuffle_range(struct queue *queue, unsigned start, unsigned end); +bool +queue_set_priority(struct queue *queue, unsigned position, + uint8_t priority, int after_order); + +bool +queue_set_priority_range(struct queue *queue, + unsigned start_position, unsigned end_position, + uint8_t priority, int after_order); + #endif diff --git a/src/queue_print.c b/src/queue_print.c index 53ddfb689..d149e8b6f 100644 --- a/src/queue_print.c +++ b/src/queue_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -41,6 +41,10 @@ queue_print_song_info(struct client *client, const struct queue *queue, song_print_info(client, queue_get(queue, position)); client_printf(client, "Pos: %u\nId: %u\n", position, queue_position_to_id(queue, position)); + + uint8_t priority = queue_get_priority_at_position(queue, position); + if (priority != 0) + client_printf(client, "Prio: %u\n", priority); } void diff --git a/src/queue_print.h b/src/queue_print.h index d754a9673..371e20416 100644 --- a/src/queue_print.h +++ b/src/queue_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/queue_save.c b/src/queue_save.c index afe04ca2d..a7c511c0e 100644 --- a/src/queue_save.c +++ b/src/queue_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/queue_save.h b/src/queue_save.h index 287683390..5526d615d 100644 --- a/src/queue_save.h +++ b/src/queue_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/refcount.h b/src/refcount.h index 87a2715a4..a0e0a30b7 100644 --- a/src/refcount.h +++ b/src/refcount.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without diff --git a/src/replay_gain_ape.c b/src/replay_gain_ape.c index 9ae47468f..0b59e3c02 100644 --- a/src/replay_gain_ape.c +++ b/src/replay_gain_ape.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/replay_gain_ape.h b/src/replay_gain_ape.h index 8525ac85e..35760a0aa 100644 --- a/src/replay_gain_ape.h +++ b/src/replay_gain_ape.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/replay_gain_config.c b/src/replay_gain_config.c index bbfe127a7..2181387b7 100644 --- a/src/replay_gain_config.c +++ b/src/replay_gain_config.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/replay_gain_config.h b/src/replay_gain_config.h index 8fb77a5f6..18747cef2 100644 --- a/src/replay_gain_config.h +++ b/src/replay_gain_config.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/replay_gain_info.c b/src/replay_gain_info.c index 3b4ab4577..1f09e7a1a 100644 --- a/src/replay_gain_info.c +++ b/src/replay_gain_info.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/replay_gain_info.h b/src/replay_gain_info.h index 83b46df84..9097c3e02 100644 --- a/src/replay_gain_info.h +++ b/src/replay_gain_info.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/riff.c b/src/riff.c index 2e8648ff6..670fb55f5 100644 --- a/src/riff.c +++ b/src/riff.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/riff.h b/src/riff.h index bfcb69a7d..7b35e092a 100644 --- a/src/riff.h +++ b/src/riff.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/rtsp_client.c b/src/rtsp_client.c new file mode 100644 index 000000000..a808ef207 --- /dev/null +++ b/src/rtsp_client.c @@ -0,0 +1,731 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Based on the RTSP client by Shiro Ninomiya <shiron@snino.com> + */ + +#include "rtsp_client.h" +#include "tcp_socket.h" +#include "glib_compat.h" + +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/time.h> + +#ifdef WIN32 +#define WINVER 0x0501 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netdb.h> +#endif + +/* + * Free all memory associated with key_data + */ +void +free_kd(struct key_data *kd) +{ + struct key_data *iter = kd; + while (iter) { + g_free(iter->key); + g_free(iter->data); + iter = iter->next; + g_free(kd); + kd = iter; + } +} + +/* + * key_data type data look up + */ +char * +kd_lookup(struct key_data *kd, const char *key) +{ + while (kd) { + if (!strcmp(kd->key, key)) { + return kd->data; + } + kd = kd->next; + } + return NULL; +} + +struct rtspcl_data * +rtspcl_open(void) +{ + struct rtspcl_data *rtspcld; + rtspcld = g_new0(struct rtspcl_data, 1); + rtspcld->mutex = g_mutex_new(); + rtspcld->cond = g_cond_new(); + rtspcld->received_lines = g_queue_new(); + rtspcld->useragent = "RTSPClient"; + return rtspcld; +} + +/* bind an opened socket to specified hostname and port. + * if hostname=NULL, use INADDR_ANY. + * if *port=0, use dynamically assigned port + */ +static int bind_host(int sd, char *hostname, unsigned long ulAddr, + unsigned short *port, GError **error_r) +{ + struct sockaddr_in my_addr; + socklen_t nlen = sizeof(struct sockaddr); + struct hostent *h; + + memset(&my_addr, 0, sizeof(my_addr)); + /* use specified hostname */ + if (hostname) { + /* get server IP address (no check if input is IP address or DNS name) */ + h = gethostbyname(hostname); + if (h == NULL) { + if (strstr(hostname, "255.255.255.255") == hostname) { + my_addr.sin_addr.s_addr=-1; + } else { + if ((my_addr.sin_addr.s_addr = inet_addr(hostname)) == 0xFFFFFFFF) { + g_set_error(error_r, rtsp_client_quark(), 0, + "failed to resolve host '%s'", + hostname); + return -1; + } + } + my_addr.sin_family = AF_INET; + } else { + my_addr.sin_family = h->h_addrtype; + memcpy((char *) &my_addr.sin_addr.s_addr, + h->h_addr_list[0], h->h_length); + } + } else { + // if hostname=NULL, use INADDR_ANY + if (ulAddr) + my_addr.sin_addr.s_addr = ulAddr; + else + my_addr.sin_addr.s_addr = htonl(INADDR_ANY); + my_addr.sin_family = AF_INET; + } + + /* bind a specified port */ + my_addr.sin_port = htons(*port); + + if (bind(sd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { + g_set_error(error_r, rtsp_client_quark(), errno, + "failed to bind socket: %s", + g_strerror(errno)); + return -1; + } + + if (*port == 0) { + getsockname(sd, (struct sockaddr *) &my_addr, &nlen); + *port = ntohs(my_addr.sin_port); + } + + return 0; +} + +/* + * open tcp port + */ +static int +open_tcp_socket(char *hostname, unsigned short *port, + GError **error_r) +{ + int sd; + + /* socket creation */ + sd = socket(AF_INET, SOCK_STREAM, 0); + if (sd < 0) { + g_set_error(error_r, rtsp_client_quark(), errno, + "failed to create TCP socket: %s", + g_strerror(errno)); + return -1; + } + if (bind_host(sd, hostname, 0, port, error_r)) { + close(sd); + return -1; + } + + return sd; +} + +static bool +get_sockaddr_by_host(const char *host, short destport, + struct sockaddr_in *addr, + GError **error_r) +{ + struct hostent *h; + + h = gethostbyname(host); + if (h) { + addr->sin_family = h->h_addrtype; + memcpy((char *) &addr->sin_addr.s_addr, h->h_addr_list[0], h->h_length); + } else { + addr->sin_family = AF_INET; + if ((addr->sin_addr.s_addr=inet_addr(host))==0xFFFFFFFF) { + g_set_error(error_r, rtsp_client_quark(), 0, + "failed to resolve host '%s'", host); + return false; + } + } + addr->sin_port = htons(destport); + return true; +} + +/* + * create tcp connection + * as long as the socket is not non-blocking, this can block the process + * nsport is network byte order + */ +static bool +get_tcp_connect(int sd, struct sockaddr_in dest_addr, GError **error_r) +{ + if (connect(sd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr))){ + g_usleep(100000); + // try one more time + if (connect(sd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr))) { + g_set_error(error_r, rtsp_client_quark(), errno, + "failed to connect to %s:%d: %s", + inet_ntoa(dest_addr.sin_addr), + ntohs(dest_addr.sin_port), + g_strerror(errno)); + return false; + } + } + return true; +} + +static bool +get_tcp_connect_by_host(int sd, const char *host, short destport, + GError **error_r) +{ + struct sockaddr_in addr; + + return get_sockaddr_by_host(host, destport, &addr, error_r) && + get_tcp_connect(sd, addr, error_r); +} + +static void +rtsp_client_flush_received(struct rtspcl_data *rtspcld) +{ + char *line; + while ((line = g_queue_pop_head(rtspcld->received_lines)) != NULL) + g_free(line); +} + +static size_t +rtsp_client_socket_data(const void *_data, size_t length, void *ctx) +{ + struct rtspcl_data *rtspcld = ctx; + + g_mutex_lock(rtspcld->mutex); + + if (rtspcld->tcp_socket == NULL) { + g_mutex_unlock(rtspcld->mutex); + return 0; + } + + const bool was_empty = g_queue_is_empty(rtspcld->received_lines); + bool added = false; + const char *data = _data, *end = data + length, *p = data, *eol; + while ((eol = memchr(p, '\n', end - p)) != NULL) { + const char *next = eol + 1; + + if (rtspcld->received_lines->length < 64) { + if (eol > p && eol[-1] == '\r') + --eol; + + g_queue_push_tail(rtspcld->received_lines, + g_strndup(p, eol - p)); + added = true; + } + + p = next; + } + + if (was_empty && added) + g_cond_broadcast(rtspcld->cond); + + g_mutex_unlock(rtspcld->mutex); + + return p - data; +} + +static void +rtsp_client_socket_error(GError *error, void *ctx) +{ + struct rtspcl_data *rtspcld = ctx; + + g_warning("%s", error->message); + g_error_free(error); + + g_mutex_lock(rtspcld->mutex); + + rtsp_client_flush_received(rtspcld); + + struct tcp_socket *s = rtspcld->tcp_socket; + rtspcld->tcp_socket = NULL; + + g_cond_broadcast(rtspcld->cond); + + g_mutex_unlock(rtspcld->mutex); + + if (s != NULL) + tcp_socket_free(s); +} + +static void +rtsp_client_socket_disconnected(void *ctx) +{ + struct rtspcl_data *rtspcld = ctx; + + g_mutex_lock(rtspcld->mutex); + + rtsp_client_flush_received(rtspcld); + + struct tcp_socket *s = rtspcld->tcp_socket; + rtspcld->tcp_socket = NULL; + + g_cond_broadcast(rtspcld->cond); + + g_mutex_unlock(rtspcld->mutex); + + if (s != NULL) + tcp_socket_free(s); +} + +static const struct tcp_socket_handler rtsp_client_socket_handler = { + .data = rtsp_client_socket_data, + .error = rtsp_client_socket_error, + .disconnected = rtsp_client_socket_disconnected, +}; + +bool +rtspcl_connect(struct rtspcl_data *rtspcld, const char *host, short destport, + const char *sid, GError **error_r) +{ + assert(rtspcld->tcp_socket == NULL); + + unsigned short myport = 0; + struct sockaddr_in name; + socklen_t namelen = sizeof(name); + + int fd = open_tcp_socket(NULL, &myport, error_r); + if (fd < 0) + return false; + + if (!get_tcp_connect_by_host(fd, host, destport, error_r)) + return false; + + getsockname(fd, (struct sockaddr*)&name, &namelen); + memcpy(&rtspcld->local_addr, &name.sin_addr,sizeof(struct in_addr)); + sprintf(rtspcld->url, "rtsp://%s/%s", inet_ntoa(name.sin_addr), sid); + getpeername(fd, (struct sockaddr*)&name, &namelen); + memcpy(&rtspcld->host_addr, &name.sin_addr, sizeof(struct in_addr)); + + rtspcld->tcp_socket = tcp_socket_new(fd, &rtsp_client_socket_handler, + rtspcld); + + return true; +} + +static void +rtspcl_disconnect(struct rtspcl_data *rtspcld) +{ + g_mutex_lock(rtspcld->mutex); + rtsp_client_flush_received(rtspcld); + g_mutex_unlock(rtspcld->mutex); + + if (rtspcld->tcp_socket != NULL) { + tcp_socket_free(rtspcld->tcp_socket); + rtspcld->tcp_socket = NULL; + } +} + +static void +rtspcl_remove_all_exthds(struct rtspcl_data *rtspcld) +{ + free_kd(rtspcld->exthds); + rtspcld->exthds = NULL; +} + +void +rtspcl_close(struct rtspcl_data *rtspcld) +{ + rtspcl_disconnect(rtspcld); + g_queue_free(rtspcld->received_lines); + rtspcl_remove_all_exthds(rtspcld); + g_free(rtspcld->session); + g_cond_free(rtspcld->cond); + g_mutex_free(rtspcld->mutex); + g_free(rtspcld); +} + +void +rtspcl_add_exthds(struct rtspcl_data *rtspcld, const char *key, char *data) +{ + struct key_data *new_kd; + new_kd = g_new(struct key_data, 1); + new_kd->key = g_strdup(key); + new_kd->data = g_strdup(data); + new_kd->next = NULL; + if (!rtspcld->exthds) { + rtspcld->exthds = new_kd; + } else { + struct key_data *iter = rtspcld->exthds; + while (iter->next) { + iter = iter->next; + } + iter->next = new_kd; + } +} + +/* + * read one line from the file descriptor + * timeout: msec unit, -1 for infinite + * if CR comes then following LF is expected + * returned string in line is always null terminated, maxlen-1 is maximum string length + */ +static int +read_line(struct rtspcl_data *rtspcld, char *line, int maxlen, + int timeout) +{ + g_mutex_lock(rtspcld->mutex); + + GTimeVal end_time; + if (timeout >= 0) { + g_get_current_time(&end_time); + + end_time.tv_sec += timeout / 1000; + timeout %= 1000; + end_time.tv_usec = timeout * 1000; + if (end_time.tv_usec > 1000000) { + end_time.tv_usec -= 1000000; + ++end_time.tv_sec; + } + } + + while (true) { + if (!g_queue_is_empty(rtspcld->received_lines)) { + /* success, copy to buffer */ + + char *p = g_queue_pop_head(rtspcld->received_lines); + g_mutex_unlock(rtspcld->mutex); + + g_strlcpy(line, p, maxlen); + g_free(p); + + return strlen(line); + } + + if (rtspcld->tcp_socket == NULL) { + /* error */ + g_mutex_unlock(rtspcld->mutex); + return -1; + } + + if (timeout < 0) { + g_cond_wait(rtspcld->cond, rtspcld->mutex); + } else if (!g_cond_timed_wait(rtspcld->cond, rtspcld->mutex, + &end_time)) { + g_mutex_unlock(rtspcld->mutex); + return 0; + } + } +} + +/* + * send RTSP request, and get response if it's needed + * if this gets a success, *kd is allocated or reallocated (if *kd is not NULL) + */ +bool +exec_request(struct rtspcl_data *rtspcld, const char *cmd, + const char *content_type, const char *content, + int get_response, + const struct key_data *hds, struct key_data **kd, + GError **error_r) +{ + char line[1024]; + char req[1024]; + char reql[128]; + const char delimiters[] = " "; + char *token, *dp; + int dsize = 0; + int timeout = 5000; // msec unit + + if (!rtspcld) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "not connected"); + return false; + } + + sprintf(req, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, rtspcld->url, ++rtspcld->cseq ); + + if ( rtspcld->session != NULL ) { + sprintf(reql,"Session: %s\r\n", rtspcld->session ); + strncat(req,reql,sizeof(req)); + } + + const struct key_data *hd_iter = hds; + while (hd_iter) { + sprintf(reql, "%s: %s\r\n", hd_iter->key, hd_iter->data); + strncat(req, reql, sizeof(req)); + hd_iter = hd_iter->next; + } + + if (content_type && content) { + sprintf(reql, "Content-Type: %s\r\nContent-Length: %d\r\n", + content_type, (int) strlen(content)); + strncat(req,reql,sizeof(req)); + } + + sprintf(reql, "User-Agent: %s\r\n", rtspcld->useragent); + strncat(req, reql, sizeof(req)); + + hd_iter = rtspcld->exthds; + while (hd_iter) { + sprintf(reql, "%s: %s\r\n", hd_iter->key, hd_iter->data); + strncat(req, reql, sizeof(req)); + hd_iter = hd_iter->next; + } + strncat(req, "\r\n", sizeof(req)); + + if (content_type && content) + strncat(req, content, sizeof(req)); + + if (!tcp_socket_send(rtspcld->tcp_socket, req, strlen(req))) { + g_set_error(error_r, rtsp_client_quark(), errno, + "write error: %s", + g_strerror(errno)); + return false; + } + + if (!get_response) return true; + + if (read_line(rtspcld, line, sizeof(line), timeout) <= 0) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "request failed"); + return false; + } + + token = strtok(line, delimiters); + token = strtok(NULL, delimiters); + if (token == NULL) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "request failed"); + return false; + } + + if (strcmp(token, "200") != 0) { + g_set_error(error_r, rtsp_client_quark(), 0, + "request failed: %s", token); + return false; + } + + /* if the caller isn't interested in response headers, put + them on the trash, which is freed before returning from + this function */ + struct key_data *trash = NULL; + if (kd == NULL) + kd = &trash; + + struct key_data *cur_kd = *kd; + + struct key_data *new_kd = NULL; + while (read_line(rtspcld, line, sizeof(line), timeout) > 0) { + timeout = 1000; // once it started, it shouldn't take a long time + if (new_kd != NULL && line[0] == ' ') { + const char *j = line; + while (*j == ' ') + ++j; + + dsize += strlen(j); + new_kd->data = g_realloc(new_kd->data, dsize); + strcat(new_kd->data, j); + continue; + } + dp = strstr(line, ":"); + if (!dp) { + free_kd(*kd); + *kd = NULL; + + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "request failed, bad header"); + return false; + } + + *dp++ = 0; + new_kd = g_new(struct key_data, 1); + new_kd->key = g_strdup(line); + dsize = strlen(dp) + 1; + new_kd->data = g_strdup(dp); + new_kd->next = NULL; + if (cur_kd == NULL) { + cur_kd = *kd = new_kd; + } else { + cur_kd->next = new_kd; + cur_kd = new_kd; + } + } + + free_kd(trash); + + return true; +} + +bool +rtspcl_set_parameter(struct rtspcl_data *rtspcld, const char *parameter, + GError **error_r) +{ + return exec_request(rtspcld, "SET_PARAMETER", "text/parameters", + parameter, 1, NULL, NULL, error_r); +} + +void +rtspcl_set_useragent(struct rtspcl_data *rtspcld, const char *name) +{ + rtspcld->useragent = name; +} + +bool +rtspcl_announce_sdp(struct rtspcl_data *rtspcld, const char *sdp, + GError **error_r) +{ + return exec_request(rtspcld, "ANNOUNCE", "application/sdp", sdp, 1, + NULL, NULL, error_r); +} + +bool +rtspcl_setup(struct rtspcl_data *rtspcld, struct key_data **kd, + int control_port, int ntp_port, + GError **error_r) +{ + struct key_data *rkd = NULL, hds; + const char delimiters[] = ";"; + char *buf = NULL; + char *token, *pc; + int rval = false; + + static char transport_key[] = "Transport"; + + char transport_value[256]; + snprintf(transport_value, sizeof(transport_value), + "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d", + control_port, ntp_port); + + hds.key = transport_key; + hds.data = transport_value; + hds.next = NULL; + if (!exec_request(rtspcld, "SETUP", NULL, NULL, 1, + &hds, &rkd, error_r)) + return false; + + if (!(rtspcld->session = g_strdup(kd_lookup(rkd, "Session")))) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "no session in response"); + goto erexit; + } + if (!(rtspcld->transport = kd_lookup(rkd, "Transport"))) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "no transport in response"); + goto erexit; + } + buf = g_strdup(rtspcld->transport); + token = strtok(buf, delimiters); + rtspcld->server_port = 0; + rtspcld->control_port = 0; + while (token) { + if ((pc = strstr(token, "="))) { + *pc = 0; + if (!strcmp(token,"server_port")) { + rtspcld->server_port=atoi(pc + 1); + } + if (!strcmp(token,"control_port")) { + rtspcld->control_port=atoi(pc + 1); + } + } + token = strtok(NULL, delimiters); + } + if (rtspcld->server_port == 0) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "no server_port in response"); + goto erexit; + } + if (rtspcld->control_port == 0) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "no control_port in response"); + goto erexit; + } + rval = true; + erexit: + g_free(buf); + + if (!rval || kd == NULL) { + free_kd(rkd); + rkd = NULL; + } + + if (kd != NULL) + *kd = rkd; + + return rval; +} + +bool +rtspcl_record(struct rtspcl_data *rtspcld, + int seq_num, int rtptime, + GError **error_r) +{ + if (!rtspcld->session) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "no session in progress"); + return false; + } + + char buf[128]; + sprintf(buf, "seq=%d,rtptime=%u", seq_num, rtptime); + + struct key_data rtp; + static char rtp_key[] = "RTP-Info"; + rtp.key = rtp_key; + rtp.data = buf; + rtp.next = NULL; + + struct key_data range; + static char range_key[] = "Range"; + range.key = range_key; + static char range_value[] = "npt=0-"; + range.data = range_value; + range.next = &rtp; + + return exec_request(rtspcld, "RECORD", NULL, NULL, 1, &range, + NULL, error_r); +} + +char * +rtspcl_local_ip(struct rtspcl_data *rtspcld) +{ + return inet_ntoa(rtspcld->local_addr); +} diff --git a/src/rtsp_client.h b/src/rtsp_client.h new file mode 100644 index 000000000..5c8425248 --- /dev/null +++ b/src/rtsp_client.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Based on the RTSP client by Shiro Ninomiya <shiron@snino.com> + */ + +#ifndef MPD_RTSP_CLIENT_H +#define MPD_RTSP_CLIENT_H + +#include <stdbool.h> +#include <glib.h> + +#ifdef WIN32 +#define WINVER 0x0501 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <netinet/in.h> +#endif + +struct key_data { + char *key; + char *data; + struct key_data *next; +}; + +struct rtspcl_data { + GMutex *mutex; + GCond *cond; + + GQueue *received_lines; + + struct tcp_socket *tcp_socket; + + char url[128]; + int cseq; + struct key_data *exthds; + char *session; + char *transport; + unsigned short server_port; + unsigned short control_port; + struct in_addr host_addr; + struct in_addr local_addr; + const char *useragent; + +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +rtsp_client_quark(void) +{ + return g_quark_from_static_string("rtsp_client"); +} + +void +free_kd(struct key_data *kd); + +char * +kd_lookup(struct key_data *kd, const char *key); + +G_GNUC_MALLOC +struct rtspcl_data * +rtspcl_open(void); + +bool +rtspcl_connect(struct rtspcl_data *rtspcld, const char *host, short destport, + const char *sid, GError **error_r); + +void +rtspcl_close(struct rtspcl_data *rtspcld); + +void +rtspcl_add_exthds(struct rtspcl_data *rtspcld, const char *key, char *data); + +bool +exec_request(struct rtspcl_data *rtspcld, const char *cmd, + const char *content_type, const char *content, + int get_response, + const struct key_data *hds, struct key_data **kd, + GError **error_r); + +bool +rtspcl_set_parameter(struct rtspcl_data *rtspcld, const char *parameter, + GError **error_r); + +void +rtspcl_set_useragent(struct rtspcl_data *rtspcld, const char *name); + +bool +rtspcl_announce_sdp(struct rtspcl_data *rtspcld, const char *sdp, + GError **error_r); + +bool +rtspcl_setup(struct rtspcl_data *rtspcld, struct key_data **kd, + int control_port, int ntp_port, + GError **error_r); + +bool +rtspcl_record(struct rtspcl_data *rtspcld, + int seq_num, int rtptime, + GError **error_r); + +char * +rtspcl_local_ip(struct rtspcl_data *rtspcld); + +#endif diff --git a/src/server_socket.c b/src/server_socket.c index bb7a6f097..82ad81f12 100644 --- a/src/server_socket.c +++ b/src/server_socket.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,11 @@ */ #include "config.h" + +#ifdef HAVE_STRUCT_UCRED +#define _GNU_SOURCE 1 +#endif + #include "server_socket.h" #include "socket_util.h" #include "fd_util.h" diff --git a/src/server_socket.h b/src/server_socket.h index ae0ce0c8d..e5777f7b3 100644 --- a/src/server_socket.h +++ b/src/server_socket.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -47,7 +47,7 @@ server_socket_close(struct server_socket *ss); * Add a listener on a port on all interfaces. * * @param port the TCP port - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success */ @@ -61,7 +61,7 @@ server_socket_add_port(struct server_socket *ss, unsigned port, * * @param hostname the host name to be resolved * @param port the TCP port - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success */ @@ -73,7 +73,7 @@ server_socket_add_host(struct server_socket *ss, const char *hostname, * Add a listener on a Unix domain socket. * * @param path the absolute socket path - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success */ diff --git a/src/sig_handlers.c b/src/sig_handlers.c index 8aa85cf88..b23f9e778 100644 --- a/src/sig_handlers.c +++ b/src/sig_handlers.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/sig_handlers.h b/src/sig_handlers.h index a578cd243..32e9bad95 100644 --- a/src/sig_handlers.h +++ b/src/sig_handlers.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/socket_util.c b/src/socket_util.c index 0909765ba..a89a67ed6 100644 --- a/src/socket_util.c +++ b/src/socket_util.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/socket_util.h b/src/socket_util.h index 7ef081362..3ebf4084c 100644 --- a/src/socket_util.h +++ b/src/socket_util.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -37,7 +37,7 @@ struct sockaddr; * * @param sa the sockaddr struct * @param length the length of #sa in bytes - * @param error location to store the error occuring, or NULL to + * @param error location to store the error occurring, or NULL to * ignore errors */ char * @@ -53,7 +53,7 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error); * @param address the address to listen on * @param address_length the size of #address * @param backlog the backlog parameter for the listen() system call - * @param error location to store the error occuring, or NULL to + * @param error location to store the error occurring, or NULL to * ignore errors * @return the socket file descriptor or -1 on error */ diff --git a/src/song.c b/src/song.c index 13fd476b9..bddf8eb83 100644 --- a/src/song.c +++ b/src/song.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/song.h b/src/song.h index 26a1dc806..9553c76c4 100644 --- a/src/song.h +++ b/src/song.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/song_print.c b/src/song_print.c index 16239e03b..e0a04ddbd 100644 --- a/src/song_print.c +++ b/src/song_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/song_print.h b/src/song_print.h index cb83f4711..506284ea0 100644 --- a/src/song_print.h +++ b/src/song_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/song_save.c b/src/song_save.c index a1a573298..5c9353628 100644 --- a/src/song_save.c +++ b/src/song_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "directory.h" #include "tag.h" #include "text_file.h" +#include "string_util.h" #include <glib.h> @@ -96,7 +97,7 @@ song_load(FILE *fp, struct directory *parent, const char *uri, } *colon++ = 0; - value = g_strchug(colon); + value = strchug_fast_c(colon); if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { if (!song->tag) { diff --git a/src/song_save.h b/src/song_save.h index 03285015a..1aaa7642c 100644 --- a/src/song_save.h +++ b/src/song_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -40,7 +40,7 @@ songvec_save(FILE *fp, const struct songvec *sv); * Loads a song from the input file. Reading stops after the * "song_end" line. * - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success, false on error */ diff --git a/src/song_sticker.c b/src/song_sticker.c index c3c64c8d1..78025906e 100644 --- a/src/song_sticker.c +++ b/src/song_sticker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/song_sticker.h b/src/song_sticker.h index 6318ccf48..43fe59dbd 100644 --- a/src/song_sticker.h +++ b/src/song_sticker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/song_update.c b/src/song_update.c index b418b600e..e2a845eef 100644 --- a/src/song_update.c +++ b/src/song_update.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -190,7 +190,7 @@ song_file_update_inarchive(struct song *song) tag_free(song->tag); //accept every file that has music suffix - //because we dont support tag reading throught + //because we don't support tag reading through //input streams song->tag = tag_new(); diff --git a/src/songvec.c b/src/songvec.c index 38bcbac88..7d5a7a474 100644 --- a/src/songvec.c +++ b/src/songvec.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/songvec.h b/src/songvec.h index 8a50b974b..521a37700 100644 --- a/src/songvec.h +++ b/src/songvec.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/state_file.c b/src/state_file.c index 55af25d5c..d7dde6583 100644 --- a/src/state_file.c +++ b/src/state_file.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -47,7 +47,7 @@ static unsigned prev_volume_version, prev_output_version, prev_playlist_version; static void -state_file_write(void) +state_file_write(struct player_control *pc) { FILE *fp; @@ -64,17 +64,17 @@ state_file_write(void) save_sw_volume_state(fp); audio_output_state_save(fp); - playlist_state_save(fp, &g_playlist); + playlist_state_save(fp, &g_playlist, pc); fclose(fp); prev_volume_version = sw_volume_state_get_hash(); prev_output_version = audio_output_state_get_version(); - prev_playlist_version = playlist_state_get_hash(&g_playlist); + prev_playlist_version = playlist_state_get_hash(&g_playlist, pc); } static void -state_file_read(void) +state_file_read(struct player_control *pc) { FILE *fp; bool success; @@ -95,7 +95,8 @@ state_file_read(void) while ((line = read_text_line(fp, buffer)) != NULL) { success = read_sw_volume_state(line) || audio_output_state_read(line) || - playlist_state_restore(line, fp, buffer, &g_playlist); + playlist_state_restore(line, fp, buffer, + &g_playlist, pc); if (!success) g_warning("Unrecognized line in state file: %s", line); } @@ -104,7 +105,7 @@ state_file_read(void) prev_volume_version = sw_volume_state_get_hash(); prev_output_version = audio_output_state_get_version(); - prev_playlist_version = playlist_state_get_hash(&g_playlist); + prev_playlist_version = playlist_state_get_hash(&g_playlist, pc); g_string_free(buffer, true); @@ -115,21 +116,23 @@ state_file_read(void) * saves the state file. */ static gboolean -timer_save_state_file(G_GNUC_UNUSED gpointer data) +timer_save_state_file(gpointer data) { + struct player_control *pc = data; + if (prev_volume_version == sw_volume_state_get_hash() && prev_output_version == audio_output_state_get_version() && - prev_playlist_version == playlist_state_get_hash(&g_playlist)) + prev_playlist_version == playlist_state_get_hash(&g_playlist, pc)) /* nothing has changed - don't save the state file, don't spin up the hard disk */ return true; - state_file_write(); + state_file_write(pc); return true; } void -state_file_init(const char *path) +state_file_init(const char *path, struct player_control *pc) { assert(state_file_path == NULL); @@ -137,15 +140,15 @@ state_file_init(const char *path) return; state_file_path = g_strdup(path); - state_file_read(); + state_file_read(pc); save_state_source_id = g_timeout_add_seconds(5 * 60, timer_save_state_file, - NULL); + pc); } void -state_file_finish(void) +state_file_finish(struct player_control *pc) { if (state_file_path == NULL) /* no state file configured, no cleanup required */ @@ -154,7 +157,7 @@ state_file_finish(void) if (save_state_source_id != 0) g_source_remove(save_state_source_id); - state_file_write(); + state_file_write(pc); g_free(state_file_path); } diff --git a/src/state_file.h b/src/state_file.h index ec01fcbed..4c4f881cc 100644 --- a/src/state_file.h +++ b/src/state_file.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,11 +20,13 @@ #ifndef MPD_STATE_FILE_H #define MPD_STATE_FILE_H +struct player_control; + void -state_file_init(const char *path); +state_file_init(const char *path, struct player_control *pc); void -state_file_finish(void); +state_file_finish(struct player_control *pc); void write_state_file(void); diff --git a/src/stats.c b/src/stats.c index 673d531ec..8f9ef0dbf 100644 --- a/src/stats.c +++ b/src/stats.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,7 @@ #include "client.h" #include "player_control.h" #include "strset.h" +#include "client_internal.h" struct stats stats; @@ -114,7 +115,7 @@ int stats_print(struct client *client) stats.album_count, stats.song_count, (long)g_timer_elapsed(stats.timer, NULL), - (long)(pc_get_total_play_time() + 0.5), + (long)(pc_get_total_play_time(client->player_control) + 0.5), stats.song_duration, db_get_mtime()); return 0; diff --git a/src/stats.h b/src/stats.h index fbb2e4a46..a686477de 100644 --- a/src/stats.h +++ b/src/stats.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/sticker.c b/src/sticker.c index f6cd04346..346a827a5 100644 --- a/src/sticker.c +++ b/src/sticker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/sticker.h b/src/sticker.h index 6cc0ebcee..5545206a5 100644 --- a/src/sticker.h +++ b/src/sticker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -51,7 +51,7 @@ struct sticker; /** * Opens the sticker database (if path is not NULL). * - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success, false on error */ diff --git a/src/sticker_print.c b/src/sticker_print.c index b158c8af3..65e79513c 100644 --- a/src/sticker_print.c +++ b/src/sticker_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/sticker_print.h b/src/sticker_print.h index ac542709c..7398c8083 100644 --- a/src/sticker_print.h +++ b/src/sticker_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/stored_playlist.c b/src/stored_playlist.c index cd2818522..9b3ec4a7f 100644 --- a/src/stored_playlist.c +++ b/src/stored_playlist.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "stored_playlist.h" #include "playlist_save.h" +#include "text_file.h" #include "song.h" #include "mapper.h" #include "path.h" @@ -179,7 +180,6 @@ spl_load(const char *utf8path) { FILE *file; GPtrArray *list; - char buffer[MPD_PATH_MAX]; char *path_fs; if (!spl_valid_name(utf8path) || map_spl_path() == NULL) @@ -196,28 +196,20 @@ spl_load(const char *utf8path) list = g_ptr_array_new(); - while (fgets(buffer, sizeof(buffer), file)) { - char *s = buffer; - - if (*s == PLAYLIST_COMMENT) + GString *buffer = g_string_sized_new(1024); + char *s; + while ((s = read_text_line(file, buffer)) != NULL) { + if (*s == 0 || *s == PLAYLIST_COMMENT) continue; - g_strchomp(buffer); - if (!uri_has_scheme(s)) { char *path_utf8; - struct song *song; path_utf8 = map_fs_to_utf8(s); if (path_utf8 == NULL) continue; - song = db_get_song(path_utf8); - g_free(path_utf8); - if (song == NULL) - continue; - - s = song_get_uri(song); + s = path_utf8; } else s = g_strdup(s); diff --git a/src/stored_playlist.h b/src/stored_playlist.h index 3afdbb0f0..b1fdb6f85 100644 --- a/src/stored_playlist.h +++ b/src/stored_playlist.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -51,7 +51,7 @@ spl_valid_name(const char *name_utf8); /** * Returns a list of stored_playlist_info struct pointers. Returns - * NULL if an error occured. + * NULL if an error occurred. */ GPtrArray * spl_list(void); diff --git a/src/string_util.c b/src/string_util.c new file mode 100644 index 000000000..6e5429076 --- /dev/null +++ b/src/string_util.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "string_util.h" + +#include <glib.h> + +#include <assert.h> + +const char * +strchug_fast_c(const char *p) +{ + while (*p != 0 && g_ascii_isspace(*p)) + ++p; + + return p; +} + +bool +string_array_contains(const char *const* haystack, const char *needle) +{ + assert(haystack != NULL); + assert(needle != NULL); + + for (; *haystack != NULL; ++haystack) + if (g_ascii_strcasecmp(*haystack, needle) == 0) + return true; + + return false; +} diff --git a/src/string_util.h b/src/string_util.h new file mode 100644 index 000000000..dc80a46ef --- /dev/null +++ b/src/string_util.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STRING_UTIL_H +#define MPD_STRING_UTIL_H + +#include <glib.h> + +#include <stdbool.h> + +/** + * Remove the "const" attribute from a string pointer. This is a + * dirty hack, don't use it unless you know what you're doing! + */ +G_GNUC_CONST +static inline char * +deconst_string(const char *p) +{ + union { + const char *in; + char *out; + } u = { + .in = p, + }; + + return u.out; +} + +/** + * Returns a pointer to the first non-whitespace character in the + * string, or to the end of the string. + * + * This is a faster version of g_strchug(), because it does not move + * data. + */ +G_GNUC_PURE +const char * +strchug_fast_c(const char *p); + +/** + * Same as strchug_fast_c(), but works with a writable pointer. + */ +G_GNUC_PURE +static inline char * +strchug_fast(char *p) +{ + return deconst_string(strchug_fast_c(p)); +} + +/** + * Checks whether a string array contains the specified string. + * + * @param haystack a NULL terminated list of strings + * @param needle the string to search for; the comparison is + * case-insensitive for ASCII characters + * @return true if found + */ +bool +string_array_contains(const char *const* haystack, const char *needle); + +#endif diff --git a/src/strset.c b/src/strset.c index e071fbc98..5862e4075 100644 --- a/src/strset.c +++ b/src/strset.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/strset.h b/src/strset.h index 9a7aa45e5..5382e59b8 100644 --- a/src/strset.h +++ b/src/strset.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_ape.c b/src/tag_ape.c index 79facba1b..1978ea39b 100644 --- a/src/tag_ape.c +++ b/src/tag_ape.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_ape.h b/src/tag_ape.h index 150659685..eb0f1b8a5 100644 --- a/src/tag_ape.h +++ b/src/tag_ape.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_id3.c b/src/tag_id3.c index 9c0a98d40..829b196b8 100644 --- a/src/tag_id3.c +++ b/src/tag_id3.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_id3.h b/src/tag_id3.h index 43f9678b4..17dde4b36 100644 --- a/src/tag_id3.h +++ b/src/tag_id3.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_internal.h b/src/tag_internal.h index 9d76efed1..b4b928e60 100644 --- a/src/tag_internal.h +++ b/src/tag_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_pool.c b/src/tag_pool.c index 6ad1e1f2d..eabf3e369 100644 --- a/src/tag_pool.c +++ b/src/tag_pool.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_pool.h b/src/tag_pool.h index 289d6fe5f..a96c00d85 100644 --- a/src/tag_pool.h +++ b/src/tag_pool.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_print.c b/src/tag_print.c index 493fa89b5..9a46b247a 100644 --- a/src/tag_print.c +++ b/src/tag_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_print.h b/src/tag_print.h index e16e2c441..b9eeeaecf 100644 --- a/src/tag_print.h +++ b/src/tag_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_rva2.c b/src/tag_rva2.c index 35f12118f..68ae9d5e5 100644 --- a/src/tag_rva2.c +++ b/src/tag_rva2.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_rva2.h b/src/tag_rva2.h index a92c97912..8aac2fe9f 100644 --- a/src/tag_rva2.h +++ b/src/tag_rva2.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_save.c b/src/tag_save.c index 9b90d1b92..efc476e19 100644 --- a/src/tag_save.c +++ b/src/tag_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_save.h b/src/tag_save.h index 2e8924c20..9f6a580c8 100644 --- a/src/tag_save.h +++ b/src/tag_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_table.h b/src/tag_table.h index ce47d69fc..367a3de5f 100644 --- a/src/tag_table.h +++ b/src/tag_table.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tcp_socket.c b/src/tcp_socket.c new file mode 100644 index 000000000..f65b9c07c --- /dev/null +++ b/src/tcp_socket.c @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "tcp_socket.h" +#include "fifo_buffer.h" +#include "io_thread.h" + +#include <assert.h> +#include <string.h> + +#ifdef WIN32 +#define WINVER 0x0501 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#endif + +struct tcp_socket { + const struct tcp_socket_handler *handler; + void *handler_ctx; + + GMutex *mutex; + + GIOChannel *channel; + GSource *in_source, *out_source; + + struct fifo_buffer *input, *output; +}; + +static gboolean +tcp_event(GIOChannel *source, GIOCondition condition, gpointer data); + +static void +tcp_socket_schedule_read(struct tcp_socket *s) +{ + assert(s->input != NULL); + assert(!fifo_buffer_is_full(s->input)); + + if (s->in_source != NULL) + return; + + s->in_source = g_io_create_watch(s->channel, + G_IO_IN|G_IO_ERR|G_IO_HUP); + g_source_set_callback(s->in_source, (GSourceFunc)tcp_event, s, NULL); + g_source_attach(s->in_source, io_thread_context()); +} + +static void +tcp_socket_unschedule_read(struct tcp_socket *s) +{ + if (s->in_source == NULL) + return; + + g_source_destroy(s->in_source); + g_source_unref(s->in_source); + s->in_source = NULL; +} + +static void +tcp_socket_schedule_write(struct tcp_socket *s) +{ + assert(s->output != NULL); + assert(!fifo_buffer_is_empty(s->output)); + + if (s->out_source != NULL) + return; + + s->out_source = g_io_create_watch(s->channel, G_IO_OUT); + g_source_set_callback(s->out_source, (GSourceFunc)tcp_event, s, NULL); + g_source_attach(s->out_source, io_thread_context()); +} + +static void +tcp_socket_unschedule_write(struct tcp_socket *s) +{ + if (s->out_source == NULL) + return; + + g_source_destroy(s->out_source); + g_source_unref(s->out_source); + s->out_source = NULL; +} + +/** + * Close the socket. Caller must lock the mutex. + */ +static void +tcp_socket_close(struct tcp_socket *s) +{ + tcp_socket_unschedule_read(s); + tcp_socket_unschedule_write(s); + + if (s->channel != NULL) { + g_io_channel_unref(s->channel); + s->channel = NULL; + } + + if (s->input != NULL) { + fifo_buffer_free(s->input); + s->input = NULL; + } + + if (s->output != NULL) { + fifo_buffer_free(s->output); + s->output = NULL; + } +} + +static gpointer +tcp_socket_close_callback(gpointer data) +{ + struct tcp_socket *s = data; + + g_mutex_lock(s->mutex); + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + + return NULL; +} + +static void +tcp_socket_close_indirect(struct tcp_socket *s) +{ + io_thread_call(tcp_socket_close_callback, s); + + assert(s->channel == NULL); + assert(s->in_source == NULL); + assert(s->out_source == NULL); +} + +static void +tcp_handle_input(struct tcp_socket *s) +{ + size_t length; + const void *p = fifo_buffer_read(s->input, &length); + if (p == NULL) + return; + + g_mutex_unlock(s->mutex); + size_t consumed = s->handler->data(p, length, s->handler_ctx); + g_mutex_lock(s->mutex); + if (consumed > 0 && s->input != NULL) + fifo_buffer_consume(s->input, consumed); +} + +static bool +tcp_in_event(struct tcp_socket *s) +{ + assert(s != NULL); + assert(s->channel != NULL); + + g_mutex_lock(s->mutex); + + size_t max_length; + void *p = fifo_buffer_write(s->input, &max_length); + if (p == NULL) { + GError *error = g_error_new_literal(tcp_socket_quark(), 0, + "buffer overflow"); + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + s->handler->error(error, s->handler_ctx); + return false; + } + + gsize bytes_read; + GError *error = NULL; + GIOStatus status = g_io_channel_read_chars(s->channel, + p, max_length, + &bytes_read, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + fifo_buffer_append(s->input, bytes_read); + tcp_handle_input(s); + g_mutex_unlock(s->mutex); + return true; + + case G_IO_STATUS_AGAIN: + /* try again later */ + g_mutex_unlock(s->mutex); + return true; + + case G_IO_STATUS_EOF: + /* peer disconnected */ + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + s->handler->disconnected(s->handler_ctx); + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + s->handler->error(error, s->handler_ctx); + return false; + } + + /* unreachable */ + assert(false); + return true; +} + +static bool +tcp_out_event(struct tcp_socket *s) +{ + assert(s != NULL); + assert(s->channel != NULL); + + g_mutex_lock(s->mutex); + + size_t length; + const void *p = fifo_buffer_read(s->output, &length); + if (p == NULL) { + /* no more data in the output buffer, remove the + output event */ + tcp_socket_unschedule_write(s); + g_mutex_unlock(s->mutex); + return false; + } + + gsize bytes_written; + GError *error = NULL; + GIOStatus status = g_io_channel_write_chars(s->channel, p, length, + &bytes_written, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + fifo_buffer_consume(s->output, bytes_written); + g_mutex_unlock(s->mutex); + return true; + + case G_IO_STATUS_AGAIN: + tcp_socket_schedule_write(s); + g_mutex_unlock(s->mutex); + return true; + + case G_IO_STATUS_EOF: + /* peer disconnected */ + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + s->handler->disconnected(s->handler_ctx); + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + s->handler->error(error, s->handler_ctx); + return false; + } + + /* unreachable */ + g_mutex_unlock(s->mutex); + assert(false); + return true; +} + +static gboolean +tcp_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, + gpointer data) +{ + struct tcp_socket *s = data; + + assert(source == s->channel); + + switch (condition) { + case G_IO_IN: + case G_IO_PRI: + return tcp_in_event(s); + + case G_IO_OUT: + return tcp_out_event(s); + + case G_IO_ERR: + case G_IO_HUP: + case G_IO_NVAL: + tcp_socket_close(s); + s->handler->disconnected(s->handler_ctx); + return false; + } + + /* unreachable */ + assert(false); + return false; +} + +struct tcp_socket * +tcp_socket_new(int fd, + const struct tcp_socket_handler *handler, void *ctx) +{ + assert(fd >= 0); + assert(handler != NULL); + assert(handler->data != NULL); + assert(handler->error != NULL); + assert(handler->disconnected != NULL); + + struct tcp_socket *s = g_new(struct tcp_socket, 1); + s->handler = handler; + s->handler_ctx = ctx; + s->mutex = g_mutex_new(); + + g_mutex_lock(s->mutex); + +#ifndef G_OS_WIN32 + s->channel = g_io_channel_unix_new(fd); +#else + s->channel = g_io_channel_win32_new_socket(fd); +#endif + /* GLib is responsible for closing the file descriptor */ + g_io_channel_set_close_on_unref(s->channel, true); + /* NULL encoding means the stream is binary safe */ + g_io_channel_set_encoding(s->channel, NULL, NULL); + /* no buffering */ + g_io_channel_set_buffered(s->channel, false); + + s->input = fifo_buffer_new(4096); + s->output = fifo_buffer_new(4096); + + s->in_source = NULL; + s->out_source = NULL; + + tcp_socket_schedule_read(s); + + g_mutex_unlock(s->mutex); + + return s; +} + +void +tcp_socket_free(struct tcp_socket *s) +{ + tcp_socket_close_indirect(s); + g_mutex_free(s->mutex); + g_free(s); +} + +bool +tcp_socket_send(struct tcp_socket *s, const void *data, size_t length) +{ + assert(s != NULL); + + g_mutex_lock(s->mutex); + + if (s->output == NULL || s->channel == NULL) { + /* already disconnected */ + g_mutex_unlock(s->mutex); + return false; + } + + size_t max_length; + void *p = fifo_buffer_write(s->output, &max_length); + if (p == NULL || max_length < length) { + /* buffer is full */ + g_mutex_unlock(s->mutex); + return false; + } + + memcpy(p, data, length); + fifo_buffer_append(s->output, length); + tcp_socket_schedule_write(s); + + g_mutex_unlock(s->mutex); + return true; +} + diff --git a/src/tcp_socket.h b/src/tcp_socket.h new file mode 100644 index 000000000..b6b367b86 --- /dev/null +++ b/src/tcp_socket.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TCP_SOCKET_H +#define MPD_TCP_SOCKET_H + +#include <glib.h> + +#include <stdbool.h> +#include <stddef.h> + +struct sockaddr; + +struct tcp_socket_handler { + /** + * New data has arrived. + * + * @return the number of bytes consumed; 0 if more data is + * needed + */ + size_t (*data)(const void *data, size_t length, void *ctx); + + void (*error)(GError *error, void *ctx); + + void (*disconnected)(void *ctx); +}; + +static inline GQuark +tcp_socket_quark(void) +{ + return g_quark_from_static_string("tcp_socket"); +} + +G_GNUC_MALLOC +struct tcp_socket * +tcp_socket_new(int fd, + const struct tcp_socket_handler *handler, void *ctx); + +void +tcp_socket_free(struct tcp_socket *s); + +bool +tcp_socket_send(struct tcp_socket *s, const void *data, size_t length); + +#endif diff --git a/src/text_file.c b/src/text_file.c index 355217aba..3674e5ce2 100644 --- a/src/text_file.c +++ b/src/text_file.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/text_file.h b/src/text_file.h index d016f8f7a..9dd810943 100644 --- a/src/text_file.h +++ b/src/text_file.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/text_input_stream.c b/src/text_input_stream.c index 29fb6dce6..c71e113c7 100644 --- a/src/text_input_stream.c +++ b/src/text_input_stream.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/text_input_stream.h b/src/text_input_stream.h index a1fda065d..9b3245689 100644 --- a/src/text_input_stream.h +++ b/src/text_input_stream.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/timer.c b/src/timer.c index 0b3b1198a..bbc7a4ab9 100644 --- a/src/timer.c +++ b/src/timer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -37,9 +37,9 @@ static uint64_t now(void) return ((uint64_t)tv.tv_sec * 1000000) + tv.tv_usec; } -Timer *timer_new(const struct audio_format *af) +struct timer *timer_new(const struct audio_format *af) { - Timer *timer = g_new(Timer, 1); + struct timer *timer = g_new(struct timer, 1); timer->time = 0; timer->started = 0; timer->rate = af->sample_rate * audio_format_frame_size(af); @@ -47,24 +47,24 @@ Timer *timer_new(const struct audio_format *af) return timer; } -void timer_free(Timer *timer) +void timer_free(struct timer *timer) { g_free(timer); } -void timer_start(Timer *timer) +void timer_start(struct timer *timer) { timer->time = now(); timer->started = 1; } -void timer_reset(Timer *timer) +void timer_reset(struct timer *timer) { timer->time = 0; timer->started = 0; } -void timer_add(Timer *timer, int size) +void timer_add(struct timer *timer, int size) { assert(timer->started); @@ -72,7 +72,7 @@ void timer_add(Timer *timer, int size) } unsigned -timer_delay(const Timer *timer) +timer_delay(const struct timer *timer) { int64_t delay = (int64_t)(timer->time - now()) / 1000; if (delay < 0) @@ -84,7 +84,7 @@ timer_delay(const Timer *timer) return delay / 1000; } -void timer_sync(Timer *timer) +void timer_sync(struct timer *timer) { int64_t sleep_duration; diff --git a/src/timer.h b/src/timer.h index bbd895b31..184881249 100644 --- a/src/timer.h +++ b/src/timer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,28 +24,28 @@ struct audio_format; -typedef struct _Timer { +struct timer { uint64_t time; int started; int rate; -} Timer; +}; -Timer *timer_new(const struct audio_format *af); +struct timer *timer_new(const struct audio_format *af); -void timer_free(Timer *timer); +void timer_free(struct timer *timer); -void timer_start(Timer *timer); +void timer_start(struct timer *timer); -void timer_reset(Timer *timer); +void timer_reset(struct timer *timer); -void timer_add(Timer *timer, int size); +void timer_add(struct timer *timer, int size); /** * Returns the number of milliseconds to sleep to get back to sync. */ unsigned -timer_delay(const Timer *timer); +timer_delay(const struct timer *timer); -void timer_sync(Timer *timer); +void timer_sync(struct timer *timer); #endif diff --git a/src/tokenizer.c b/src/tokenizer.c index 2b9e05070..bbb34e100 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "tokenizer.h" +#include "string_util.h" #include <stdbool.h> #include <assert.h> @@ -72,7 +73,7 @@ tokenizer_next_word(char **input_p, GError **error_r) /* a whitespace: the word ends here */ *input = 0; /* skip all following spaces, too */ - input = g_strchug(input + 1); + input = strchug_fast(input + 1); break; } @@ -126,7 +127,7 @@ tokenizer_next_unquoted(char **input_p, GError **error_r) /* a whitespace: the word ends here */ *input = 0; /* skip all following spaces, too */ - input = g_strchug(input + 1); + input = strchug_fast(input + 1); break; } @@ -205,7 +206,7 @@ tokenizer_next_string(char **input_p, GError **error_r) /* finish the string and return it */ *dest = 0; - *input_p = g_strchug(input); + *input_p = strchug_fast(input); return word; } diff --git a/src/tokenizer.h b/src/tokenizer.h index 61ff398a4..d55eb3ca6 100644 --- a/src/tokenizer.h +++ b/src/tokenizer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/udp_server.c b/src/udp_server.c new file mode 100644 index 000000000..5cfc816d6 --- /dev/null +++ b/src/udp_server.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "udp_server.h" +#include "io_thread.h" + +#include <glib.h> +#include <unistd.h> +#include <sys/time.h> +#include <errno.h> + +#ifdef WIN32 +#define WINVER 0x0501 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#endif + +struct udp_server { + const struct udp_server_handler *handler; + void *handler_ctx; + + int fd; + GIOChannel *channel; + GSource *source; + + char buffer[8192]; +}; + +static gboolean +udp_in_event(G_GNUC_UNUSED GIOChannel *source, + G_GNUC_UNUSED GIOCondition condition, + gpointer data) +{ + struct udp_server *udp = data; + + struct sockaddr_storage address_storage; + struct sockaddr *address = (struct sockaddr *)&address_storage; + socklen_t address_length = sizeof(address_storage); + + ssize_t nbytes = recvfrom(udp->fd, udp->buffer, sizeof(udp->buffer), +#ifdef WIN32 + 0, +#else + MSG_DONTWAIT, +#endif + address, &address_length); + if (nbytes <= 0) + return true; + + udp->handler->datagram(udp->fd, udp->buffer, nbytes, + address, address_length, udp->handler_ctx); + return true; +} + +struct udp_server * +udp_server_new(unsigned port, + const struct udp_server_handler *handler, void *ctx, + GError **error_r) +{ + int fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + g_set_error(error_r, udp_server_quark(), errno, + "failed to create UDP socket: %s", + g_strerror(errno)); + return NULL; + } + + const struct sockaddr_in address = { + .sin_family = AF_INET, + .sin_addr = { + .s_addr = htonl(INADDR_ANY), + }, + .sin_port = htons(port), + }; + + if (bind(fd, (const struct sockaddr *)&address, sizeof(address)) < 0) { + g_set_error(error_r, udp_server_quark(), errno, + "failed to bind UDP port %u: %s", + port, g_strerror(errno)); + close(fd); + return NULL; + } + + struct udp_server *udp = g_new(struct udp_server, 1); + udp->handler = handler; + udp->handler_ctx = ctx; + + udp->fd = fd; +#ifndef G_OS_WIN32 + udp->channel = g_io_channel_unix_new(fd); +#else + udp->channel = g_io_channel_win32_new_socket(fd); +#endif + /* NULL encoding means the stream is binary safe */ + g_io_channel_set_encoding(udp->channel, NULL, NULL); + /* no buffering */ + g_io_channel_set_buffered(udp->channel, false); + + udp->source = g_io_create_watch(udp->channel, G_IO_IN); + g_source_set_callback(udp->source, (GSourceFunc)udp_in_event, udp, + NULL); + g_source_attach(udp->source, io_thread_context()); + + return udp; +} + +void +udp_server_free(struct udp_server *udp) +{ + g_source_destroy(udp->source); + g_source_unref(udp->source); + g_io_channel_unref(udp->channel); + close(udp->fd); + g_free(udp); +} diff --git a/src/udp_server.h b/src/udp_server.h new file mode 100644 index 000000000..9e3471a45 --- /dev/null +++ b/src/udp_server.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UDP_SERVER_H +#define MPD_UDP_SERVER_H + +#include <glib.h> + +#include <stddef.h> + +struct sockaddr; + +struct udp_server_handler { + /** + * A datagram was received. + */ + void (*datagram)(int fd, const void *data, size_t length, + const struct sockaddr *source_address, + size_t source_address_length, void *ctx); +}; + +static inline GQuark +udp_server_quark(void) +{ + return g_quark_from_static_string("udp_server"); +} + +struct udp_server * +udp_server_new(unsigned port, + const struct udp_server_handler *handler, void *ctx, + GError **error_r); + +void +udp_server_free(struct udp_server *udp); + +#endif diff --git a/src/update.c b/src/update.c index d57fb114d..8fe6023ae 100644 --- a/src/update.c +++ b/src/update.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/update.h b/src/update.h index 3f8a6f6a4..3d586b694 100644 --- a/src/update.h +++ b/src/update.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/update_internal.h b/src/update_internal.h index 65744f0d6..d4fe91014 100644 --- a/src/update_internal.h +++ b/src/update_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/update_queue.c b/src/update_queue.c index d7b2d4e5f..4de250cc2 100644 --- a/src/update_queue.c +++ b/src/update_queue.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/update_remove.c b/src/update_remove.c index f7c2342a2..ca5fbd182 100644 --- a/src/update_remove.c +++ b/src/update_remove.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,10 +19,10 @@ #include "config.h" /* must be first for large file support */ #include "update_internal.h" -#include "notify.h" #include "event_pipe.h" #include "song.h" #include "playlist.h" +#include "main.h" #ifdef ENABLE_SQLITE #include "sticker.h" @@ -35,7 +35,8 @@ static const struct song *removed_song; -static struct notify remove_notify; +static GMutex *remove_mutex; +static GCond *remove_cond; /** * Safely remove a song from the database. This must be done in the @@ -58,16 +59,20 @@ song_remove_event(void) sticker_song_delete(removed_song); #endif - playlist_delete_song(&g_playlist, removed_song); - removed_song = NULL; + playlist_delete_song(&g_playlist, global_player_control, removed_song); - notify_signal(&remove_notify); + /* clear "removed_song" and send signal to update thread */ + g_mutex_lock(remove_mutex); + removed_song = NULL; + g_cond_signal(remove_cond); + g_mutex_unlock(remove_mutex); } void update_remove_global_init(void) { - notify_init(&remove_notify); + remove_mutex = g_mutex_new(); + remove_cond = g_cond_new(); event_pipe_register(PIPE_EVENT_DELETE, song_remove_event); } @@ -75,7 +80,8 @@ update_remove_global_init(void) void update_remove_global_finish(void) { - notify_deinit(&remove_notify); + g_mutex_free(remove_mutex); + g_cond_free(remove_cond); } void @@ -87,8 +93,10 @@ update_remove_song(const struct song *song) event_pipe_emit(PIPE_EVENT_DELETE); - do { - notify_wait(&remove_notify); - } while (removed_song != NULL); + g_mutex_lock(remove_mutex); + + while (removed_song != NULL) + g_cond_wait(remove_cond, remove_mutex); + g_mutex_unlock(remove_mutex); } diff --git a/src/update_walk.c b/src/update_walk.c index bf3c8f54b..e70ead173 100644 --- a/src/update_walk.c +++ b/src/update_walk.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/utils.c b/src/utils.c index 53494cc5d..9481785d7 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -101,16 +101,3 @@ char *parsePath(char *path) } #endif } - -bool -string_array_contains(const char *const* haystack, const char *needle) -{ - assert(haystack != NULL); - assert(needle != NULL); - - for (; *haystack != NULL; ++haystack) - if (g_ascii_strcasecmp(*haystack, needle) == 0) - return true; - - return false; -} diff --git a/src/utils.h b/src/utils.h index 629056637..8200400af 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -33,15 +33,4 @@ char *parsePath(char *path); -/** - * Checks whether a string array contains the specified string. - * - * @param haystack a NULL terminated list of strings - * @param needle the string to search for; the comparison is - * case-insensitive for ASCII characters - * @return true if found - */ -bool -string_array_contains(const char *const* haystack, const char *needle); - #endif diff --git a/src/volume.c b/src/volume.c index d7b72dd56..819e6fbfa 100644 --- a/src/volume.c +++ b/src/volume.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/volume.h b/src/volume.h index db266fec9..b08899a84 100644 --- a/src/volume.h +++ b/src/volume.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c index 518a7a481..f2cc5359b 100644 --- a/src/zeroconf-avahi.c +++ b/src/zeroconf-avahi.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/zeroconf-bonjour.c b/src/zeroconf-bonjour.c index 84f777c50..0f216aade 100644 --- a/src/zeroconf-bonjour.c +++ b/src/zeroconf-bonjour.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/zeroconf-internal.h b/src/zeroconf-internal.h index 7cb962431..983e5c556 100644 --- a/src/zeroconf-internal.h +++ b/src/zeroconf-internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/zeroconf.c b/src/zeroconf.c index 7b00789b6..907eb8bfc 100644 --- a/src/zeroconf.c +++ b/src/zeroconf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/zeroconf.h b/src/zeroconf.h index 23354f87d..8e33a3d89 100644 --- a/src/zeroconf.h +++ b/src/zeroconf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/dump_playlist.c b/test/dump_playlist.c index a8cb4d750..bf3fed7c9 100644 --- a/test/dump_playlist.c +++ b/test/dump_playlist.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "io_thread.h" #include "input_init.h" #include "input_stream.h" #include "tag_pool.h" @@ -30,6 +31,7 @@ #include <glib.h> #include <unistd.h> +#include <stdlib.h> static void my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, @@ -73,6 +75,13 @@ int main(int argc, char **argv) return 1; } + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + if (!input_stream_global_init(&error)) { g_warning("%s", error->message); g_error_free(error); @@ -150,6 +159,7 @@ int main(int argc, char **argv) input_stream_close(is); playlist_list_global_finish(); input_stream_global_finish(); + io_thread_deinit(); config_global_finish(); tag_pool_deinit(); diff --git a/test/read_conf.c b/test/read_conf.c index f1b38cafe..4f6005c6f 100644 --- a/test/read_conf.c +++ b/test/read_conf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/read_mixer.c b/test/read_mixer.c index 1b5b093a3..b45a14400 100644 --- a/test/read_mixer.c +++ b/test/read_mixer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -55,6 +55,25 @@ pulse_output_set_volume(G_GNUC_UNUSED struct pulse_output *po, #endif +#ifdef ENABLE_RAOP_OUTPUT +#include "output/raop_output_plugin.h" + +bool +raop_set_volume(G_GNUC_UNUSED struct raop_data *rd, + G_GNUC_UNUSED unsigned volume, + G_GNUC_UNUSED GError **error_r) +{ + return false; +} + +int +raop_get_volume(G_GNUC_UNUSED struct raop_data *rd) +{ + return -1; +} + +#endif + void event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) { diff --git a/test/read_tags.c b/test/read_tags.c index 3e5e523bf..64535a87f 100644 --- a/test/read_tags.c +++ b/test/read_tags.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "io_thread.h" #include "decoder_list.h" #include "decoder_api.h" #include "input_init.h" @@ -32,6 +33,7 @@ #include <assert.h> #include <unistd.h> +#include <stdlib.h> #ifdef HAVE_LOCALE_H #include <locale.h> @@ -164,6 +166,13 @@ int main(int argc, char **argv) decoder_name = argv[1]; path = argv[2]; + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + if (!input_stream_global_init(&error)) { g_warning("%s", error->message); g_error_free(error); @@ -195,6 +204,8 @@ int main(int argc, char **argv) decoder_plugin_deinit_all(); input_stream_global_finish(); + io_thread_deinit(); + if (tag == NULL) { g_printerr("Failed to read tags\n"); return 1; diff --git a/test/run_convert.c b/test/run_convert.c index 415d7535c..57a3a2f7f 100644 --- a/test/run_convert.c +++ b/test/run_convert.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/run_decoder.c b/test/run_decoder.c index c997ebf8f..efc246f55 100644 --- a/test/run_decoder.c +++ b/test/run_decoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "io_thread.h" #include "decoder_list.h" #include "decoder_api.h" #include "input_init.h" @@ -31,6 +32,7 @@ #include <assert.h> #include <unistd.h> +#include <stdlib.h> static void my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, @@ -180,6 +182,13 @@ int main(int argc, char **argv) g_log_set_default_handler(my_log_func, NULL); + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + if (!input_stream_global_init(&error)) { g_warning("%s", error->message); g_error_free(error); @@ -222,6 +231,7 @@ int main(int argc, char **argv) decoder_plugin_deinit_all(); input_stream_global_finish(); + io_thread_deinit(); if (!decoder.initialized) { g_printerr("Decoding failed\n"); diff --git a/test/run_encoder.c b/test/run_encoder.c index 4b512d46a..ce2928e26 100644 --- a/test/run_encoder.c +++ b/test/run_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/run_filter.c b/test/run_filter.c index 3758eb5bb..d1dffc190 100644 --- a/test/run_filter.c +++ b/test/run_filter.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/run_inotify.c b/test/run_inotify.c index 9f3c30b8c..3e7c70dba 100644 --- a/test/run_inotify.c +++ b/test/run_inotify.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/run_input.c b/test/run_input.c index a50cd70ab..c00698dff 100644 --- a/test/run_input.c +++ b/test/run_input.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "io_thread.h" #include "input_init.h" #include "input_stream.h" #include "tag_pool.h" @@ -32,6 +33,7 @@ #include <glib.h> #include <unistd.h> +#include <stdlib.h> static void my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, @@ -122,6 +124,13 @@ int main(int argc, char **argv) tag_pool_init(); config_global_init(); + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + #ifdef ENABLE_ARCHIVE archive_plugin_init_all(); #endif @@ -155,6 +164,8 @@ int main(int argc, char **argv) archive_plugin_deinit_all(); #endif + io_thread_deinit(); + config_global_finish(); tag_pool_deinit(); diff --git a/test/run_normalize.c b/test/run_normalize.c index dd1140782..d16ed60ea 100644 --- a/test/run_normalize.c +++ b/test/run_normalize.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/run_ntp_server.c b/test/run_ntp_server.c new file mode 100644 index 000000000..6f732a074 --- /dev/null +++ b/test/run_ntp_server.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ntp_server.h" +#include "signals.h" +#include "io_thread.h" + +#include <glib.h> + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#ifdef WIN32 +#define WINVER 0x0501 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif + +void +on_quit(void) +{ + io_thread_quit(); +} + +int +main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) +{ + g_thread_init(NULL); + signals_init(); + io_thread_init(); + + struct ntp_server ntp; + ntp_server_init(&ntp); + + GError *error = NULL; + if (!ntp_server_open(&ntp, &error)) { + io_thread_deinit(); + g_printerr("%s\n", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + io_thread_run(); + + ntp_server_close(&ntp); + io_thread_deinit(); + return EXIT_SUCCESS; +} diff --git a/test/run_output.c b/test/run_output.c index 5028068ff..c1d7a8120 100644 --- a/test/run_output.c +++ b/test/run_output.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "io_thread.h" #include "output_plugin.h" #include "output_internal.h" #include "output_control.h" @@ -28,6 +29,7 @@ #include "event_pipe.h" #include "idle.h" #include "playlist.h" +#include "player_control.h" #include "stdbin.h" #include <glib.h> @@ -35,6 +37,7 @@ #include <assert.h> #include <string.h> #include <unistd.h> +#include <stdlib.h> struct playlist g_playlist; @@ -104,7 +107,9 @@ load_audio_output(struct audio_output *ao, const char *name) return false; } - success = audio_output_init(ao, param, &error); + static struct player_control dummy_player_control; + + success = audio_output_init(ao, param, &dummy_player_control, &error); if (!success) { g_printerr("%s\n", error->message); g_error_free(error); @@ -113,16 +118,70 @@ load_audio_output(struct audio_output *ao, const char *name) return success; } +static bool +run_output(struct audio_output *ao, struct audio_format *audio_format) +{ + /* open the audio output */ + + GError *error = NULL; + if (!ao_plugin_open(ao->plugin, ao->data, audio_format, &error)) { + g_printerr("Failed to open audio output: %s\n", + error->message); + g_error_free(error); + return false; + } + + struct audio_format_string af_string; + g_printerr("audio_format=%s\n", + audio_format_to_string(audio_format, &af_string)); + + size_t frame_size = audio_format_frame_size(audio_format); + + /* play */ + + size_t length = 0; + char buffer[4096]; + while (true) { + if (length < sizeof(buffer)) { + ssize_t nbytes = read(0, buffer + length, + sizeof(buffer) - length); + if (nbytes <= 0) + break; + + length += (size_t)nbytes; + } + + size_t play_length = (length / frame_size) * frame_size; + if (play_length > 0) { + size_t consumed = ao_plugin_play(ao->plugin, ao->data, + buffer, play_length, + &error); + if (consumed == 0) { + ao_plugin_close(ao->plugin, ao->data); + g_printerr("Failed to play: %s\n", + error->message); + g_error_free(error); + return false; + } + + assert(consumed <= length); + assert(consumed % frame_size == 0); + + length -= consumed; + memmove(buffer, buffer + consumed, length); + } + } + + ao_plugin_close(ao->plugin, ao->data); + return true; +} + int main(int argc, char **argv) { struct audio_output ao; struct audio_format audio_format; - struct audio_format_string af_string; bool success; GError *error = NULL; - char buffer[4096]; - ssize_t nbytes; - size_t frame_size, length = 0, play_length, consumed; if (argc < 3 || argc > 4) { g_printerr("Usage: run_output CONFIG NAME [FORMAT] <IN\n"); @@ -143,6 +202,13 @@ int main(int argc, char **argv) return 1; } + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + /* initialize the audio output */ if (!load_audio_output(&ao, argv[2])) @@ -161,59 +227,17 @@ int main(int argc, char **argv) } } - /* open the audio output */ - - success = ao_plugin_open(ao.plugin, ao.data, &audio_format, &error); - if (!success) { - g_printerr("Failed to open audio output: %s\n", - error->message); - g_error_free(error); - return 1; - } - - g_printerr("audio_format=%s\n", - audio_format_to_string(&audio_format, &af_string)); - - frame_size = audio_format_frame_size(&audio_format); - - /* play */ - - while (true) { - if (length < sizeof(buffer)) { - nbytes = read(0, buffer + length, sizeof(buffer) - length); - if (nbytes <= 0) - break; - - length += (size_t)nbytes; - } + /* do it */ - play_length = (length / frame_size) * frame_size; - if (play_length > 0) { - consumed = ao_plugin_play(ao.plugin, ao.data, - buffer, play_length, - &error); - if (consumed == 0) { - g_printerr("Failed to play: %s\n", - error->message); - g_error_free(error); - return 1; - } - - assert(consumed <= length); - assert(consumed % frame_size == 0); - - length -= consumed; - memmove(buffer, buffer + consumed, length); - } - } + success = run_output(&ao, &audio_format); /* cleanup and exit */ - ao_plugin_close(ao.plugin, ao.data); - ao_plugin_finish(ao.plugin, ao.data); - g_mutex_free(ao.mutex); + audio_output_destruct(&ao); + + io_thread_deinit(); config_global_finish(); - return 0; + return success ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/test/signals.c b/test/signals.c new file mode 100644 index 000000000..5f5d336f3 --- /dev/null +++ b/test/signals.c @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "signals.h" +#ifndef WIN32 + +#include "mpd_error.h" + +#include <glib.h> + +#include <signal.h> +#include <errno.h> +#include <string.h> + +static void +quit_signal_handler(G_GNUC_UNUSED int signum) +{ + on_quit(); +} + +static void +x_sigaction(int signum, const struct sigaction *act) +{ + if (sigaction(signum, act, NULL) < 0) + MPD_ERROR("sigaction() failed: %s", strerror(errno)); +} + +#endif + +void +signals_init(void) +{ +#ifndef WIN32 + struct sigaction sa; + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + x_sigaction(SIGPIPE, &sa); + + sa.sa_handler = quit_signal_handler; + x_sigaction(SIGINT, &sa); + x_sigaction(SIGTERM, &sa); +#endif +} diff --git a/test/signals.h b/test/signals.h new file mode 100644 index 000000000..e524d35e2 --- /dev/null +++ b/test/signals.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SIGNALS_H +#define MPD_SIGNALS_H + +void +on_quit(void); + +void +signals_init(void); + +#endif diff --git a/test/software_volume.c b/test/software_volume.c index c4de69328..67dd1808e 100644 --- a/test/software_volume.c +++ b/test/software_volume.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/stdbin.h b/test/stdbin.h index 362605ad9..48cac7338 100644 --- a/test/stdbin.h +++ b/test/stdbin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/test_queue_priority.c b/test/test_queue_priority.c new file mode 100644 index 000000000..d61b8c8da --- /dev/null +++ b/test/test_queue_priority.c @@ -0,0 +1,174 @@ +#include "queue.h" +#include "song.h" + +void +song_free(G_GNUC_UNUSED struct song *song) +{ +} + +G_GNUC_UNUSED +static void +dump_order(const struct queue *queue) +{ + g_printerr("queue length=%u, order:\n", queue_length(queue)); + for (unsigned i = 0; i < queue_length(queue); ++i) + g_printerr(" [%u] -> %u (prio=%u)\n", i, queue->order[i], + queue->items[queue->order[i]].priority); +} + +static void +check_descending_priority(G_GNUC_UNUSED const struct queue *queue, + unsigned start_order) +{ + assert(start_order < queue_length(queue)); + + uint8_t last_priority = 0xff; + for (unsigned order = start_order; order < queue_length(queue); ++order) { + unsigned position = queue_order_to_position(queue, order); + uint8_t priority = queue->items[position].priority; + assert(priority <= last_priority); + last_priority = priority; + } +} + +int +main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) +{ + struct song songs[16]; + + struct queue queue; + queue_init(&queue, 32); + + for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i) + queue_append(&queue, &songs[i]); + + assert(queue_length(&queue) == G_N_ELEMENTS(songs)); + + /* priority=10 for 4 items */ + + queue_set_priority_range(&queue, 4, 8, 10, -1); + + queue.random = true; + queue_shuffle_order(&queue); + check_descending_priority(&queue, 0); + + for (unsigned i = 0; i < 4; ++i) { + assert(queue_position_to_order(&queue, i) >= 4); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue_position_to_order(&queue, i) < 4); + } + + for (unsigned i = 8; i < G_N_ELEMENTS(songs); ++i) { + assert(queue_position_to_order(&queue, i) >= 4); + } + + /* priority=50 one more item */ + + queue_set_priority_range(&queue, 15, 16, 50, -1); + check_descending_priority(&queue, 0); + + assert(queue_position_to_order(&queue, 15) == 0); + + for (unsigned i = 0; i < 4; ++i) { + assert(queue_position_to_order(&queue, i) >= 4); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue_position_to_order(&queue, i) >= 1 && + queue_position_to_order(&queue, i) < 5); + } + + for (unsigned i = 8; i < 15; ++i) { + assert(queue_position_to_order(&queue, i) >= 5); + } + + /* priority=20 for one of the 4 priority=10 items */ + + queue_set_priority_range(&queue, 3, 4, 20, -1); + check_descending_priority(&queue, 0); + + assert(queue_position_to_order(&queue, 3) == 1); + assert(queue_position_to_order(&queue, 15) == 0); + + for (unsigned i = 0; i < 3; ++i) { + assert(queue_position_to_order(&queue, i) >= 5); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue_position_to_order(&queue, i) >= 2 && + queue_position_to_order(&queue, i) < 6); + } + + for (unsigned i = 8; i < 15; ++i) { + assert(queue_position_to_order(&queue, i) >= 6); + } + + /* priority=20 for another one of the 4 priority=10 items; + pass "after_order" (with priority=10) and see if it's moved + after that one */ + + unsigned current_order = 4; + unsigned current_position = + queue_order_to_position(&queue, current_order); + + unsigned a_order = 3; + unsigned a_position = queue_order_to_position(&queue, a_order); + assert(queue.items[a_position].priority == 10); + queue_set_priority(&queue, a_position, 20, current_order); + + current_order = queue_position_to_order(&queue, current_position); + assert(current_order == 3); + + a_order = queue_position_to_order(&queue, a_position); + assert(a_order == 4); + + check_descending_priority(&queue, current_order + 1); + + /* priority=70 for one of the last items; must be inserted + right after the current song, before the priority=20 one we + just created */ + + unsigned b_order = 10; + unsigned b_position = queue_order_to_position(&queue, b_order); + assert(queue.items[b_position].priority == 0); + queue_set_priority(&queue, b_position, 70, current_order); + + current_order = queue_position_to_order(&queue, current_position); + assert(current_order == 3); + + b_order = queue_position_to_order(&queue, b_position); + assert(b_order == 4); + + check_descending_priority(&queue, current_order + 1); + + /* priority=60 for the old prio50 item; must not be moved, + because it's before the current song, and it's status + hasn't changed (it was already higher before) */ + + unsigned c_order = 0; + unsigned c_position = queue_order_to_position(&queue, c_order); + assert(queue.items[c_position].priority == 50); + queue_set_priority(&queue, c_position, 60, current_order); + + current_order = queue_position_to_order(&queue, current_position); + assert(current_order == 3); + + c_order = queue_position_to_order(&queue, c_position); + assert(c_order == 0); + + /* move the prio=20 item back */ + + a_order = queue_position_to_order(&queue, a_position); + assert(a_order == 5); + assert(queue.items[a_position].priority == 20); + queue_set_priority(&queue, a_position, 5, current_order); + + + current_order = queue_position_to_order(&queue, current_position); + assert(current_order == 3); + + a_order = queue_position_to_order(&queue, a_position); + assert(a_order == 6); +} diff --git a/valgrind.suppressions b/valgrind.suppressions index 8d687f7b8..5538516a9 100644 --- a/valgrind.suppressions +++ b/valgrind.suppressions @@ -4,6 +4,14 @@ # bogus messages. { + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:g_random_int +} + +{ g_main_context_dispatch Memcheck:Leak fun:malloc @@ -90,108 +98,34 @@ } { - g_get_language_names - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup - fun:g_get_language_names -} - -{ - g_get_language_names - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strconcat - fun:_g_compute_locale_variants - fun:g_get_language_names -} - -{ - g_get_language_names - Memcheck:Leak - fun:memalign - fun:posix_memalign - fun:slab_allocator_alloc_chunk - fun:g_slice_alloc - fun:g_hash_table_new_full - fun:g_get_language_names -} - -{ - g_get_language_names - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_get_language_names -} - -{ - g_get_language_names - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_get_language_names -} - -{ g_static_private_set Memcheck:Leak - fun:realloc - fun:g_realloc - fun:g_array_maybe_expand - fun:g_array_set_size + fun:memalign + ... fun:g_static_private_set } { g_static_private_set Memcheck:Leak - fun:malloc - fun:realloc - fun:g_realloc - fun:g_array_maybe_expand - fun:g_array_set_size + fun:*alloc + ... fun:g_static_private_set } { - g_get_language_names - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_hash_table_insert_internal - fun:g_get_language_names -} - -{ - g_get_language_names - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_slice_alloc - fun:g_hash_table_insert_internal - fun:g_get_language_names -} - -{ - g_get_language_names + g_static_private_set Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_hash_table_resize - fun:g_hash_table_insert_internal - fun:g_get_language_names + fun:*alloc + ... + fun:g_intern_static_string } { g_get_language_names Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_slice_alloc - fun:g_hash_table_new_full + fun:*alloc + ... fun:g_get_language_names } @@ -199,49 +133,31 @@ g_get_language_names Memcheck:Leak fun:memalign - fun:posix_memalign - fun:slab_allocator_alloc_chunk - fun:g_slice_alloc - fun:g_slist_prepend - fun:g_strsplit + ... fun:g_get_language_names } { g_set_prgname Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup + fun:*alloc + ... fun:g_set_prgname } { g_set_application_name Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup + fun:*alloc + ... fun:g_set_application_name } { g_thread_init_glib Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_private_new_posix_impl - fun:_g_messages_thread_init_nomessage - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_private_new_posix_impl - fun:_g_slice_thread_init_nomessage + fun:*alloc + ... fun:g_thread_init_glib } @@ -254,197 +170,43 @@ } { - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_private_new_posix_impl - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_mutex_new_posix_impl - fun:_g_messages_thread_init_nomessage - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_thread_self - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_mutex_new_posix_impl - fun:_g_slice_thread_init_nomessage - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_cond_new_posix_impl - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_mutex_new_posix_impl - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_mutex_new_posix_impl - fun:_g_mem_thread_init_noprivate_nomessage - fun:g_thread_init_glib -} - -{ - g_get_filename_charsets - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup - fun:g_get_filename_charsets -} - -{ - g_get_filename_charsets - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_get_filename_charsets -} - -{ g_get_filename_charsets Memcheck:Leak - fun:memalign - fun:posix_memalign - fun:slab_allocator_alloc_chunk - fun:g_slice_alloc - fun:g_array_sized_new - fun:g_static_private_set + fun:*alloc + ... fun:g_get_filename_charsets } { - g_get_filename_charsets - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_slice_alloc - fun:g_array_sized_new - fun:g_static_private_set - fun:g_get_filename_charsets -} - -{ - g_static_private_set - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_slice_alloc - fun:g_array_sized_new - fun:g_static_private_set -} - -{ - g_static_private_get - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_thread_self - fun:g_static_private_get -} - -{ g_get_charset Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup - fun:g_get_charset -} - -{ - g_get_charset - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_get_charset -} - -{ - g_get_charset - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_slice_alloc - fun:g_array_sized_new - fun:g_static_private_set - fun:g_get_charset -} - -{ - g_get_charset - Memcheck:Leak - fun:memalign - fun:posix_memalign - fun:slab_allocator_alloc_chunk - fun:g_slice_alloc - fun:g_array_sized_new - fun:g_static_private_set + fun:*alloc + ... fun:g_get_charset } { openssl Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc - fun:engine_cleanup_add_last - fun:ENGINE_add + fun:*alloc + ... fun:ENGINE_load_dynamic } { - openssl + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc - fun:ENGINE_new - fun:ENGINE_load_dynamic + fun:*alloc + ... + fun:g_data_initialize } { - openssl + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc - obj:/usr/lib/libssl.so.0.9.8 - fun:SSL_COMP_get_compression_methods - fun:SSL_library_init + fun:*alloc + ... + fun:g_resolver_get_default } { @@ -452,165 +214,33 @@ Memcheck:Leak fun:malloc fun:CRYPTO_malloc - fun:sk_new - obj:/usr/lib/libssl.so.0.9.8 + ... fun:SSL_COMP_get_compression_methods fun:SSL_library_init } { - openssl - Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc - fun:sk_new - fun:engine_cleanup_add_last - fun:ENGINE_add - fun:ENGINE_load_dynamic -} - -{ <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc - fun:ERR_get_state - fun:ERR_clear_error - fun:Curl_ossl_init - fun:curl_global_init -} - -{ - openssl - Memcheck:Leak - fun:malloc + fun:*alloc fun:CRYPTO_malloc - fun:lh_new - obj:/usr/lib/libcrypto.so.0.9.8 - obj:/usr/lib/libcrypto.so.0.9.8 + ... fun:ERR_get_state - fun:ERR_clear_error - fun:Curl_ossl_init - fun:curl_global_init } { - openssl + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc + fun:*alloc fun:CRYPTO_malloc - fun:lh_insert - obj:/usr/lib/libcrypto.so.0.9.8 - fun:ERR_get_state - fun:ERR_clear_error + ... + fun:RSA_new_method } { <insert a suppression name here> Memcheck:Leak - fun:malloc - fun:_dl_map_object_deps - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open - fun:do_dlopen - fun:_dl_catch_error - fun:dlerror_run - fun:__libc_dlopen_mode - fun:pthread_cancel_init -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:calloc - fun:_dl_new_object - fun:_dl_map_object_from_fd - fun:_dl_map_object - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:malloc - fun:_dl_new_object - fun:_dl_map_object_from_fd - fun:_dl_map_object - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:malloc - fun:local_strdup - fun:_dl_map_object - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:calloc - fun:_dl_check_map_versions - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:malloc - fun:_dl_map_object_deps - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:malloc - fun:_dl_map_object_deps - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:malloc - fun:_dl_new_object - fun:_dl_map_object_from_fd - fun:_dl_map_object - fun:openaux - fun:_dl_catch_error - fun:_dl_map_object_deps - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open -} - -{ - <insert_a_suppression_name_here> - Memcheck:Leak - fun:malloc - fun:local_strdup - fun:_dl_map_object - fun:openaux - fun:_dl_catch_error - fun:_dl_map_object_deps - fun:dl_open_worker - fun:_dl_catch_error + fun:*alloc fun:_dl_open } @@ -621,20 +251,6 @@ fun:_dlerror_run } -{ - dlopen - Memcheck:Leak - fun:malloc - fun:_dl_scope_free - fun:_dl_map_object_deps - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open - fun:do_dlopen - fun:_dl_catch_error - fun:dlerror_run -} - # is that a leak in libdbus? { @@ -713,91 +329,172 @@ } { - g_quark_from_static_string + g_quark_from_string Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_hash_table_new_full - fun:g_quark_from_static_string + fun:*alloc + ... + fun:g_quark_from_* } { - g_quark_from_static_string + g_get_any_init_do Memcheck:Leak fun:malloc - fun:realloc - fun:g_realloc - fun:g_quark_from_static_string + fun:g_malloc + fun:g_strdup + fun:g_get_any_init_do } { - g_quark_from_string + g_get_any_init_do Memcheck:Leak fun:malloc fun:g_malloc - fun:g_strdup - fun:g_quark_from_string + fun:g_strjoinv + fun:g_get_any_init_do } { - g_quark_from_string + nss + Memcheck:Leak + fun:malloc + fun:__nss_lookup_function +} + +{ + nss + Memcheck:Leak + fun:malloc + fun:tsearch + fun:__nss_lookup_function +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:g_type_init_with_debug_flags +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:g_type_register_static +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:g_type_add_interface_static +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:g_type_add_interface_check +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:g_type_interface_add_prerequisite +} + +{ + <insert_a_suppression_name_here> Memcheck:Leak fun:calloc fun:g_malloc0 - fun:g_hash_table_new_full - fun:g_quark_from_string + fun:g_type_class_ref } { - g_quark_from_string + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:realloc - fun:g_realloc - fun:g_quark_from_string + fun:*alloc + ... + fun:g_*_class_intern_init } { - g_quark_from_string + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_slice_alloc - fun:g_hash_table_new_full - fun:g_quark_from_string + fun:*alloc + ... + fun:type_iface_vtable_base_init_Wm } { - g_get_any_init_do + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup - fun:g_get_any_init_do + fun:*alloc + ... + fun:g_object_do_class_init } { - g_get_any_init_do + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strjoinv - fun:g_get_any_init_do + fun:*alloc + ... + fun:g_object_base_class_init } { - nss + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:__nss_lookup_function + fun:*alloc + ... + fun:g_object_class_install_property } { - nss + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:tsearch - fun:__nss_lookup_function + fun:*alloc + ... + fun:soup_*_class_intern_init +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:soup_auth_manager_add_type +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:soup_auth_manager_class_intern_init +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:soup_auth_manager_ntlm_class_intern_init +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:intern_header_name } { |