Commit 8ec10e2b authored by Led's avatar Led

Merge commit '0.14.2' into alt-git

parents 1a421a5e fd69782a
......@@ -36,3 +36,5 @@ tags
*~
.stgit*
doc/protocol.html
doc/protocol
doc/api
......@@ -10,12 +10,6 @@ Max Kellermann <max@duempel.org>
José Anarch <anarchsss@gmail.com>
JACK plugin
Guus Sliepen <guus@sliepen.eu.org>
libsamplerate code
Jim Ramsay <i.am@jimramsay.com>
Zerconf/avahi support
Patrik Weiskircher <pat@icore.at>
Stored playlist commands
......@@ -28,6 +22,8 @@ Viliam Mateicka <viliam.mateicka@gmail.com>
Eric Wollesen <encoded@xmtp.net>
encoder API, shout output
Thomas Jansen <mithi@mithi.net>
Former Developers
-----------------
......@@ -38,6 +34,12 @@ Warren Dukes <warren.dukes@gmail.com>
Niklas Hofer
'next' and 'previous' patch
Jim Ramsay <i.am@jimramsay.com>
Zerconf/avahi support
Guus Sliepen <guus@sliepen.eu.org>
libsamplerate code
J. Alexander Treuman <jat@spatialrift.net>
general, MP3, ID3, PulseAudio, format conversion, stored playlists
......
......@@ -95,6 +95,15 @@ For MOD support. You will need libmikmod.
libavcodec, libavformat (ffmpeg) - http://ffmpeg.mplayerhq.hu/
Multi-codec library.
libsidplay2 - http://sidplay2.sourceforge.net/
For C64 SID support.
libfluidsynth - http://fluidsynth.resonance.org/
For MIDI support.
libwildmidi - http://wildmidi.sourceforge.net/
For MIDI support.
Optional Miscellaneous Dependencies
-----------------------------------
......@@ -108,6 +117,9 @@ For advanced samplerate conversions.
libcurl - http://curl.haxx.se/
For playing HTTP streams.
libmms - https://launchpad.net/libmms
For playing MMS streams.
pkg-config
----------
......
ver 0.15 - (200?/??/??)
* input:
- parse Icy-Metadata
- added support for the MMS protocol
* tags:
- support the "album artist" tag
- support MusicBrainz tags
- parse RVA2 tags in mp3 files
* decoders:
- audiofile: streaming support added
- modplug: another MOD plugin, based on libmodplug
- mikmod disabled by default, due to severe security issues in libmikmod
- sidplay: new decoder plugin for C64 SID (using libsidplay2)
- fluidsynth: new decoder plugin for MIDI files (using libfluidsynth)
- wildmidi: another decoder plugin for MIDI files (using libwildmidi)
* audio outputs:
- shout: enlarged buffer size to 32 kB
- null: allow disabling synchronization
* commands:
- "playlistinfo" supports a range now
- added "sticker database", command "sticker", which allows clients
to implement features like "song rating"
* Rewritten mixer code to support multiple mixers
* Add audio archive extraction support:
- bzip2
- iso9660
- zip
* Add RVA2 tag support
* the option "error_file" was removed, all messages are logged into
"log_file"
* support logging to syslog
* fall back to XDG music directory if no music_directory is configured
* failure to read the state file is non-fatal
* added Icy-Metadata support
* --create-db starts the MPD daemon instead of exiting
* playlist_directory and music_directory are optional
* playlist: recalculate the queued song after random is toggled
* playlist: don't unpause on delete
ver 0.14.2 (2009/02/13)
* configure.ac:
- define HAVE_FFMPEG after all checks
* decoders:
- ffmpeg: added support for the tags comment, genre, year
- ffmpeg: don't warn of empty packet output
- ffmpeg: check if the time stamp is valid
- ffmpeg: fixed seek integer overflow
- ffmpeg: enable WAV streaming
- ffmpeg: added TTA support
- wavpack: pass NULL if the .wvc file fails to open
- mikmod: call MikMod_Exit() only in the finish() method
- aac: fix stream metadata
* audio outputs:
- jack: allocate ring buffers before connecting
- jack: clear "shutdown" flag on reconnect
- jack: reduced sleep time to 1ms
- shout: fixed memory leak in the mp3 encoder
- shout: switch to blocking mode
- shout: use libshout's synchronization
- shout: don't postpone metadata
- shout: clear buffer before calling the encoder
* mapper: remove trailing slashes from music_directory
* player: set player error when output device fails
* update: recursively purge deleted directories
* update: free deleted subdirectories
ver 0.14.1 (2009/01/17)
* decoders:
- mp4: support the writer/composer tag
......@@ -43,7 +92,6 @@ ver 0.14.1 (2009/01/17)
* use custom PRNG for volume dithering (speedup)
* detect libid3tag without pkg-config
ver 0.14 (2008/12/25)
* audio outputs:
- wait 10 seconds before reopening a failed device
......
......@@ -5,7 +5,7 @@ AM_INIT_AUTOMAKE([foreign 1.9 dist-bzip2])
AM_CONFIG_HEADER(config.h)
AC_CONFIG_MACRO_DIR([m4])
AC_DEFINE(PROTOCOL_VERSION, "0.14.0", [The mpd protocol version])
AC_DEFINE(PROTOCOL_VERSION, "0.15.0", [The mpd protocol version])
dnl
......@@ -13,6 +13,8 @@ dnl programs
dnl
AC_PROG_CC_C99
AC_PROG_CXX
AC_PROG_INSTALL
AC_PROG_MAKE_SET
PKG_PROG_PKG_CONFIG
......@@ -247,6 +249,18 @@ if test x$enable_curl = xyes; then
fi
AM_CONDITIONAL(HAVE_CURL, test x$enable_curl = xyes)
AC_ARG_ENABLE(mms,
AS_HELP_STRING([--enable-mms],
[enable the MMS protocol with libmms (default: disable)]),,
[enable_mms=no])
if test x$enable_mms = xyes; then
PKG_CHECK_MODULES(MMS, [libmms],
AC_DEFINE(ENABLE_MMS, 1, [Define when libmms is used for the MMS protocol]),
AC_MSG_ERROR([libmms not found]))
fi
AM_CONDITIONAL(ENABLE_MMS, test x$enable_mms = xyes)
dnl
dnl archive plugins
......@@ -407,6 +421,21 @@ AC_ARG_WITH(tremor,[[ --with-tremor[=PFX] Use Tremor(vorbisidec) intege
AC_ARG_WITH(tremor-libraries,[ --with-tremor-libraries=DIR Directory where Tremor library is installed (optional)], tremor_libraries="$withval", tremor_libraries="")
AC_ARG_WITH(tremor-includes,[ --with-tremor-includes=DIR Directory where Tremor header files are installed (optional)], tremor_includes="$withval", tremor_includes="")
AC_ARG_ENABLE(sidplay,
AS_HELP_STRING([--enable-sidplay],
[enable C64 SID support via libsidplay2 (default: disable)]),,
enable_sidplay=no)
AC_ARG_ENABLE(fluidsynth,
AS_HELP_STRING([--enable-fluidsynth],
[enable MIDI support via fluidsynth (default: disable)]),,
enable_fluidsynth=no)
AC_ARG_ENABLE(wildmidi,
AS_HELP_STRING([--enable-wildmidi],
[enable MIDI support via wildmidi (default: disable)]),,
enable_wildmidi=no)
AC_ARG_ENABLE(wavpack,
AS_HELP_STRING([--disable-wavpack],
[disable WavPack support (default: enable)]),
......@@ -615,6 +644,16 @@ if test x$enable_jack = xyes; then
[enable_jack=no;AC_MSG_WARN([JACK not found -- disabling])])
fi
if test x$enable_jack = xyes; then
# check whether jack_set_info_function() is available
old_LIBS=$LIBS
LIBS="$LIBS $JACK_LIBS"
AC_CHECK_FUNCS(jack_set_info_function)
LIBS=$old_LIBS
fi
AM_CONDITIONAL(HAVE_JACK, test x$enable_jack = xyes)
if test x$enable_id3 = xyes; then
......@@ -962,8 +1001,7 @@ fi
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
if test x$enable_ffmpeg = xyes; then
PKG_CHECK_MODULES(FFMPEG, [libavformat libavcodec libavutil],
AC_DEFINE(HAVE_FFMPEG, 1, [Define for FFMPEG support]),
PKG_CHECK_MODULES(FFMPEG, [libavformat libavcodec libavutil],,
enable_ffmpeg=no)
fi
......@@ -986,23 +1024,78 @@ if test x$enable_ffmpeg = xyes; then
CPPCFLAGS=$old_CPPFLAGS
fi
if test x$enable_ffmpeg = xyes; then
AC_DEFINE(HAVE_FFMPEG, 1, [Define for FFMPEG support])
fi
AM_CONDITIONAL(HAVE_FFMPEG, test x$enable_ffmpeg = xyes)
if test x$enable_sidplay = xyes; then
# we have no test yet.. we're not using pkg-config here
# because libsidplay2's .pc file requires libtool
AC_SUBST(SIDPLAY_LIBS,"-lsidplay2 -lresid-builder")
AC_SUBST(SIDPLAY_CFLAGS,)
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplay2 support])
fi
AM_CONDITIONAL(ENABLE_SIDPLAY, test x$enable_sidplay = xyes)
if test x$enable_fluidsynth = xyes; then
PKG_CHECK_MODULES(FLUIDSYNTH, [fluidsynth],
AC_DEFINE(ENABLE_FLUIDSYNTH, 1, [Define for fluidsynth support]),
enable_fluidsynth=no)
fi
AM_CONDITIONAL(ENABLE_FLUIDSYNTH, test x$enable_fluidsynth = xyes)
if test x$enable_wildmidi = xyes; then
oldcflags=$CFLAGS
oldlibs=$LIBS
oldcppflags=$CPPFLAGS
AC_CHECK_LIB(WildMidi, WildMidi_Init,,
AC_MSG_ERROR([libwildmidi not found]))
CFLAGS=$oldcflags
LIBS=$oldlibs
CPPFLAGS=$oldcppflags
AC_SUBST(WILDMIDI_LIBS,-lWildMidi)
AC_SUBST(WILDMIDI_CFLAGS,)
AC_DEFINE(ENABLE_WILDMIDI, 1, [Define for wildmidi support])
fi
AM_CONDITIONAL(ENABLE_WILDMIDI, test x$enable_wildmidi = xyes)
dnl
dnl Documentation
dnl
AC_MSG_CHECKING([for xmlto (DocBook processing)])
AC_PATH_PROG(XMLTO, xmlto)
if test x$XMLTO != x; then
AC_ARG_ENABLE(documentation,
AS_HELP_STRING([--enable-documentation],
[build documentation (default: disable)]),,
[enable_documentation=no])
if test x$enable_documentation = xyes; then
AC_PATH_PROG(XMLTO, xmlto)
if test x$XMLTO = x; then
AC_MSG_ERROR([xmlto not found])
fi
AC_SUBST(XMLTO)
AC_MSG_RESULT($XMLTO)
else
AC_MSG_RESULT(no)
AC_PATH_PROG(DOXYGEN, doxygen)
if test x$DOXYGEN = x; then
AC_MSG_ERROR([doxygen not found])
fi
AC_SUBST(DOXYGEN)
fi
AM_CONDITIONAL(HAVE_XMLTO, test x$XMLTO != x)
AM_CONDITIONAL(ENABLE_DOCUMENTATION, test x$enable_documentation = xyes)
dnl
......@@ -1262,6 +1355,24 @@ else
echo " MODPLUG support ...............disabled"
fi
if test x$enable_sidplay = xyes; then
echo " C64 SID support ...............enabled"
else
echo " C64 SID support ...............disabled"
fi
if test x$enable_fluidsynth = xyes; then
echo " fluidsynth MIDI support .......enabled"
else
echo " fluidsynth MIDI support .......disabled"
fi
if test x$enable_wildmidi = xyes; then
echo " wildmidi MIDI support .........enabled"
else
echo " wildmidi MIDI support .........disabled"
fi
if test x$enable_ffmpeg = xyes; then
echo " FFMPEG support ................enabled"
else
......@@ -1279,6 +1390,9 @@ if
test x$enable_wavpack = xno &&
test x$enable_ffmpeg = xno &&
test x$enable_modplug = xno &&
test x$enable_sidplay = xno &&
test x$enable_fluidsynth = xno &&
test x$enable_wildmidi = xno &&
test x$enable_mod = xno; then
AC_MSG_ERROR([No input plugins supported!])
fi
......@@ -1327,6 +1441,12 @@ else
echo " HTTP streaming (libcurl) ......disabled"
fi
if test x$enable_mms != xno; then
echo " MMS streaming (libmms) ........enabled"
else
echo " MMS streaming (libmms) ........disabled"
fi
echo ""
echo "##########################################"
echo ""
......
DOCBOOK_FILES = protocol.xml
DOCBOOK_HTML = $(patsubst %.xml,%.html,$(DOCBOOK_FILES))
DOCBOOK_HTML = $(patsubst %.xml,%/index.html,$(DOCBOOK_FILES))
man_MANS = mpd.1 mpd.conf.5
doc_DATA = mpdconf.example
EXTRA_DIST = $(man_MANS) $(DOCBOOK_FILES) mpdconf.example
MOSTLYCLEANFILES = $(DOCBOOK_HTML)
if ENABLE_DOCUMENTATION
protocoldir = $(docdir)/protocol
protocol_DATA = $(wildcard protocol/*.html)
if HAVE_XMLTO
doc_DATA += $(DOCBOOK_HTML)
$(DOCBOOK_HTML): %/index.html: %.xml
$(XMLTO) -o protocol --stringparam chunker.output.encoding=utf-8 html $<
api/html/index.html: doxygen.conf
$(DOXYGEN) $<
all-local: $(DOCBOOK_HTML) api/html/index.html
clean-local:
rm -rf $(patsubst %.xml,%,$(DOCBOOK_FILES))
rm -rf api
install-data-local: api/html/index.html
$(mkinstalldirs) $(DESTDIR)$(docdir)/api/html
$(INSTALL_DATA) -c -m 644 api/html/*.html api/html/*.css api/html/*.png api/html/*.gif $(DESTDIR)$(docdir)/api/html
$(DOCBOOK_HTML): %.html: %.xml
$(XMLTO) html-nochunks $<
endif
# Doxyfile 1.5.6
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project
#
# All text after a hash (#) is considered a comment and will be ignored
# The format is:
# TAG = value [value, ...]
# For lists items can also be appended using:
# TAG += value [value, ...]
# Values that contain spaces should be placed between quotes (" ")
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
# This tag specifies the encoding used for all characters in the config file
# that follow. The default is UTF-8 which is also the encoding used for all
# text before the first occurrence of this tag. Doxygen uses libiconv (or the
# iconv built into libc) for the transcoding. See
# http://www.gnu.org/software/libiconv for the list of possible encodings.
DOXYFILE_ENCODING = UTF-8
# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
# by quotes) that should identify the project.
PROJECT_NAME = MPD
# The PROJECT_NUMBER tag can be used to enter a project or revision number.
# This could be handy for archiving the generated documentation or
# if some version control system is used.
PROJECT_NUMBER =
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put.
# If a relative path is entered, it will be relative to the location
# where doxygen was started. If left blank the current directory will be used.
OUTPUT_DIRECTORY = api
# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
# 4096 sub-directories (in 2 levels) under the output directory of each output
# format and will distribute the generated files over these directories.
# Enabling this option can be useful when feeding doxygen a huge amount of
# source files, where putting all generated files in the same directory would
# otherwise cause performance problems for the file system.
CREATE_SUBDIRS = NO
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all constant output in the proper language.
# The default language is English, other supported languages are:
# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish,
# and Ukrainian.
OUTPUT_LANGUAGE = English
# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
# include brief member descriptions after the members that are listed in
# the file and class documentation (similar to JavaDoc).
# Set to NO to disable this.
BRIEF_MEMBER_DESC = YES
# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
# the brief description of a member or function before the detailed description.
# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
# brief descriptions will be completely suppressed.
REPEAT_BRIEF = YES
# This tag implements a quasi-intelligent brief description abbreviator
# that is used to form the text in various listings. Each string
# in this list, if found as the leading text of the brief description, will be
# stripped from the text and the result after processing the whole list, is
# used as the annotated text. Otherwise, the brief description is used as-is.
# If left blank, the following values are used ("$name" is automatically
# replaced with the name of the entity): "The $name class" "The $name widget"
# "The $name file" "is" "provides" "specifies" "contains"
# "represents" "a" "an" "the"
ABBREVIATE_BRIEF =
# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
# Doxygen will generate a detailed section even if there is only a brief
# description.
ALWAYS_DETAILED_SEC = NO
# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
# inherited members of a class in the documentation of that class as if those
# members were ordinary class members. Constructors, destructors and assignment
# operators of the base classes will not be shown.
INLINE_INHERITED_MEMB = NO
# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
# path before files name in the file list and in the header files. If set
# to NO the shortest path that makes the file name unique will be used.
FULL_PATH_NAMES = YES
# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
# can be used to strip a user-defined part of the path. Stripping is
# only done if one of the specified strings matches the left-hand part of
# the path. The tag can be used to show relative paths in the file list.
# If left blank the directory from which doxygen is run is used as the
# path to strip.
STRIP_FROM_PATH =
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
# the path mentioned in the documentation of a class, which tells
# the reader which header file to include in order to use a class.
# If left blank only the name of the header file containing the class
# definition is used. Otherwise one should specify the include paths that
# are normally passed to the compiler using the -I flag.
STRIP_FROM_INC_PATH =
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
# (but less readable) file names. This can be useful is your file systems
# doesn't support long names like on DOS, Mac, or CD-ROM.
SHORT_NAMES = NO
# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
# will interpret the first line (until the first dot) of a JavaDoc-style
# comment as the brief description. If set to NO, the JavaDoc
# comments will behave just like regular Qt-style comments
# (thus requiring an explicit @brief command for a brief description.)
JAVADOC_AUTOBRIEF = YES
# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
# interpret the first line (until the first dot) of a Qt-style
# comment as the brief description. If set to NO, the comments
# will behave just like regular Qt-style comments (thus requiring
# an explicit \brief command for a brief description.)
QT_AUTOBRIEF = NO
# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
# treat a multi-line C++ special comment block (i.e. a block of //! or ///
# comments) as a brief description. This used to be the default behaviour.
# The new default is to treat a multi-line C++ comment block as a detailed
# description. Set this tag to YES if you prefer the old behaviour instead.
MULTILINE_CPP_IS_BRIEF = NO
# If the DETAILS_AT_TOP tag is set to YES then Doxygen
# will output the detailed description near the top, like JavaDoc.
# If set to NO, the detailed description appears after the member
# documentation.
DETAILS_AT_TOP = NO
# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
# member inherits the documentation from any documented member that it
# re-implements.
INHERIT_DOCS = YES
# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
# a new page for each member. If set to NO, the documentation of a member will
# be part of the file/class/namespace that contains it.
SEPARATE_MEMBER_PAGES = NO
# The TAB_SIZE tag can be used to set the number of spaces in a tab.
# Doxygen uses this value to replace tabs by spaces in code fragments.
TAB_SIZE = 8
# This tag can be used to specify a number of aliases that acts
# as commands in the documentation. An alias has the form "name=value".
# For example adding "sideeffect=\par Side Effects:\n" will allow you to
# put the command \sideeffect (or @sideeffect) in the documentation, which
# will result in a user-defined paragraph with heading "Side Effects:".
# You can put \n's in the value part of an alias to insert newlines.
ALIASES =
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
# sources only. Doxygen will then generate output that is more tailored for C.
# For instance, some of the names that are used will be different. The list
# of all members will be omitted, etc.
OPTIMIZE_OUTPUT_FOR_C = YES
# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
# sources only. Doxygen will then generate output that is more tailored for
# Java. For instance, namespaces will be presented as packages, qualified
# scopes will look different, etc.
OPTIMIZE_OUTPUT_JAVA = NO
# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
# sources only. Doxygen will then generate output that is more tailored for
# Fortran.
OPTIMIZE_FOR_FORTRAN = NO
# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
# sources. Doxygen will then generate output that is tailored for
# VHDL.
OPTIMIZE_OUTPUT_VHDL = NO
# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
# to include (a tag file for) the STL sources as input, then you should
# set this tag to YES in order to let doxygen match functions declarations and
# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
# func(std::string) {}). This also make the inheritance and collaboration
# diagrams that involve STL classes more complete and accurate.
BUILTIN_STL_SUPPORT = NO
# If you use Microsoft's C++/CLI language, you should set this option to YES to
# enable parsing support.
CPP_CLI_SUPPORT = NO
# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
# Doxygen will parse them like normal C++ but will assume all classes use public
# instead of private inheritance when no explicit protection keyword is present.
SIP_SUPPORT = NO
# For Microsoft's IDL there are propget and propput attributes to indicate getter
# and setter methods for a property. Setting this option to YES (the default)
# will make doxygen to replace the get and set methods by a property in the
# documentation. This will only work if the methods are indeed getting or
# setting a simple type. If this is not the case, or you want to show the
# methods anyway, you should set this option to NO.
IDL_PROPERTY_SUPPORT = YES
# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
# tag is set to YES, then doxygen will reuse the documentation of the first
# member in the group (if any) for the other members of the group. By default
# all members of a group must be documented explicitly.
DISTRIBUTE_GROUP_DOC = NO
# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
# the same type (for instance a group of public functions) to be put as a
# subgroup of that type (e.g. under the Public Functions section). Set it to
# NO to prevent subgrouping. Alternatively, this can be done per class using
# the \nosubgrouping command.
SUBGROUPING = YES
# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
# is documented as struct, union, or enum with the name of the typedef. So
# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
# with name TypeT. When disabled the typedef will appear as a member of a file,
# namespace, or class. And the struct will be named TypeS. This can typically
# be useful for C code in case the coding convention dictates that all compound
# types are typedef'ed and only the typedef is referenced, never the tag name.
TYPEDEF_HIDES_STRUCT = NO
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
# documentation are documented, even if no documentation was available.
# Private class members and static file members will be hidden unless
# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
EXTRACT_ALL = YES
# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
# will be included in the documentation.
EXTRACT_PRIVATE = NO
# If the EXTRACT_STATIC tag is set to YES all static members of a file
# will be included in the documentation.
EXTRACT_STATIC = YES
# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
# defined locally in source files will be included in the documentation.
# If set to NO only classes defined in header files are included.
EXTRACT_LOCAL_CLASSES = YES
# This flag is only useful for Objective-C code. When set to YES local
# methods, which are defined in the implementation section but not in
# the interface are included in the documentation.
# If set to NO (the default) only methods in the interface are included.
EXTRACT_LOCAL_METHODS = NO
# If this flag is set to YES, the members of anonymous namespaces will be
# extracted and appear in the documentation as a namespace called
# 'anonymous_namespace{file}', where file will be replaced with the base
# name of the file that contains the anonymous namespace. By default
# anonymous namespace are hidden.
EXTRACT_ANON_NSPACES = NO
# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
# undocumented members of documented classes, files or namespaces.
# If set to NO (the default) these members will be included in the
# various overviews, but no documentation section is generated.
# This option has no effect if EXTRACT_ALL is enabled.
HIDE_UNDOC_MEMBERS = NO
# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy.
# If set to NO (the default) these classes will be included in the various
# overviews. This option has no effect if EXTRACT_ALL is enabled.
HIDE_UNDOC_CLASSES = NO
# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
# friend (class|struct|union) declarations.
# If set to NO (the default) these declarations will be included in the
# documentation.
HIDE_FRIEND_COMPOUNDS = NO
# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
# documentation blocks found inside the body of a function.
# If set to NO (the default) these blocks will be appended to the
# function's detailed documentation block.
HIDE_IN_BODY_DOCS = NO
# The INTERNAL_DOCS tag determines if documentation
# that is typed after a \internal command is included. If the tag is set
# to NO (the default) then the documentation will be excluded.
# Set it to YES to include the internal documentation.
INTERNAL_DOCS = NO
# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
# file names in lower-case letters. If set to YES upper-case letters are also
# allowed. This is useful if you have classes or files whose names only differ
# in case and if your file system supports case sensitive file names. Windows
# and Mac users are advised to set this option to NO.
CASE_SENSE_NAMES = YES
# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
# will show members with their full class and namespace scopes in the
# documentation. If set to YES the scope will be hidden.
HIDE_SCOPE_NAMES = NO
# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
# will put a list of the files that are included by a file in the documentation
# of that file.
SHOW_INCLUDE_FILES = YES
# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
# is inserted in the documentation for inline members.
INLINE_INFO = YES
# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
# will sort the (detailed) documentation of file and class members
# alphabetically by member name. If set to NO the members will appear in
# declaration order.
SORT_MEMBER_DOCS = YES
# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
# brief documentation of file, namespace and class members alphabetically
# by member name. If set to NO (the default) the members will appear in
# declaration order.
SORT_BRIEF_DOCS = NO
# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
# hierarchy of group names into alphabetical order. If set to NO (the default)
# the group names will appear in their defined order.
SORT_GROUP_NAMES = NO
# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
# sorted by fully-qualified names, including namespaces. If set to
# NO (the default), the class list will be sorted only by class name,
# not including the namespace part.
# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
# Note: This option applies only to the class list, not to the
# alphabetical list.
SORT_BY_SCOPE_NAME = NO
# The GENERATE_TODOLIST tag can be used to enable (YES) or
# disable (NO) the todo list. This list is created by putting \todo
# commands in the documentation.
GENERATE_TODOLIST = YES
# The GENERATE_TESTLIST tag can be used to enable (YES) or
# disable (NO) the test list. This list is created by putting \test
# commands in the documentation.
GENERATE_TESTLIST = YES
# The GENERATE_BUGLIST tag can be used to enable (YES) or
# disable (NO) the bug list. This list is created by putting \bug
# commands in the documentation.
GENERATE_BUGLIST = YES
# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
# disable (NO) the deprecated list. This list is created by putting
# \deprecated commands in the documentation.
GENERATE_DEPRECATEDLIST= YES
# The ENABLED_SECTIONS tag can be used to enable conditional
# documentation sections, marked by \if sectionname ... \endif.
ENABLED_SECTIONS =
# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
# the initial value of a variable or define consists of for it to appear in
# the documentation. If the initializer consists of more lines than specified
# here it will be hidden. Use a value of 0 to hide initializers completely.
# The appearance of the initializer of individual variables and defines in the
# documentation can be controlled using \showinitializer or \hideinitializer
# command in the documentation regardless of this setting.
MAX_INITIALIZER_LINES = 30
# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
# at the bottom of the documentation of classes and structs. If set to YES the
# list will mention the files that were used to generate the documentation.
SHOW_USED_FILES = YES
# If the sources in your project are distributed over multiple directories
# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
# in the documentation. The default is NO.
SHOW_DIRECTORIES = NO
# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
# This will remove the Files entry from the Quick Index and from the
# Folder Tree View (if specified). The default is YES.
SHOW_FILES = YES
# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
# Namespaces page. This will remove the Namespaces entry from the Quick Index
# and from the Folder Tree View (if specified). The default is YES.
SHOW_NAMESPACES = YES
# The FILE_VERSION_FILTER tag can be used to specify a program or script that
# doxygen should invoke to get the current version for each file (typically from
# the version control system). Doxygen will invoke the program by executing (via
# popen()) the command <command> <input-file>, where <command> is the value of
# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
# provided by doxygen. Whatever the program writes to standard output
# is used as the file version. See the manual for examples.
FILE_VERSION_FILTER =
#---------------------------------------------------------------------------
# configuration options related to warning and progress messages
#---------------------------------------------------------------------------
# The QUIET tag can be used to turn on/off the messages that are generated
# by doxygen. Possible values are YES and NO. If left blank NO is used.
QUIET = NO
# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated by doxygen. Possible values are YES and NO. If left blank
# NO is used.
WARNINGS = YES
# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
# automatically be disabled.
WARN_IF_UNDOCUMENTED = YES
# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as not documenting some
# parameters in a documented function, or documenting parameters that
# don't exist or using markup commands wrongly.
WARN_IF_DOC_ERROR = YES
# This WARN_NO_PARAMDOC option can be abled to get warnings for
# functions that are documented, but have no documentation for their parameters
# or return value. If set to NO (the default) doxygen will only warn about
# wrong or incomplete parameter documentation, but not about the absence of
# documentation.
WARN_NO_PARAMDOC = NO
# The WARN_FORMAT tag determines the format of the warning messages that
# doxygen can produce. The string should contain the $file, $line, and $text
# tags, which will be replaced by the file and line number from which the
# warning originated and the warning text. Optionally the format may contain
# $version, which will be replaced by the version of the file (if it could
# be obtained via FILE_VERSION_FILTER)
WARN_FORMAT = "$file:$line: $text"
# The WARN_LOGFILE tag can be used to specify a file to which warning
# and error messages should be written. If left blank the output is written
# to stderr.
WARN_LOGFILE =
#---------------------------------------------------------------------------
# configuration options related to the input files
#---------------------------------------------------------------------------
# The INPUT tag can be used to specify the files and/or directories that contain
# documented source files. You may enter file names like "myfile.cpp" or
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
INPUT = ../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
# also the default input encoding. Doxygen uses libiconv (or the iconv built
# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
# the list of possible encodings.
INPUT_ENCODING = UTF-8
# If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
# and *.h) to filter out the source-files in the directories. If left
# blank the following patterns are tested:
# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
FILE_PATTERNS = *.h
# The RECURSIVE tag can be used to turn specify whether or not subdirectories
# should be searched for input files as well. Possible values are YES and NO.
# If left blank NO is used.
RECURSIVE = NO
# The EXCLUDE tag can be used to specify files and/or directories that should
# excluded from the INPUT source files. This way you can easily exclude a
# subdirectory from a directory tree whose root is specified with the INPUT tag.
EXCLUDE =
# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
# directories that are symbolic links (a Unix filesystem feature) are excluded
# from the input.
EXCLUDE_SYMLINKS = NO
# If the value of the INPUT tag contains directories, you can use the
# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
# certain files from those directories. Note that the wildcards are matched
# against the file with absolute path, so to exclude all test directories
# for example use the pattern */test/*
EXCLUDE_PATTERNS =
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# AClass::ANamespace, ANamespace::*Test
EXCLUDE_SYMBOLS =
# The EXAMPLE_PATH tag can be used to specify one or more files or
# directories that contain example code fragments that are included (see
# the \include command).
EXAMPLE_PATH =
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
# and *.h) to filter out the source-files in the directories. If left
# blank all files are included.
EXAMPLE_PATTERNS =
# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
# searched for input files to be used with the \include or \dontinclude
# commands irrespective of the value of the RECURSIVE tag.
# Possible values are YES and NO. If left blank NO is used.
EXAMPLE_RECURSIVE = NO
# The IMAGE_PATH tag can be used to specify one or more files or
# directories that contain image that are included in the documentation (see
# the \image command).
IMAGE_PATH =
# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program
# by executing (via popen()) the command <filter> <input-file>, where <filter>
# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
# input file. Doxygen will then use the output that the filter program writes
# to standard output. If FILTER_PATTERNS is specified, this tag will be
# ignored.
INPUT_FILTER =
# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
# basis. Doxygen will compare the file name with each pattern and apply the
# filter if there is a match. The filters are a list of the form:
# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
# is applied to all files.
FILTER_PATTERNS =
# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
# INPUT_FILTER) will be used to filter the input files when producing source
# files to browse (i.e. when SOURCE_BROWSER is set to YES).
FILTER_SOURCE_FILES = NO
#---------------------------------------------------------------------------
# configuration options related to source browsing
#---------------------------------------------------------------------------
# If the SOURCE_BROWSER tag is set to YES then a list of source files will
# be generated. Documented entities will be cross-referenced with these sources.
# Note: To get rid of all source code in the generated output, make sure also
# VERBATIM_HEADERS is set to NO.
SOURCE_BROWSER = YES
# Setting the INLINE_SOURCES tag to YES will include the body
# of functions and classes directly in the documentation.
INLINE_SOURCES = NO
# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
# doxygen to hide any special comment blocks from generated source code
# fragments. Normal C and C++ comments will always remain visible.
STRIP_CODE_COMMENTS = YES
# If the REFERENCED_BY_RELATION tag is set to YES
# then for each documented function all documented
# functions referencing it will be listed.
REFERENCED_BY_RELATION = NO
# If the REFERENCES_RELATION tag is set to YES
# then for each documented function all documented entities
# called/used by that function will be listed.
REFERENCES_RELATION = NO
# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
# link to the source code. Otherwise they will link to the documentstion.
REFERENCES_LINK_SOURCE = YES
# If the USE_HTAGS tag is set to YES then the references to source code
# will point to the HTML generated by the htags(1) tool instead of doxygen
# built-in source browser. The htags tool is part of GNU's global source
# tagging system (see http://www.gnu.org/software/global/global.html). You
# will need version 4.8.6 or higher.
USE_HTAGS = NO
# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
# will generate a verbatim copy of the header file for each class for
# which an include is specified. Set to NO to disable this.
VERBATIM_HEADERS = YES
#---------------------------------------------------------------------------
# configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
# of all compounds will be generated. Enable this if the project
# contains a lot of classes, structs, unions or interfaces.
ALPHABETICAL_INDEX = NO
# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
# in which this list will be split (can be a number in the range [1..20])
COLS_IN_ALPHA_INDEX = 5
# In case all classes in a project start with a common prefix, all
# classes will be put under the same header in the alphabetical index.
# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
# should be ignored while generating the index headers.
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# configuration options related to the HTML output
#---------------------------------------------------------------------------
# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
# generate HTML output.
GENERATE_HTML = YES
# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be
# put in front of it. If left blank `html' will be used as the default path.
HTML_OUTPUT = html
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
# doxygen will generate files with .html extension.
HTML_FILE_EXTENSION = .html
# The HTML_HEADER tag can be used to specify a personal HTML header for
# each generated HTML page. If it is left blank doxygen will generate a
# standard header.
HTML_HEADER =
# The HTML_FOOTER tag can be used to specify a personal HTML footer for
# each generated HTML page. If it is left blank doxygen will generate a
# standard footer.
HTML_FOOTER =
# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
# style sheet that is used by each HTML page. It can be used to
# fine-tune the look of the HTML output. If the tag is left blank doxygen
# will generate a default style sheet. Note that doxygen will try to copy
# the style sheet file to the HTML output directory, so don't put your own
# stylesheet in the HTML output directory as well, or it will be erased!
HTML_STYLESHEET =
# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
# files or namespaces will be aligned in HTML using tables. If set to
# NO a bullet list will be used.
HTML_ALIGN_MEMBERS = YES
# If the GENERATE_HTMLHELP tag is set to YES, additional index files
# will be generated that can be used as input for tools like the
# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
# of the generated HTML documentation.
GENERATE_HTMLHELP = NO
# If the GENERATE_DOCSET tag is set to YES, additional index files
# will be generated that can be used as input for Apple's Xcode 3
# integrated development environment, introduced with OSX 10.5 (Leopard).
# To create a documentation set, doxygen will generate a Makefile in the
# HTML output directory. Running make will produce the docset in that
# directory and running "make install" will install the docset in
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
# it at startup.
GENERATE_DOCSET = NO
# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
# feed. A documentation feed provides an umbrella under which multiple
# documentation sets from a single provider (such as a company or product suite)
# can be grouped.
DOCSET_FEEDNAME = "Doxygen generated docs"
# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
# should uniquely identify the documentation set bundle. This should be a
# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
# will append .docset to the name.
DOCSET_BUNDLE_ID = org.doxygen.Project
# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
# documentation will contain sections that can be hidden and shown after the
# page has loaded. For this to work a browser that supports
# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
HTML_DYNAMIC_SECTIONS = NO
# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
# be used to specify the file name of the resulting .chm file. You
# can add a path in front of the file if the result should not be
# written to the html output directory.
CHM_FILE =
# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
# be used to specify the location (absolute path including file name) of
# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
# the HTML help compiler on the generated index.hhp.
HHC_LOCATION =
# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
# controls if a separate .chi index file is generated (YES) or that
# it should be included in the master .chm file (NO).
GENERATE_CHI = NO
# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
# is used to encode HtmlHelp index (hhk), content (hhc) and project file
# content.
CHM_INDEX_ENCODING =
# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
# controls whether a binary table of contents is generated (YES) or a
# normal table of contents (NO) in the .chm file.
BINARY_TOC = NO
# The TOC_EXPAND flag can be set to YES to add extra items for group members
# to the contents of the HTML help documentation and to the tree view.
TOC_EXPAND = NO
# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
# top of each HTML page. The value NO (the default) enables the index and
# the value YES disables it.
DISABLE_INDEX = NO
# This tag can be used to set the number of enum values (range [1..20])
# that doxygen will group on one line in the generated HTML documentation.
ENUM_VALUES_PER_LINE = 4
# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
# structure should be generated to display hierarchical information.
# If the tag value is set to FRAME, a side panel will be generated
# containing a tree-like index structure (just like the one that
# is generated for HTML Help). For this to work a browser that supports
# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
# probably better off using the HTML help feature. Other possible values
# for this tag are: HIERARCHIES, which will generate the Groups, Directories,
# and Class Hiererachy pages using a tree view instead of an ordered list;
# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which
# disables this behavior completely. For backwards compatibility with previous
# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE
# respectively.
GENERATE_TREEVIEW = NONE
# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
# used to set the initial width (in pixels) of the frame in which the tree
# is shown.
TREEVIEW_WIDTH = 250
# Use this tag to change the font size of Latex formulas included
# as images in the HTML documentation. The default is 10. Note that
# when you change the font size after a successful doxygen run you need
# to manually remove any form_*.png images from the HTML output directory
# to force them to be regenerated.
FORMULA_FONTSIZE = 10
#---------------------------------------------------------------------------
# configuration options related to the LaTeX output
#---------------------------------------------------------------------------
# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
# generate Latex output.
GENERATE_LATEX = NO
# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be
# put in front of it. If left blank `latex' will be used as the default path.
LATEX_OUTPUT = latex
# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked. If left blank `latex' will be used as the default command name.
LATEX_CMD_NAME = latex
# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
# generate index for LaTeX. If left blank `makeindex' will be used as the
# default command name.
MAKEINDEX_CMD_NAME = makeindex
# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
# LaTeX documents. This may be useful for small projects and may help to
# save some trees in general.
COMPACT_LATEX = NO
# The PAPER_TYPE tag can be used to set the paper type that is used
# by the printer. Possible values are: a4, a4wide, letter, legal and
# executive. If left blank a4wide will be used.
PAPER_TYPE = a4wide
# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
# packages that should be included in the LaTeX output.
EXTRA_PACKAGES =
# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
# the generated latex document. The header should contain everything until
# the first chapter. If it is left blank doxygen will generate a
# standard header. Notice: only use this tag if you know what you are doing!
LATEX_HEADER =
# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
# is prepared for conversion to pdf (using ps2pdf). The pdf file will
# contain links (just like the HTML output) instead of page references
# This makes the output suitable for online browsing using a pdf viewer.
PDF_HYPERLINKS = YES
# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
# plain latex in the generated Makefile. Set this option to YES to get a
# higher quality PDF documentation.
USE_PDFLATEX = YES
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
# command to the generated LaTeX files. This will instruct LaTeX to keep
# running if errors occur, instead of asking the user for help.
# This option is also used when generating formulas in HTML.
LATEX_BATCHMODE = NO
# If LATEX_HIDE_INDICES is set to YES then doxygen will not
# include the index chapters (such as File Index, Compound Index, etc.)
# in the output.
LATEX_HIDE_INDICES = NO
#---------------------------------------------------------------------------
# configuration options related to the RTF output
#---------------------------------------------------------------------------
# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
# The RTF output is optimized for Word 97 and may not look very pretty with
# other RTF readers or editors.
GENERATE_RTF = NO
# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be
# put in front of it. If left blank `rtf' will be used as the default path.
RTF_OUTPUT = rtf
# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
# RTF documents. This may be useful for small projects and may help to
# save some trees in general.
COMPACT_RTF = NO
# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
# will contain hyperlink fields. The RTF file will
# contain links (just like the HTML output) instead of page references.
# This makes the output suitable for online browsing using WORD or other
# programs which support those fields.
# Note: wordpad (write) and others do not support links.
RTF_HYPERLINKS = NO
# Load stylesheet definitions from file. Syntax is similar to doxygen's
# config file, i.e. a series of assignments. You only have to provide
# replacements, missing definitions are set to their default value.
RTF_STYLESHEET_FILE =
# Set optional variables used in the generation of an rtf document.
# Syntax is similar to doxygen's config file.
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
# configuration options related to the man page output
#---------------------------------------------------------------------------
# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
# generate man pages
GENERATE_MAN = NO
# The MAN_OUTPUT tag is used to specify where the man pages will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be
# put in front of it. If left blank `man' will be used as the default path.
MAN_OUTPUT = man
# The MAN_EXTENSION tag determines the extension that is added to
# the generated man pages (default is the subroutine's section .3)
MAN_EXTENSION = .3
# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
# then it will generate one additional man file for each entity
# documented in the real man page(s). These additional files
# only source the real man page, but without them the man command
# would be unable to find the correct page. The default is NO.
MAN_LINKS = NO
#---------------------------------------------------------------------------
# configuration options related to the XML output
#---------------------------------------------------------------------------
# If the GENERATE_XML tag is set to YES Doxygen will
# generate an XML file that captures the structure of
# the code including all documentation.
GENERATE_XML = NO
# The XML_OUTPUT tag is used to specify where the XML pages will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be
# put in front of it. If left blank `xml' will be used as the default path.
XML_OUTPUT = xml
# The XML_SCHEMA tag can be used to specify an XML schema,
# which can be used by a validating XML parser to check the
# syntax of the XML files.
XML_SCHEMA =
# The XML_DTD tag can be used to specify an XML DTD,
# which can be used by a validating XML parser to check the
# syntax of the XML files.
XML_DTD =
# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
# dump the program listings (including syntax highlighting
# and cross-referencing information) to the XML output. Note that
# enabling this will significantly increase the size of the XML output.
XML_PROGRAMLISTING = YES
#---------------------------------------------------------------------------
# configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
# generate an AutoGen Definitions (see autogen.sf.net) file
# that captures the structure of the code including all
# documentation. Note that this feature is still experimental
# and incomplete at the moment.
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# configuration options related to the Perl module output
#---------------------------------------------------------------------------
# If the GENERATE_PERLMOD tag is set to YES Doxygen will
# generate a Perl module file that captures the structure of
# the code including all documentation. Note that this
# feature is still experimental and incomplete at the
# moment.
GENERATE_PERLMOD = NO
# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
# the necessary Makefile rules, Perl scripts and LaTeX code to be able
# to generate PDF and DVI output from the Perl module output.
PERLMOD_LATEX = NO
# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
# nicely formatted so it can be parsed by a human reader. This is useful
# if you want to understand what is going on. On the other hand, if this
# tag is set to NO the size of the Perl module output will be much smaller
# and Perl will parse it just the same.
PERLMOD_PRETTY = YES
# The names of the make variables in the generated doxyrules.make file
# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
# This is useful so different doxyrules.make files included by the same
# Makefile don't overwrite each other's variables.
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
# evaluate all C-preprocessor directives found in the sources and include
# files.
ENABLE_PREPROCESSING = YES
# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
# names in the source code. If set to NO (the default) only conditional
# compilation will be performed. Macro expansion can be done in a controlled
# way by setting EXPAND_ONLY_PREDEF to YES.
MACRO_EXPANSION = YES
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
# then the macro expansion is limited to the macros specified with the
# PREDEFINED and EXPAND_AS_DEFINED tags.
EXPAND_ONLY_PREDEF = YES
# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
# in the INCLUDE_PATH (see below) will be search if a #include is found.
SEARCH_INCLUDES = YES
# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by
# the preprocessor.
INCLUDE_PATH =
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
# patterns (like *.h and *.hpp) to filter out the header-files in the
# directories. If left blank, the patterns specified with FILE_PATTERNS will
# be used.
INCLUDE_FILE_PATTERNS =
# The PREDEFINED tag can be used to specify one or more macro names that
# are defined before the preprocessor is started (similar to the -D option of
# gcc). The argument of the tag is a list of macros of the form: name
# or name=definition (no spaces). If the definition and the = are
# omitted =1 is assumed. To prevent a macro definition from being
# undefined via #undef or recursively expanded use the := operator
# instead of the = operator.
PREDEFINED = G_GNUC_UNUSED=
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
# this tag can be used to specify a list of macro names that should be expanded.
# The macro definition that is found in the sources will be used.
# Use the PREDEFINED tag if you want to use a different macro definition.
EXPAND_AS_DEFINED =
# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
# doxygen's preprocessor will remove all function-like macros that are alone
# on a line, have an all uppercase name, and do not end with a semicolon. Such
# function macros are typically used for boiler-plate code, and will confuse
# the parser if not removed.
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration::additions related to external references
#---------------------------------------------------------------------------
# The TAGFILES option can be used to specify one or more tagfiles.
# Optionally an initial location of the external documentation
# can be added for each tagfile. The format of a tag file without
# this location is as follows:
# TAGFILES = file1 file2 ...
# Adding location for the tag files is done as follows:
# TAGFILES = file1=loc1 "file2 = loc2" ...
# where "loc1" and "loc2" can be relative or absolute paths or
# URLs. If a location is present for each tag, the installdox tool
# does not have to be run to correct the links.
# Note that each tag file must have a unique name
# (where the name does NOT include the path)
# If a tag file is not located in the directory in which doxygen
# is run, you must also specify the path to the tagfile here.
TAGFILES =
# When a file name is specified after GENERATE_TAGFILE, doxygen will create
# a tag file that is based on the input files it reads.
GENERATE_TAGFILE =
# If the ALLEXTERNALS tag is set to YES all external classes will be listed
# in the class index. If set to NO only the inherited external classes
# will be listed.
ALLEXTERNALS = NO
# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
# in the modules index. If set to NO, only the current project's groups will
# be listed.
EXTERNAL_GROUPS = YES
# The PERL_PATH should be the absolute path and name of the perl script
# interpreter (i.e. the result of `which perl').
PERL_PATH = /usr/bin/perl
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
# or super classes. Setting the tag to NO turns the diagrams off. Note that
# this option is superseded by the HAVE_DOT option below. This is only a
# fallback. It is recommended to install and use dot, since it yields more
# powerful graphs.
CLASS_DIAGRAMS = YES
# You can define message sequence charts within doxygen comments using the \msc
# command. Doxygen will then run the mscgen tool (see
# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
# documentation. The MSCGEN_PATH tag allows you to specify the directory where
# the mscgen tool resides. If left empty the tool is assumed to be found in the
# default search path.
MSCGEN_PATH =
# If set to YES, the inheritance and collaboration graphs will hide
# inheritance and usage relations if the target is undocumented
# or is not a class.
HIDE_UNDOC_RELATIONS = YES
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
# available from the path. This tool is part of Graphviz, a graph visualization
# toolkit from AT&T and Lucent Bell Labs. The other options in this section
# have no effect if this option is set to NO (the default)
HAVE_DOT = NO
# By default doxygen will write a font called FreeSans.ttf to the output
# directory and reference it in all dot files that doxygen generates. This
# font does not include all possible unicode characters however, so when you need
# these (or just want a differently looking font) you can specify the font name
# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
# which can be done by putting it in a standard location or by setting the
# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
# containing the font.
DOT_FONTNAME = FreeSans
# By default doxygen will tell dot to use the output directory to look for the
# FreeSans.ttf font (which doxygen will put there itself). If you specify a
# different font using DOT_FONTNAME you can set the path where dot
# can find it using this tag.
DOT_FONTPATH =
# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
# will generate a graph for each documented class showing the direct and
# indirect inheritance relations. Setting this tag to YES will force the
# the CLASS_DIAGRAMS tag to NO.
CLASS_GRAPH = YES
# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
# will generate a graph for each documented class showing the direct and
# indirect implementation dependencies (inheritance, containment, and
# class references variables) of the class with other documented classes.
COLLABORATION_GRAPH = YES
# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
# will generate a graph for groups, showing the direct groups dependencies
GROUP_GRAPHS = YES
# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
# collaboration diagrams in a style similar to the OMG's Unified Modeling
# Language.
UML_LOOK = NO
# If set to YES, the inheritance and collaboration graphs will show the
# relations between templates and their instances.
TEMPLATE_RELATIONS = NO
# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
# tags are set to YES then doxygen will generate a graph for each documented
# file showing the direct and indirect include dependencies of the file with
# other documented files.
INCLUDE_GRAPH = YES
# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
# documented header file showing the documented files that directly or
# indirectly include this file.
INCLUDED_BY_GRAPH = YES
# If the CALL_GRAPH and HAVE_DOT options are set to YES then
# doxygen will generate a call dependency graph for every global function
# or class method. Note that enabling this option will significantly increase
# the time of a run. So in most cases it will be better to enable call graphs
# for selected functions only using the \callgraph command.
CALL_GRAPH = NO
# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
# doxygen will generate a caller dependency graph for every global function
# or class method. Note that enabling this option will significantly increase
# the time of a run. So in most cases it will be better to enable caller
# graphs for selected functions only using the \callergraph command.
CALLER_GRAPH = NO
# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
# will graphical hierarchy of all classes instead of a textual one.
GRAPHICAL_HIERARCHY = YES
# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
# then doxygen will show the dependencies a directory has on other directories
# in a graphical way. The dependency relations are determined by the #include
# relations between the files in the directories.
DIRECTORY_GRAPH = YES
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot. Possible values are png, jpg, or gif
# If left blank png will be used.
DOT_IMAGE_FORMAT = png
# The tag DOT_PATH can be used to specify the path where the dot tool can be
# found. If left blank, it is assumed the dot tool can be found in the path.
DOT_PATH =
# The DOTFILE_DIRS tag can be used to specify one or more directories that
# contain dot files that are included in the documentation (see the
# \dotfile command).
DOTFILE_DIRS =
# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
# nodes that will be shown in the graph. If the number of nodes in a graph
# becomes larger than this value, doxygen will truncate the graph, which is
# visualized by representing a node as a red box. Note that doxygen if the
# number of direct children of the root node in a graph is already larger than
# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
DOT_GRAPH_MAX_NODES = 50
# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
# graphs generated by dot. A depth value of 3 means that only nodes reachable
# from the root by following a path via at most 3 edges will be shown. Nodes
# that lay further from the root node will be omitted. Note that setting this
# option to 1 or 2 may greatly reduce the computation time needed for large
# code bases. Also note that the size of a graph can be further restricted by
# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
# background. This is enabled by default, which results in a transparent
# background. Warning: Depending on the platform used, enabling this option
# may lead to badly anti-aliased labels on the edges of a graph (i.e. they
# become hard to read).
DOT_TRANSPARENT = YES
# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10)
# support this, this feature is disabled by default.
DOT_MULTI_TARGETS = NO
# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
# generate a legend page explaining the meaning of the various boxes and
# arrows in the dot generated graphs.
GENERATE_LEGEND = YES
# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
# remove the intermediate dot files that are used to generate
# the various graphs.
DOT_CLEANUP = YES
#---------------------------------------------------------------------------
# Configuration::additions related to the search engine
#---------------------------------------------------------------------------
# The SEARCHENGINE tag specifies whether or not a search engine should be
# used. If set to NO the values of all tags below this one will be ignored.
SEARCHENGINE = NO
......@@ -29,12 +29,6 @@ See \fBdocs/mpdconf.example\fP in the source tarball for an example
configuration file.
.SH REQUIRED PARAMETERS
.TP
.B music_directory <directory>
This specifies the directory where music is located.
.TP
.B playlist_directory <directory>
This specifies the directory where saved playlists are stored.
.TP
.B follow_outside_symlinks <yes or no>
Control if MPD will follow symbolic links pointing outside the music dir.
You must recreate the database after changing this option.
......@@ -49,6 +43,10 @@ The default is "yes".
.B db_file <file>
This specifies where the db file will be stored.
.TP
.B sticker_file <file>
The location of the sticker database. This is a database which
manages dynamic information attached to songs.
.TP
.B log_file <file>
This specifies where the log file should be located.
The special value "syslog" makes MPD use the local syslog daemon.
......@@ -57,6 +55,14 @@ The special value "syslog" makes MPD use the local syslog daemon.
.B pid_file <file>
This specifies the file to save mpd's process ID in.
.TP
.B music_directory <directory>
This specifies the directory where music is located.
If you do not configure this, you can only play streams.
.TP
.B playlist_directory <directory>
This specifies the directory where saved playlists are stored.
If you do not configure this, you cannot save playlists.
.TP
.B state_file <file>
This specifies if a state file is used and where it is located. The state of
mpd will be saved to this file when mpd is terminated by a TERM signal or by
......
......@@ -782,11 +782,14 @@ OK
<term>
<cmdsynopsis>
<command>shuffle</command>
<arg><replaceable>SONGRANGE</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Shuffles the current playlist.
<varname>SONGRANGE</varname> is optional and specifies
a range of songs.
</para>
</listitem>
</varlistentry>
......@@ -1156,6 +1159,73 @@ OK
</section>
<section>
<title>Stickers</title>
<para>
"Stickers" are pieces of information attached to existing MPD
objects (e.g. song files, directories, albums). Clients can
create arbitrary name/value pairs. MPD itself does not assume
any special meaning in them.
</para>
<para>
The goal is to allow clients to share additional (possibly
dynamic) information about songs, which is neither stored on
the client (not available to other clients), nor stored in the
song files (MPD has no write access).
</para>
<para>
Client developers should create a standard for common sticker
names, to ensure interoperability.
</para>
<para>
Objects which may have stickers are addressed by their object
type ("song" for song objects) and their URI (the path within
the database for songs).
</para>
<variablelist>
<varlistentry id="command_sticker_get">
<term>
<cmdsynopsis>
<command>sticker</command>
<arg choice="plain">get</arg>
<arg choice="req"><replaceable>TYPE</replaceable></arg>
<arg choice="req"><replaceable>URI</replaceable></arg>
<arg choice="req"><replaceable>NAME</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Reads a sticker value for the specified object.
</para>
</listitem>
</varlistentry>
<varlistentry id="command_sticker_set">
<term>
<cmdsynopsis>
<command>sticker</command>
<arg choice="plain">set</arg>
<arg choice="req"><replaceable>TYPE</replaceable></arg>
<arg choice="req"><replaceable>URI</replaceable></arg>
<arg choice="req"><replaceable>NAME</replaceable></arg>
<arg choice="req"><replaceable>VALUE</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Adds a sticker value to the specified object. If a
sticker item with that name already exists, it is
replaced.
</para>
</listitem>
</varlistentry>
</variablelist>
</section>
<section>
<title>Connection settings</title>
<variablelist>
......
<?xml version='1.0' encoding="utf-8"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"docbook/dtd/xml/4.2/docbookx.dtd">
<book>
<title>The Music Player Daemon Sticker Database</title>
<chapter>
<title>Introduction to MPD's Sticker Database</title>
<para>
This document shell give a short guideline for recommended tags
for use in MPD's Sticker Database.
MPD's Sticker Database is a subsystem that enables users to add
custom tags. MPD does not alter the media files.
</para>
</chapter>
<chapter>
<title>Guideline for recommended tags</title>
<para>
Since there is no standard for tags in media files, this
document is trying to give you some help deciding what tags to
use. The selection of these tags tries to cover the most
widely used tags. This way the tags might still work in other
players, if you sync the database with your original media
files.
Keep in mind that we stick with lower case tags with underscores
instead of spaces. If there will be a Sync tool in future
its easy to change this on the fly, if needed.
</para>
<tag>
<name>rating</name>
<value>1-5</value>
<para>
Will store a rating value from 1 (worst) to 5 (best) for a given song.
</para>
</tag>
<tag>
<name>album_rating</name>
<value>1-5</value>
<para>
Will store a rating value from 1 (worst) to 5 (best) for a given album.
</para>
</tag>
<tag>
<name>style</name>
<value>Keyword</value>
<para>
This tag is used to keep the Genre tag clean, by now having 1000's of genres.
Instead you define a Main Genre for each file and can make a more specific
description. This should be one Keyword like "Post Punk" or "Progressive Death Metal"
An Alternative name for this tag is "Subgenre", time will tell which one gets
more support.
</para>
</tag>
<tag>
<name>lyrics</name>
<value>The lyrics of the song, including header with Artist - Title</value>
<para>
This one is self explaining. This gives the option to store lyrics of
a song where they belong to: mapped to the song
</para>
</tag>
<tag>
<name>similar_artists</name>
<value>Comma seperated list of artists</value>
<para>
This tag enables a last.fm alike aproach which will still work when being offline
Keep in mind, that this tag is absolutely non-standard! I am not aware of any
other player that uses a comparable tag.
</para>
</tag>
</chapter>
</book>
......@@ -4,12 +4,16 @@ mpd_CFLAGS = $(MPD_CFLAGS)
mpd_CPPFLAGS = \
$(SQLITE_CFLAGS) \
$(CURL_CFLAGS) \
$(MMS_CFLAGS) \
$(AO_CFLAGS) $(ALSA_CFLAGS) \
$(SHOUT_CFLAGS) \
$(OGGVORBIS_CFLAGS) $(VORBISENC_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(AUDIOFILE_CFLAGS) $(LIBMIKMOD_CFLAGS) \
$(MODPLUG_CFLAGS) \
$(SIDPLAY_CFLAGS) \
$(FLUIDSYNTH_CFLAGS) \
$(WILDMIDI_CFLAGS) \
$(ID3TAG_CFLAGS) \
$(MAD_CFLAGS) \
$(FFMPEG_CFLAGS) \
......@@ -17,11 +21,15 @@ mpd_CPPFLAGS = \
mpd_LDADD = $(MPD_LIBS) \
$(SQLITE_LIBS) \
$(CURL_LIBS) \
$(MMS_LIBS) \
$(AO_LIBS) $(ALSA_LIBS) \
$(SHOUT_LIBS) \
$(OGGVORBIS_LIBS) $(VORBISENC_LIBS) $(FLAC_LIBS) \
$(AUDIOFILE_LIBS) $(LIBMIKMOD_LIBS) \
$(MODPLUG_LIBS) \
$(SIDPLAY_LIBS) \
$(FLUIDSYNTH_LIBS) \
$(WILDMIDI_LIBS) \
$(ID3TAG_LIBS) \
$(MAD_LIBS) \
$(MP4FF_LIBS) \
......@@ -33,12 +41,17 @@ mpd_headers = \
ack.h \
audio.h \
audio_format.h \
audio_parser.h \
audioOutput.h \
output_internal.h \
output_api.h \
output_list.h \
output_all.h \
output_thread.h \
output_control.h \
output_state.h \
output_print.h \
output_command.h \
output/shout_plugin.h \
buffer2array.h \
command.h \
......@@ -64,6 +77,7 @@ mpd_headers = \
input_stream.h \
input_file.h \
input_curl.h \
input_mms.h \
icy_metadata.h \
client.h \
listen.h \
......@@ -90,17 +104,24 @@ mpd_headers = \
permission.h \
player_thread.h \
player_control.h \
playerData.h \
playlist.h \
playlist_internal.h \
playlist_print.h \
playlist_save.h \
playlist_state.h \
queue.h \
queue_print.h \
queue_save.h \
replay_gain.h \
sig_handlers.h \
song.h \
song_print.h \
song_save.h \
song_sticker.h \
songvec.h \
state_file.h \
stats.h \
sticker.h \
tag.h \
tag_internal.h \
tag_pool.h \
......@@ -123,11 +144,16 @@ mpd_SOURCES = \
$(mpd_headers) \
notify.c \
audio.c \
audio_parser.c \
audioOutput.c \
output_api.c \
output_list.c \
output_all.c \
output_thread.c \
output_control.c \
output_state.c \
output_print.c \
output_command.c \
output_init.c \
output/null_plugin.c \
buffer2array.c \
......@@ -172,9 +198,16 @@ mpd_SOURCES = \
permission.c \
player_thread.c \
player_control.c \
playerData.c \
playlist.c \
playlist_global.c \
playlist_control.c \
playlist_edit.c \
playlist_print.c \
playlist_save.c \
playlist_state.c \
queue.c \
queue_print.c \
queue_save.c \
replay_gain.c \
sig_handlers.c \
song.c \
......@@ -194,6 +227,10 @@ mpd_SOURCES = \
stored_playlist.c \
timer.c
if ENABLE_SQLITE
mpd_SOURCES += sticker.c song_sticker.c
endif
if HAVE_LIBSAMPLERATE
mpd_SOURCES += pcm_resample_libsamplerate.c
else
......@@ -273,13 +310,25 @@ mpd_SOURCES += decoder/audiofile_plugin.c
endif
if HAVE_MIKMOD
mpd_SOURCES += decoder/mod_plugin.c
mpd_SOURCES += decoder/mikmod_plugin.c
endif
if HAVE_MODPLUG
mpd_SOURCES += decoder/modplug_plugin.c
endif
if ENABLE_SIDPLAY
mpd_SOURCES += decoder/sidplay_plugin.cxx
endif
if ENABLE_FLUIDSYNTH
mpd_SOURCES += decoder/fluidsynth_plugin.c
endif
if ENABLE_WILDMIDI
mpd_SOURCES += decoder/wildmidi_plugin.c
endif
if HAVE_FFMPEG
mpd_SOURCES += decoder/ffmpeg_plugin.c
endif
......@@ -302,6 +351,10 @@ if HAVE_CURL
mpd_SOURCES += input_curl.c icy_metadata.c
endif
if ENABLE_MMS
mpd_SOURCES += input_mms.c
endif
if HAVE_ALSA
mpd_SOURCES += output/alsa_plugin.c
......
......@@ -148,22 +148,16 @@ bz2_close(struct archive_file *file)
/* single archive handling */
static void
bz2_setup_stream(struct archive_file *file, struct input_stream *is)
static bool
bz2_open_stream(struct archive_file *file, struct input_stream *is,
G_GNUC_UNUSED const char *path)
{
bz2_context *context = (bz2_context *) file;
//setup file ops
is->plugin = &bz2_inputplugin;
//insert back reference
is->archive = context;
is->data = context;
is->seekable = false;
}
static bool
bz2_is_open(struct input_stream *is, G_GNUC_UNUSED const char *url)
{
bz2_context *context = (bz2_context *) is->archive;
if (!bz2_alloc(context)) {
g_warning("alloc bz2 failed\n");
......@@ -175,7 +169,7 @@ bz2_is_open(struct input_stream *is, G_GNUC_UNUSED const char *url)
static void
bz2_is_close(struct input_stream *is)
{
bz2_context *context = (bz2_context *) is->archive;
bz2_context *context = (bz2_context *) is->data;
bz2_destroy(context);
is->data = NULL;
}
......@@ -211,7 +205,7 @@ bz2_fillbuffer(bz2_context *context,
static size_t
bz2_is_read(struct input_stream *is, void *ptr, size_t size)
{
bz2_context *context = (bz2_context *) is->archive;
bz2_context *context = (bz2_context *) is->data;
bz_stream *bzstream;
int bz_result;
size_t numBytes = size;
......@@ -253,7 +247,7 @@ bz2_is_read(struct input_stream *is, void *ptr, size_t size)
static bool
bz2_is_eof(struct input_stream *is)
{
bz2_context *context = (bz2_context *) is->archive;
bz2_context *context = (bz2_context *) is->data;
if (context->last_bz_result == BZ_STREAM_END) {
return true;
......@@ -262,19 +256,6 @@ bz2_is_eof(struct input_stream *is)
return false;
}
static bool
bz2_is_seek(G_GNUC_UNUSED struct input_stream *is,
G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
{
return false;
}
static int
bz2_is_buffer(G_GNUC_UNUSED struct input_stream *is)
{
return 0;
}
/* exported structures */
static const char *const bz2_extensions[] = {
......@@ -283,12 +264,9 @@ static const char *const bz2_extensions[] = {
};
static const struct input_plugin bz2_inputplugin = {
.open = bz2_is_open,
.close = bz2_is_close,
.read = bz2_is_read,
.eof = bz2_is_eof,
.seek = bz2_is_seek,
.buffer = bz2_is_buffer
};
const struct archive_plugin bz2_plugin = {
......@@ -296,7 +274,7 @@ const struct archive_plugin bz2_plugin = {
.open = bz2_open,
.scan_reset = bz2_scan_reset,
.scan_next = bz2_scan_next,
.setup_stream = bz2_setup_stream,
.open_stream = bz2_open_stream,
.close = bz2_close,
.suffixes = bz2_extensions
};
......
......@@ -136,23 +136,17 @@ iso_close(struct archive_file *file)
/* single archive handling */
static void
iso_setup_stream(struct archive_file *file, struct input_stream *is)
static bool
iso_open_stream(struct archive_file *file, struct input_stream *is,
const char *pathname)
{
iso_context *context = (iso_context *) file;
//setup file ops
is->plugin = &iso_inputplugin;
//insert back reference
is->archive = context;
is->data = context;
//we are not seekable
is->seekable = false;
}
static bool
iso_is_open(struct input_stream *is, const char *pathname)
{
iso_context *context = (iso_context *) is->archive;
context->statbuf = iso9660_ifs_stat_translate (context->iso, pathname);
......@@ -168,7 +162,7 @@ iso_is_open(struct input_stream *is, const char *pathname)
static void
iso_is_close(struct input_stream *is)
{
iso_context *context = (iso_context *) is->archive;
iso_context *context = (iso_context *) is->data;
g_free(context->statbuf);
}
......@@ -176,7 +170,7 @@ iso_is_close(struct input_stream *is)
static size_t
iso_is_read(struct input_stream *is, void *ptr, size_t size)
{
iso_context *context = (iso_context *) is->archive;
iso_context *context = (iso_context *) is->data;
int toread, readed = 0;
int no_blocks, cur_block;
size_t left_bytes = context->statbuf->size - context->cur_ofs;
......@@ -213,23 +207,10 @@ iso_is_read(struct input_stream *is, void *ptr, size_t size)
static bool
iso_is_eof(struct input_stream *is)
{
iso_context *context = (iso_context *) is->archive;
iso_context *context = (iso_context *) is->data;
return (context->cur_ofs == context->statbuf->size);
}
static bool
iso_is_seek(G_GNUC_UNUSED struct input_stream *is,
G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
{
return false;
}
static int
iso_is_buffer(G_GNUC_UNUSED struct input_stream *is)
{
return 0;
}
/* exported structures */
static const char *const iso_extensions[] = {
......@@ -238,12 +219,9 @@ static const char *const iso_extensions[] = {
};
static const struct input_plugin iso_inputplugin = {
.open = iso_is_open,
.close = iso_is_close,
.read = iso_is_read,
.eof = iso_is_eof,
.seek = iso_is_seek,
.buffer = iso_is_buffer
};
const struct archive_plugin iso_plugin = {
......@@ -251,7 +229,7 @@ const struct archive_plugin iso_plugin = {
.open = iso_open,
.scan_reset = iso_scan_reset,
.scan_next = iso_scan_next,
.setup_stream = iso_setup_stream,
.open_stream = iso_open_stream,
.close = iso_close,
.suffixes = iso_extensions
};
......@@ -103,24 +103,19 @@ zip_close(struct archive_file *file)
/* single archive handling */
static void
zip_setup_stream(struct archive_file *file, struct input_stream *is)
static bool
zip_open_stream(struct archive_file *file, struct input_stream *is,
const char *pathname)
{
zip_context *context = (zip_context *) file;
ZZIP_STAT z_stat;
//setup file ops
is->plugin = &zip_inputplugin;
//insert back reference
is->archive = context;
//we are not seekable
is->seekable = false;
}
static bool
zip_is_open(struct input_stream *is, const char *pathname)
{
zip_context *context = (zip_context *) is->archive;
ZZIP_STAT z_stat;
is->data = context;
//we are seekable (but its not recommendent to do so)
is->seekable = true;
context->file = zzip_file_open(context->dir, pathname, 0);
if (!context->file) {
......@@ -135,14 +130,14 @@ zip_is_open(struct input_stream *is, const char *pathname)
static void
zip_is_close(struct input_stream *is)
{
zip_context *context = (zip_context *) is->archive;
zip_context *context = (zip_context *) is->data;
zzip_file_close (context->file);
}
static size_t
zip_is_read(struct input_stream *is, void *ptr, size_t size)
{
zip_context *context = (zip_context *) is->archive;
zip_context *context = (zip_context *) is->data;
int ret;
ret = zzip_file_read(context->file, ptr, size);
if (ret < 0) {
......@@ -155,7 +150,7 @@ zip_is_read(struct input_stream *is, void *ptr, size_t size)
static bool
zip_is_eof(struct input_stream *is)
{
zip_context *context = (zip_context *) is->archive;
zip_context *context = (zip_context *) is->data;
return ((size_t) zzip_tell(context->file) == context->length);
}
......@@ -163,15 +158,15 @@ static bool
zip_is_seek(G_GNUC_UNUSED struct input_stream *is,
G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
{
zip_context *context = (zip_context *) is->data;
zzip_off_t ofs = zzip_seek(context->file, offset, whence);
if (ofs != -1) {
is->offset = ofs;
return true;
}
return false;
}
static int
zip_is_buffer(G_GNUC_UNUSED struct input_stream *is)
{
return 0;
}
/* exported structures */
static const char *const zip_extensions[] = {
......@@ -180,12 +175,10 @@ static const char *const zip_extensions[] = {
};
static const struct input_plugin zip_inputplugin = {
.open = zip_is_open,
.close = zip_is_close,
.read = zip_is_read,
.eof = zip_is_eof,
.seek = zip_is_seek,
.buffer = zip_is_buffer
};
const struct archive_plugin zip_plugin = {
......@@ -193,7 +186,7 @@ const struct archive_plugin zip_plugin = {
.open = zip_open,
.scan_reset = zip_scan_reset,
.scan_next = zip_scan_next,
.setup_stream = zip_setup_stream,
.open_stream = zip_open_stream,
.close = zip_close,
.suffixes = zip_extensions
};
......@@ -70,11 +70,12 @@ struct archive_plugin {
char *(*scan_next)(struct archive_file *);
/**
* this is used to setup input stream handle, to be able to read
* from archive. open method of inputstream can be the used to
* extract particular file
* Opens an input_stream of a file within the archive.
*
* @param path the path within the archive
*/
void (*setup_stream)(struct archive_file *, struct input_stream *is);
bool (*open_stream)(struct archive_file *, struct input_stream *is,
const char *path);
/**
* closes archive file.
......
......@@ -18,12 +18,9 @@
#include "audio.h"
#include "audio_format.h"
#include "output_api.h"
#include "output_control.h"
#include "audio_parser.h"
#include "output_internal.h"
#include "path.h"
#include "client.h"
#include "idle.h"
#include "output_all.h"
#include "mixer_api.h"
#include <glib.h>
......@@ -31,70 +28,7 @@
#include <assert.h>
#include <stdlib.h>
#define AUDIO_DEVICE_STATE "audio_device_state:"
#define AUDIO_BUFFER_SIZE 2*MPD_PATH_MAX
static struct audio_format configured_audio_format;
static struct audio_format input_audio_format;
static struct audio_output *audioOutputArray;
static unsigned int audioOutputArraySize;
unsigned int audio_output_count(void)
{
unsigned int nr = 0;
struct config_param *param = NULL;
while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param)))
nr++;
if (!nr)
nr = 1; /* we'll always have at least one device */
return nr;
}
/* make sure initPlayerData is called before this function!! */
void initAudioDriver(void)
{
struct config_param *param = NULL;
unsigned int i;
notify_init(&audio_output_client_notify);
audioOutputArraySize = audio_output_count();
audioOutputArray = g_new(struct audio_output, audioOutputArraySize);
for (i = 0; i < audioOutputArraySize; i++)
{
struct audio_output *output = &audioOutputArray[i];
unsigned int j;
param = config_get_next_param(CONF_AUDIO_OUTPUT, param);
/* only allow param to be NULL if there just one audioOutput */
assert(param || (audioOutputArraySize == 1));
if (!audio_output_init(output, param)) {
if (param)
{
g_error("problems configuring output device "
"defined at line %i\n", param->line);
}
else
{
g_error("No audio_output specified and unable to "
"detect a default audio output device\n");
}
}
/* require output names to be unique: */
for (j = 0; j < i; j++) {
if (!strcmp(output->name, audioOutputArray[j].name)) {
g_error("output devices with identical "
"names: %s\n", output->name);
}
}
}
}
void getOutputAudioFormat(const struct audio_format *inAudioFormat,
struct audio_format *outAudioFormat)
......@@ -106,68 +40,18 @@ void getOutputAudioFormat(const struct audio_format *inAudioFormat,
void initAudioConfig(void)
{
struct config_param *param = config_get_param(CONF_AUDIO_OUTPUT_FORMAT);
const struct config_param *param = config_get_param(CONF_AUDIO_OUTPUT_FORMAT);
GError *error = NULL;
bool ret;
if (NULL == param || NULL == param->value)
return;
if (0 != parseAudioConfig(&configured_audio_format, param->value)) {
g_error("error parsing \"%s\" at line %i\n",
CONF_AUDIO_OUTPUT_FORMAT, param->line);
}
}
int parseAudioConfig(struct audio_format *audioFormat, char *conf)
{
char *test;
memset(audioFormat, 0, sizeof(*audioFormat));
audioFormat->sample_rate = strtol(conf, &test, 10);
if (*test != ':') {
g_warning("error parsing audio output format: %s\n", conf);
return -1;
}
if (audioFormat->sample_rate <= 0) {
g_warning("sample rate %u is not >= 0\n",
audioFormat->sample_rate);
return -1;
}
audioFormat->bits = (uint8_t)strtoul(test + 1, &test, 10);
if (*test != ':') {
g_warning("error parsing audio output format: %s\n", conf);
return -1;
}
if (audioFormat->bits != 16 && audioFormat->bits != 24 &&
audioFormat->bits != 8) {
g_warning("bits %u can not be used for audio output\n",
audioFormat->bits);
return -1;
}
audioFormat->channels = (uint8_t)strtoul(test + 1, &test, 10);
if (*test != '\0') {
g_warning("error parsing audio output format: %s\n", conf);
return -1;
}
switch (audioFormat->channels) {
case 1:
case 2:
break;
default:
g_warning("channels %u can not be used for audio output\n",
audioFormat->channels);
return -1;
}
return 0;
ret = audio_format_parse(&configured_audio_format, param->value,
&error);
if (!ret)
g_error("error parsing \"%s\" at line %i: %s",
CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message);
}
void finishAudioConfig(void)
......@@ -175,267 +59,14 @@ void finishAudioConfig(void)
audio_format_clear(&configured_audio_format);
}
void finishAudioDriver(void)
{
unsigned int i;
for (i = 0; i < audioOutputArraySize; i++) {
audio_output_finish(&audioOutputArray[i]);
}
free(audioOutputArray);
audioOutputArray = NULL;
audioOutputArraySize = 0;
notify_deinit(&audio_output_client_notify);
}
bool
isCurrentAudioFormat(const struct audio_format *audioFormat)
{
assert(audioFormat != NULL);
return audio_format_equals(audioFormat, &input_audio_format);
}
static void audio_output_wait_all(void)
{
unsigned i;
while (1) {
int finished = 1;
for (i = 0; i < audioOutputArraySize; ++i)
if (audio_output_is_open(&audioOutputArray[i]) &&
!audio_output_command_is_finished(&audioOutputArray[i]))
finished = 0;
if (finished)
break;
notify_wait(&audio_output_client_notify);
};
}
static void syncAudioDeviceStates(void)
{
unsigned int i;
if (!audio_format_defined(&input_audio_format))
return;
for (i = 0; i < audioOutputArraySize; ++i)
audio_output_update(&audioOutputArray[i], &input_audio_format);
}
bool playAudio(const char *buffer, size_t length)
{
bool ret = false;
unsigned int i;
assert(length > 0);
/* no partial frames allowed */
assert((length % audio_format_frame_size(&input_audio_format)) == 0);
syncAudioDeviceStates();
for (i = 0; i < audioOutputArraySize; ++i)
if (audio_output_is_open(&audioOutputArray[i]))
audio_output_play(&audioOutputArray[i],
buffer, length);
while (true) {
bool finished = true;
for (i = 0; i < audioOutputArraySize; ++i) {
struct audio_output *ao = &audioOutputArray[i];
if (!audio_output_is_open(ao))
continue;
if (audio_output_command_is_finished(ao))
ret = true;
else {
finished = false;
audio_output_signal(ao);
}
}
if (finished)
break;
notify_wait(&audio_output_client_notify);
};
return ret;
}
bool openAudioDevice(const struct audio_format *audioFormat)
{
bool ret = false;
unsigned int i;
if (!audioOutputArray)
return false;
if (audioFormat != NULL)
input_audio_format = *audioFormat;
syncAudioDeviceStates();
for (i = 0; i < audioOutputArraySize; ++i) {
if (audioOutputArray[i].open)
ret = true;
}
if (!ret)
/* close all devices if there was an error */
closeAudioDevice();
return ret;
}
void audio_output_pause_all(void)
{
unsigned int i;
syncAudioDeviceStates();
for (i = 0; i < audioOutputArraySize; ++i)
if (audio_output_is_open(&audioOutputArray[i]))
audio_output_pause(&audioOutputArray[i]);
audio_output_wait_all();
}
void dropBufferedAudio(void)
{
unsigned int i;
syncAudioDeviceStates();
for (i = 0; i < audioOutputArraySize; ++i) {
if (audio_output_is_open(&audioOutputArray[i]))
audio_output_cancel(&audioOutputArray[i]);
}
audio_output_wait_all();
}
void closeAudioDevice(void)
{
unsigned int i;
for (i = 0; i < audioOutputArraySize; ++i)
audio_output_close(&audioOutputArray[i]);
}
void sendMetadataToAudioDevice(const struct tag *tag)
{
unsigned int i;
for (i = 0; i < audioOutputArraySize; ++i)
if (audio_output_is_open(&audioOutputArray[i]))
audio_output_send_tag(&audioOutputArray[i], tag);
audio_output_wait_all();
}
int enableAudioDevice(unsigned int device)
{
if (device >= audioOutputArraySize)
return -1;
audioOutputArray[device].reopen_after = 0;
audioOutputArray[device].enabled = true;
idle_add(IDLE_OUTPUT);
return 0;
}
int disableAudioDevice(unsigned int device)
{
if (device >= audioOutputArraySize)
return -1;
audioOutputArray[device].enabled = false;
idle_add(IDLE_OUTPUT);
return 0;
}
void printAudioDevices(struct client *client)
{
unsigned int i;
for (i = 0; i < audioOutputArraySize; i++) {
client_printf(client,
"outputid: %i\n"
"outputname: %s\n"
"outputenabled: %i\n",
i,
audioOutputArray[i].name,
audioOutputArray[i].enabled);
}
}
void saveAudioDevicesState(FILE *fp)
{
unsigned int i;
assert(audioOutputArraySize != 0);
for (i = 0; i < audioOutputArraySize; i++) {
fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n",
audioOutputArray[i].enabled,
audioOutputArray[i].name);
}
}
void readAudioDevicesState(FILE *fp)
{
char buffer[AUDIO_BUFFER_SIZE];
unsigned int i;
assert(audioOutputArraySize != 0);
while (fgets(buffer, sizeof(buffer), fp)) {
char *c, *name;
g_strchomp(buffer);
if (!g_str_has_prefix(buffer, AUDIO_DEVICE_STATE))
continue;
c = strchr(buffer, ':');
if (!c || !(++c))
goto errline;
name = strchr(c, ':');
if (!name || !(++name))
goto errline;
for (i = 0; i < audioOutputArraySize; ++i) {
if (!strcmp(name, audioOutputArray[i].name)) {
/* devices default to on */
if (!atoi(c))
audioOutputArray[i].enabled = false;
break;
}
}
continue;
errline:
/* nonfatal */
g_warning("invalid line in state_file: %s\n", buffer);
}
}
bool mixer_control_setvol(unsigned int device, int volume, int rel)
{
struct audio_output *output;
if (device >= audioOutputArraySize)
if (device >= audio_output_count())
return false;
output = &audioOutputArray[device];
output = audio_output_get(device);
if (output->plugin && output->plugin->control) {
if (rel) {
int cur_volume;
......@@ -457,29 +88,13 @@ bool mixer_control_setvol(unsigned int device, int volume, int rel)
bool mixer_control_getvol(unsigned int device, int *volume)
{
struct audio_output *output;
if (device >= audioOutputArraySize)
if (device >= audio_output_count())
return false;
output = &audioOutputArray[device];
output = audio_output_get(device);
if (output->plugin && output->plugin->control) {
return output->plugin->control(output->data, AC_MIXER_GETVOL, volume);
}
return false;
}
bool mixer_configure_legacy(char *name, struct config_param *param)
{
unsigned i;
struct audio_output *output;
for (i = 0; i < audioOutputArraySize; ++i) {
output = &audioOutputArray[i];
if (output && output->plugin && !strcmp(name, output->plugin->name)) {
if (output->plugin->control) {
g_debug("reconfiguring %s mixer\n", name);
return output->plugin->control(output->data, AC_MIXER_CONFIGURE, param);
}
}
}
return false;
}
......@@ -20,59 +20,18 @@
#define MPD_AUDIO_H
#include <stdbool.h>
#include <stdio.h>
#define AUDIO_AO_DRIVER_DEFAULT "default"
struct audio_format;
struct tag;
struct client;
struct config_param;
unsigned int audio_output_count(void);
void getOutputAudioFormat(const struct audio_format *inFormat,
struct audio_format *outFormat);
int parseAudioConfig(struct audio_format *audioFormat, char *conf);
/* make sure initPlayerData is called before this function!! */
void initAudioConfig(void);
void finishAudioConfig(void);
void initAudioDriver(void);
void finishAudioDriver(void);
bool openAudioDevice(const struct audio_format *audioFormat);
bool playAudio(const char *playChunk, size_t size);
void audio_output_pause_all(void);
void dropBufferedAudio(void);
void closeAudioDevice(void);
bool isCurrentAudioFormat(const struct audio_format *audioFormat);
void sendMetadataToAudioDevice(const struct tag *tag);
/* these functions are called in the main parent process while the child
process is busy playing to the audio */
int enableAudioDevice(unsigned int device);
int disableAudioDevice(unsigned int device);
void printAudioDevices(struct client *client);
void readAudioDevicesState(FILE *fp);
void saveAudioDevicesState(FILE *fp);
bool mixer_control_setvol(unsigned int device, int volume, int rel);
bool mixer_control_getvol(unsigned int device, int *volume);
bool mixer_configure_legacy(char *name, struct config_param *param);
#endif
......@@ -41,14 +41,45 @@ static inline bool audio_format_defined(const struct audio_format *af)
}
/**
* Checks whether the sample rate is valid.
*
* @param sample_rate the sample rate in Hz
*/
static inline bool
audio_valid_sample_rate(unsigned sample_rate)
{
return sample_rate > 0 && sample_rate < (1 << 30);
}
/**
* Checks whether the sample format is valid.
*
* @param bits the number of significant bits per sample
*/
static inline bool
audio_valid_sample_format(unsigned bits)
{
return bits == 16 || bits == 24 || bits == 8;
}
/**
* Checks whether the number of channels is valid.
*/
static inline bool
audio_valid_channel_count(unsigned channels)
{
return channels == 1 || channels == 2;
}
/**
* Returns false if the format is not valid for playback with MPD.
* This function performs some basic validity checks.
*/
static inline bool audio_format_valid(const struct audio_format *af)
{
return af->sample_rate > 0 &&
(af->bits == 8 || af->bits == 16 || af->bits == 24) &&
af->channels >= 1;
return audio_valid_sample_rate(af->sample_rate) &&
audio_valid_sample_format(af->bits) &&
audio_valid_channel_count(af->channels);
}
static inline bool audio_format_equals(const struct audio_format *a,
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Parser functions for audio related objects.
*
*/
#include "audio_parser.h"
#include "audio_format.h"
#include <stdlib.h>
/**
* The GLib quark used for errors reported by this library.
*/
static inline GQuark
audio_parser_quark(void)
{
return g_quark_from_static_string("audio_parser");
}
bool
audio_format_parse(struct audio_format *dest, const char *src, GError **error)
{
char *endptr;
unsigned long value;
audio_format_clear(dest);
/* parse sample rate */
value = strtoul(src, &endptr, 10);
if (endptr == src) {
g_set_error(error, audio_parser_quark(), 0,
"Sample rate missing");
return false;
} else if (*endptr != ':') {
g_set_error(error, audio_parser_quark(), 0,
"Sample format missing");
return false;
} else if (!audio_valid_sample_rate(value)) {
g_set_error(error, audio_parser_quark(), 0,
"Invalid sample rate: %lu", value);
return false;
}
dest->sample_rate = value;
/* parse sample format */
src = endptr + 1;
value = strtoul(src, &endptr, 10);
if (endptr == src) {
g_set_error(error, audio_parser_quark(), 0,
"Sample format missing");
return false;
} else if (*endptr != ':') {
g_set_error(error, audio_parser_quark(), 0,
"Channel count missing");
return false;
} else if (!audio_valid_sample_format(value)) {
g_set_error(error, audio_parser_quark(), 0,
"Invalid sample format: %lu", value);
return false;
}
dest->bits = value;
/* parse channel count */
src = endptr + 1;
value = strtoul(src, &endptr, 10);
if (*endptr != 0 || !audio_valid_channel_count(value)) {
g_set_error(error, audio_parser_quark(), 0,
"Invalid channel count: %s", src);
return false;
}
dest->channels = value;
return true;
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Parser functions for audio related objects.
*
*/
#ifndef AUDIO_PARSER_H
#define AUDIO_PARSER_H
#include <glib.h>
#include <stdbool.h>
struct audio_format;
bool
audio_format_parse(struct audio_format *dest, const char *src, GError **error);
#endif
......@@ -59,11 +59,9 @@ static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
/* set this to zero to indicate we have no possible clients */
static unsigned int client_max_connections; /*CLIENT_MAX_CONNECTIONS_DEFAULT; */
static int client_timeout = CLIENT_TIMEOUT_DEFAULT;
static size_t client_max_command_list_size =
CLIENT_MAX_COMMAND_LIST_DEFAULT;
static size_t client_max_output_buffer_size =
CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT;
static int client_timeout;
static size_t client_max_command_list_size;
static size_t client_max_output_buffer_size;
struct deferred_buffer {
size_t size;
......@@ -75,7 +73,6 @@ struct client {
size_t bufferLength;
size_t bufferPos;
int fd; /* file descriptor; -1 if expired */
GIOChannel *channel;
guint source_id;
......@@ -116,7 +113,7 @@ static void client_write_output(struct client *client);
bool client_is_expired(const struct client *client)
{
return client->fd < 0;
return client->channel == NULL;
}
int client_get_uid(const struct client *client)
......@@ -150,7 +147,7 @@ client_manager_expire_event(G_GNUC_UNUSED gpointer data)
static inline void client_set_expired(struct client *client)
{
if (expire_source_id == 0 && client->fd >= 0)
if (expire_source_id == 0 && !client_is_expired(client))
/* delayed deletion */
expire_source_id = g_idle_add(client_manager_expire_event,
NULL);
......@@ -164,11 +161,6 @@ static inline void client_set_expired(struct client *client)
g_io_channel_unref(client->channel);
client->channel = NULL;
}
if (client->fd >= 0) {
close(client->fd);
client->fd = -1;
}
}
static gboolean
......@@ -184,10 +176,23 @@ static void client_init(struct client *client, int fd)
client->cmd_list_OK = -1;
client->bufferLength = 0;
client->bufferPos = 0;
client->fd = fd;
client->channel = g_io_channel_unix_new(client->fd);
client->source_id = g_io_add_watch(client->channel, G_IO_IN,
#ifndef G_OS_WIN32
client->channel = g_io_channel_unix_new(fd);
#else
client->channel = g_io_channel_win32_new_socket(fd);
#endif
/* GLib is responsible for closing the file descriptor */
g_io_channel_set_close_on_unref(client->channel, true);
/* NULL encoding means the stream is binary safe; the MPD
protocol is UTF-8 only, but we are doing this call anyway
to prevent GLib from messing around with the stream */
g_io_channel_set_encoding(client->channel, NULL, NULL);
/* we prefer to do buffering */
g_io_channel_set_buffered(client->channel, false);
client->source_id = g_io_add_watch(client->channel,
G_IO_IN|G_IO_ERR|G_IO_HUP,
client_in_event, client);
client->lastTime = time(NULL);
......@@ -242,7 +247,7 @@ static void client_close(struct client *client)
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
"client %i: closed", client->num);
free(client);
g_free(client);
}
static const char *
......@@ -457,33 +462,49 @@ static int client_input_received(struct client *client, size_t bytesRead)
static int client_read(struct client *client)
{
ssize_t bytesRead;
GError *error = NULL;
GIOStatus status;
gsize bytes_read;
assert(client->bufferPos <= client->bufferLength);
assert(client->bufferLength < sizeof(client->buffer));
bytesRead = read(client->fd,
client->buffer + client->bufferLength,
sizeof(client->buffer) - client->bufferLength);
status = g_io_channel_read_chars
(client->channel,
client->buffer + client->bufferLength,
sizeof(client->buffer) - client->bufferLength,
&bytes_read, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
return client_input_received(client, bytes_read);
if (bytesRead > 0)
return client_input_received(client, bytesRead);
else if (bytesRead < 0 && errno == EINTR)
case G_IO_STATUS_AGAIN:
/* try again later, after select() */
return 0;
else
/* peer disconnected or I/O error */
case G_IO_STATUS_EOF:
/* peer disconnected */
return COMMAND_RETURN_CLOSE;
case G_IO_STATUS_ERROR:
/* I/O error */
g_warning("failed to read from client %d: %s",
client->num, error->message);
g_error_free(error);
return COMMAND_RETURN_CLOSE;
}
/* unreachable */
return COMMAND_RETURN_CLOSE;
}
static gboolean
client_out_event(G_GNUC_UNUSED GIOChannel *source,
G_GNUC_UNUSED GIOCondition condition,
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
gpointer data);
static gboolean
client_in_event(G_GNUC_UNUSED GIOChannel *source,
G_GNUC_UNUSED GIOCondition condition,
GIOCondition condition,
gpointer data)
{
struct client *client = data;
......@@ -491,6 +512,11 @@ client_in_event(G_GNUC_UNUSED GIOChannel *source,
assert(!client_is_expired(client));
if (condition != G_IO_IN) {
client_set_expired(client);
return false;
}
client->lastTime = time(NULL);
ret = client_read(client);
......@@ -512,7 +538,8 @@ client_in_event(G_GNUC_UNUSED GIOChannel *source,
if (!g_queue_is_empty(client->deferred_send)) {
/* deferred buffers exist: schedule write */
client->source_id = g_io_add_watch(client->channel, G_IO_OUT,
client->source_id = g_io_add_watch(client->channel,
G_IO_OUT|G_IO_ERR|G_IO_HUP,
client_out_event, client);
return false;
}
......@@ -522,14 +549,18 @@ client_in_event(G_GNUC_UNUSED GIOChannel *source,
}
static gboolean
client_out_event(G_GNUC_UNUSED GIOChannel *source,
G_GNUC_UNUSED GIOCondition condition,
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
gpointer data)
{
struct client *client = data;
assert(!client_is_expired(client));
if (condition != G_IO_OUT) {
client_set_expired(client);
return false;
}
client_write_deferred(client);
if (client_is_expired(client)) {
......@@ -542,7 +573,8 @@ client_out_event(G_GNUC_UNUSED GIOChannel *source,
if (g_queue_is_empty(client->deferred_send)) {
/* done sending deferred buffers exist: schedule
read */
client->source_id = g_io_add_watch(client->channel, G_IO_IN,
client->source_id = g_io_add_watch(client->channel,
G_IO_IN|G_IO_ERR|G_IO_HUP,
client_in_event, client);
return false;
}
......@@ -553,55 +585,20 @@ client_out_event(G_GNUC_UNUSED GIOChannel *source,
void client_manager_init(void)
{
char *test;
struct config_param *param;
client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
CLIENT_TIMEOUT_DEFAULT);
client_max_connections =
config_get_positive(CONF_MAX_CONN,
CLIENT_MAX_CONNECTIONS_DEFAULT);
client_max_command_list_size =
config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
* 1024;
param = config_get_param(CONF_CONN_TIMEOUT);
if (param) {
client_timeout = strtol(param->value, &test, 10);
if (*test != '\0' || client_timeout <= 0) {
g_error("connection timeout \"%s\" is not a positive "
"integer, line %i",
CONF_CONN_TIMEOUT, param->line);
}
}
param = config_get_param(CONF_MAX_CONN);
if (param) {
client_max_connections = strtol(param->value, &test, 10);
if (*test != '\0' || client_max_connections <= 0) {
g_error("max connections \"%s\" is not a positive integer"
", line %i",
param->value, param->line);
}
} else
client_max_connections = CLIENT_MAX_CONNECTIONS_DEFAULT;
param = config_get_param(CONF_MAX_COMMAND_LIST_SIZE);
if (param) {
long tmp = strtol(param->value, &test, 10);
if (*test != '\0' || tmp <= 0) {
g_error("max command list size \"%s\" is not a positive "
"integer, line %i",
param->value, param->line);
}
client_max_command_list_size = tmp * 1024;
}
param = config_get_param(CONF_MAX_OUTPUT_BUFFER_SIZE);
if (param) {
long tmp = strtol(param->value, &test, 10);
if (*test != '\0' || tmp <= 0) {
g_error("max output buffer size \"%s\" is not a positive "
"integer, line %i",
param->value, param->line);
}
client_max_output_buffer_size = tmp * 1024;
}
client_max_output_buffer_size =
config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
* 1024;
}
static void client_close_all(void)
......@@ -648,9 +645,47 @@ client_manager_expire(void)
g_list_foreach(clients, client_check_expired_callback, NULL);
}
static size_t
client_write_deferred_buffer(struct client *client,
const struct deferred_buffer *buffer)
{
GError *error = NULL;
GIOStatus status;
gsize bytes_written;
status = g_io_channel_write_chars
(client->channel, buffer->data, buffer->size,
&bytes_written, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
return bytes_written;
case G_IO_STATUS_AGAIN:
return 0;
case G_IO_STATUS_EOF:
/* client has disconnected */
client_set_expired(client);
return 0;
case G_IO_STATUS_ERROR:
/* I/O error */
client_set_expired(client);
g_warning("failed to flush buffer for %i: %s",
client->num, error->message);
g_error_free(error);
return 0;
}
/* unreachable */
return 0;
}
static void client_write_deferred(struct client *client)
{
ssize_t ret = 0;
size_t ret;
while (!g_queue_is_empty(client->deferred_send)) {
struct deferred_buffer *buf =
......@@ -659,10 +694,11 @@ static void client_write_deferred(struct client *client)
assert(buf->size > 0);
assert(buf->size <= client->deferred_bytes);
ret = write(client->fd, buf->data, buf->size);
if (ret < 0)
ret = client_write_deferred_buffer(client, buf);
if (ret == 0)
break;
else if ((size_t)ret < buf->size) {
if (ret < buf->size) {
assert(client->deferred_bytes >= (size_t)ret);
client->deferred_bytes -= ret;
buf->size -= ret;
......@@ -674,9 +710,10 @@ static void client_write_deferred(struct client *client)
assert(client->deferred_bytes >= decr);
client->deferred_bytes -= decr;
free(buf);
g_free(buf);
g_queue_pop_head(client->deferred_send);
}
client->lastTime = time(NULL);
}
......@@ -684,11 +721,6 @@ static void client_write_deferred(struct client *client)
g_debug("client %i: buffer empty %lu", client->num,
(unsigned long)client->deferred_bytes);
assert(client->deferred_bytes == 0);
} else if (ret < 0 && errno != EAGAIN && errno != EINTR) {
/* cause client to close */
g_debug("client %i: problems flushing buffer",
client->num);
client_set_expired(client);
}
}
......@@ -723,23 +755,40 @@ static void client_defer_output(struct client *client,
static void client_write_direct(struct client *client,
const char *data, size_t length)
{
ssize_t ret;
GError *error = NULL;
GIOStatus status;
gsize bytes_written;
assert(length > 0);
assert(g_queue_is_empty(client->deferred_send));
if ((ret = write(client->fd, data, length)) < 0) {
if (errno == EAGAIN || errno == EINTR) {
client_defer_output(client, data, length);
} else {
g_debug("client %i: problems writing", client->num);
client_set_expired(client);
return;
}
} else if ((size_t)ret < client->send_buf_used) {
client_defer_output(client, data + ret, length - ret);
status = g_io_channel_write_chars(client->channel, data, length,
&bytes_written, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
case G_IO_STATUS_AGAIN:
break;
case G_IO_STATUS_EOF:
/* client has disconnected */
client_set_expired(client);
return;
case G_IO_STATUS_ERROR:
/* I/O error */
client_set_expired(client);
g_warning("failed to write to %i: %s",
client->num, error->message);
g_error_free(error);
return;
}
if (bytes_written < length)
client_defer_output(client, data + bytes_written,
length - bytes_written);
if (!g_queue_is_empty(client->deferred_send))
g_debug("client %i: buffer created", client->num);
}
......@@ -815,7 +864,7 @@ void client_vprintf(struct client *client, const char *fmt, va_list args)
buffer = g_malloc(length + 1);
vsnprintf(buffer, length + 1, fmt, args);
client_write(client, buffer, length);
free(buffer);
g_free(buffer);
}
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
......
......@@ -19,6 +19,9 @@
#include "command.h"
#include "player_control.h"
#include "playlist.h"
#include "playlist_print.h"
#include "playlist_save.h"
#include "queue_print.h"
#include "ls.h"
#include "directory.h"
#include "directory_print.h"
......@@ -31,7 +34,9 @@
#include "log.h"
#include "stored_playlist.h"
#include "ack.h"
#include "audio.h"
#include "output_command.h"
#include "output_print.h"
#include "locate.h"
#include "dbUtils.h"
#include "tag.h"
#include "client.h"
......@@ -40,6 +45,11 @@
#include "idle.h"
#include "config.h"
#ifdef ENABLE_SQLITE
#include "sticker.h"
#include "song_sticker.h"
#endif
#include <assert.h>
#include <time.h>
#include <stdlib.h>
......@@ -53,6 +63,8 @@
#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength"
#define COMMAND_STATUS_SONG "song"
#define COMMAND_STATUS_SONGID "songid"
#define COMMAND_STATUS_NEXTSONG "nextsong"
#define COMMAND_STATUS_NEXTSONGID "nextsongid"
#define COMMAND_STATUS_TIME "time"
#define COMMAND_STATUS_BITRATE "bitrate"
#define COMMAND_STATUS_ERROR "error"
......@@ -320,6 +332,11 @@ print_playlist_result(struct client *client,
command_error(client, ACK_ERROR_PLAYLIST_MAX,
"playlist is at the max size");
return COMMAND_RETURN_ERROR;
case PLAYLIST_RESULT_DISABLED:
command_error(client, ACK_ERROR_UNKNOWN,
"stored playlist support is disabled");
return COMMAND_RETURN_ERROR;
}
assert(0);
......@@ -378,7 +395,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 = playPlaylist(song, 0);
result = playPlaylist(&g_playlist, song);
return print_playlist_result(client, result);
}
......@@ -391,7 +408,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 = playPlaylistById(id, 0);
result = playPlaylistById(&g_playlist, id);
return print_playlist_result(client, result);
}
......@@ -399,7 +416,7 @@ static enum command_return
handle_stop(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
stopPlaylist();
stopPlaylist(&g_playlist);
return COMMAND_RETURN_OK;
}
......@@ -407,14 +424,8 @@ static enum command_return
handle_currentsong(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
int song = getPlaylistCurrentSong();
enum playlist_result result;
if (song < 0)
return COMMAND_RETURN_OK;
result = playlistInfo(client, song, song + 1);
return print_playlist_result(client, result);
playlist_print_current(client, &g_playlist);
return PLAYLIST_RESULT_SUCCESS;
}
static enum command_return
......@@ -441,7 +452,6 @@ handle_status(struct client *client,
int updateJobId;
int song;
playPlaylistIfPlayerStopped();
switch (getPlayerState()) {
case PLAYER_STATE_STOP:
state = "stop";
......@@ -463,19 +473,19 @@ handle_status(struct client *client,
COMMAND_STATUS_CROSSFADE ": %i\n"
COMMAND_STATUS_STATE ": %s\n",
volume_level_get(),
getPlaylistRepeatStatus(),
getPlaylistRandomStatus(),
getPlaylistVersion(),
getPlaylistLength(),
getPlaylistRepeatStatus(&g_playlist),
getPlaylistRandomStatus(&g_playlist),
getPlaylistVersion(&g_playlist),
getPlaylistLength(&g_playlist),
(int)(getPlayerCrossFade() + 0.5),
state);
song = getPlaylistCurrentSong();
song = getPlaylistCurrentSong(&g_playlist);
if (song >= 0) {
client_printf(client,
COMMAND_STATUS_SONG ": %i\n"
COMMAND_STATUS_SONGID ": %u\n",
song, getPlaylistSongId(song));
song, getPlaylistSongId(&g_playlist, song));
}
if (getPlayerState() != PLAYER_STATE_STOP) {
......@@ -501,6 +511,14 @@ handle_status(struct client *client,
getPlayerErrorStr());
}
song = getPlaylistNextSong(&g_playlist);
if (song >= 0) {
client_printf(client,
COMMAND_STATUS_NEXTSONG ": %i\n"
COMMAND_STATUS_NEXTSONGID ": %u\n",
song, getPlaylistSongId(&g_playlist, song));
}
return COMMAND_RETURN_OK;
}
......@@ -528,7 +546,8 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
#ifdef WIN32
result = PLAYLIST_RESULT_DENIED;
#else
result = playlist_append_file(uri + 7, client_get_uid(client),
result = playlist_append_file(&g_playlist,
uri + 7, client_get_uid(client),
NULL);
#endif
return print_playlist_result(client, result);
......@@ -541,7 +560,7 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
return addToPlaylist(uri, NULL);
return addToPlaylist(&g_playlist, uri, NULL);
}
result = addAllIn(uri);
......@@ -565,7 +584,7 @@ handle_addid(struct client *client, int argc, char *argv[])
#ifdef WIN32
result = PLAYLIST_RESULT_DENIED;
#else
result = playlist_append_file(uri + 7,
result = playlist_append_file(&g_playlist, uri + 7,
client_get_uid(client),
&added_id);
#endif
......@@ -576,7 +595,7 @@ handle_addid(struct client *client, int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
result = addToPlaylist(uri, &added_id);
result = addToPlaylist(&g_playlist, uri, &added_id);
}
if (result != PLAYLIST_RESULT_SUCCESS)
......@@ -586,11 +605,11 @@ 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 = moveSongInPlaylistById(added_id, to);
result = moveSongInPlaylistById(&g_playlist, added_id, to);
if (result != PLAYLIST_RESULT_SUCCESS) {
enum command_return ret =
print_playlist_result(client, result);
deleteFromPlaylistById(added_id);
deleteFromPlaylistById(&g_playlist, added_id);
return ret;
}
}
......@@ -608,7 +627,7 @@ handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &song, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
result = deleteFromPlaylist(song);
result = deleteFromPlaylist(&g_playlist, song);
return print_playlist_result(client, result);
}
......@@ -621,7 +640,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 = deleteFromPlaylistById(id);
result = deleteFromPlaylistById(&g_playlist, id);
return print_playlist_result(client, result);
}
......@@ -629,7 +648,7 @@ static enum command_return
handle_playlist(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
showPlaylist(client);
playlist_print_uris(client, &g_playlist);
return COMMAND_RETURN_OK;
}
......@@ -637,7 +656,12 @@ static enum command_return
handle_shuffle(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
shufflePlaylist();
unsigned start = 0, end = queue_length(&g_playlist.queue);
if (argc == 2 && !check_range(client, &start, &end,
argv[1], need_range))
return COMMAND_RETURN_ERROR;
shufflePlaylist(&g_playlist, start, end);
return COMMAND_RETURN_OK;
}
......@@ -645,7 +669,7 @@ static enum command_return
handle_clear(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
clearPlaylist();
clearPlaylist(&g_playlist);
return COMMAND_RETURN_OK;
}
......@@ -655,7 +679,7 @@ handle_save(struct client *client,
{
enum playlist_result result;
result = savePlaylist(argv[1]);
result = spl_save_playlist(argv[1], &g_playlist);
return print_playlist_result(client, result);
}
......@@ -664,33 +688,37 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
enum playlist_result result;
result = loadPlaylist(argv[1]);
result = playlist_load_spl(&g_playlist, argv[1]);
return print_playlist_result(client, result);
}
static enum command_return
handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
int ret;
bool ret;
ret = PlaylistInfo(client, argv[1], 0);
if (ret == -1)
ret = spl_print(client, argv[1], false);
if (!ret) {
command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
return COMMAND_RETURN_ERROR;
}
return ret;
return COMMAND_RETURN_OK;
}
static enum command_return
handle_listplaylistinfo(struct client *client,
G_GNUC_UNUSED int argc, char *argv[])
{
int ret;
bool ret;
ret = PlaylistInfo(client, argv[1], 1);
if (ret == -1)
ret = spl_print(client, argv[1], true);
if (!ret) {
command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
return COMMAND_RETURN_ERROR;
}
return ret;
return COMMAND_RETURN_OK;
}
static enum command_return
......@@ -750,7 +778,9 @@ handle_plchanges(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_uint32(client, &version, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
return playlistChanges(client, version);
playlist_print_changes_info(client, &g_playlist, version);
return COMMAND_RETURN_OK;
}
static enum command_return
......@@ -760,57 +790,70 @@ handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[
if (!check_uint32(client, &version, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
return playlistChangesPosId(client, version);
playlist_print_changes_position(client, &g_playlist, version);
return COMMAND_RETURN_OK;
}
static enum command_return
handle_playlistinfo(struct client *client, int argc, char *argv[])
{
unsigned start = 0, end = UINT_MAX;
enum playlist_result result;
bool ret;
if (argc == 2 && !check_range(client, &start, &end,
argv[1], need_range))
return COMMAND_RETURN_ERROR;
result = playlistInfo(client, start, end);
return print_playlist_result(client, result);
ret = playlist_print_info(client, &g_playlist, start, end);
if (!ret)
return print_playlist_result(client,
PLAYLIST_RESULT_BAD_RANGE);
return COMMAND_RETURN_OK;
}
static enum command_return
handle_playlistid(struct client *client, int argc, char *argv[])
{
int id = -1;
enum playlist_result result;
if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
result = playlistId(client, id);
return print_playlist_result(client, result);
if (id >= 0) {
bool ret = playlist_print_id(client, &g_playlist, id);
if (!ret)
return print_playlist_result(client,
PLAYLIST_RESULT_NO_SUCH_SONG);
} else {
playlist_print_info(client, &g_playlist, 0, UINT_MAX);
}
return COMMAND_RETURN_OK;
}
static enum command_return
handle_find(struct client *client, int argc, char *argv[])
{
int ret;
struct locate_item_list *list =
locate_item_list_parse(argv + 1, argc - 1);
LocateTagItem *items;
int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
argc - 1,
&items);
if (list == NULL || list->length == 0) {
if (list != NULL)
locate_item_list_free(list);
if (numItems <= 0) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
return COMMAND_RETURN_ERROR;
}
ret = findSongsIn(client, NULL, numItems, items);
ret = findSongsIn(client, NULL, list);
if (ret == -1)
command_error(client, ACK_ERROR_NO_EXIST,
"directory or file not found");
freeLocateTagItemArray(numItems, items);
locate_item_list_free(list);
return ret;
}
......@@ -819,23 +862,23 @@ static enum command_return
handle_search(struct client *client, int argc, char *argv[])
{
int ret;
struct locate_item_list *list =
locate_item_list_parse(argv + 1, argc - 1);
LocateTagItem *items;
int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
argc - 1,
&items);
if (list == NULL || list->length == 0) {
if (list != NULL)
locate_item_list_free(list);
if (numItems <= 0) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
return COMMAND_RETURN_ERROR;
}
ret = searchForSongsIn(client, NULL, numItems, items);
ret = searchForSongsIn(client, NULL, list);
if (ret == -1)
command_error(client, ACK_ERROR_NO_EXIST,
"directory or file not found");
freeLocateTagItemArray(numItems, items);
locate_item_list_free(list);
return ret;
}
......@@ -844,23 +887,23 @@ static enum command_return
handle_count(struct client *client, int argc, char *argv[])
{
int ret;
struct locate_item_list *list =
locate_item_list_parse(argv + 1, argc - 1);
LocateTagItem *items;
int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
argc - 1,
&items);
if (list == NULL || list->length == 0) {
if (list != NULL)
locate_item_list_free(list);
if (numItems <= 0) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
return COMMAND_RETURN_ERROR;
}
ret = searchStatsForSongsIn(client, NULL, numItems, items);
ret = searchStatsForSongsIn(client, NULL, list);
if (ret == -1)
command_error(client, ACK_ERROR_NO_EXIST,
"directory or file not found");
freeLocateTagItemArray(numItems, items);
locate_item_list_free(list);
return ret;
}
......@@ -868,19 +911,20 @@ handle_count(struct client *client, int argc, char *argv[])
static enum command_return
handle_playlistfind(struct client *client, int argc, char *argv[])
{
LocateTagItem *items;
int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
argc - 1,
&items);
struct locate_item_list *list =
locate_item_list_parse(argv + 1, argc - 1);
if (list == NULL || list->length == 0) {
if (list != NULL)
locate_item_list_free(list);
if (numItems <= 0) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
return COMMAND_RETURN_ERROR;
}
findSongsInPlaylist(client, numItems, items);
playlist_print_find(client, &g_playlist, list);
freeLocateTagItemArray(numItems, items);
locate_item_list_free(list);
return COMMAND_RETURN_OK;
}
......@@ -888,19 +932,20 @@ handle_playlistfind(struct client *client, int argc, char *argv[])
static enum command_return
handle_playlistsearch(struct client *client, int argc, char *argv[])
{
LocateTagItem *items;
int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
argc - 1,
&items);
struct locate_item_list *list =
locate_item_list_parse(argv + 1, argc - 1);
if (list == NULL || list->length == 0) {
if (list != NULL)
locate_item_list_free(list);
if (numItems <= 0) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
return COMMAND_RETURN_ERROR;
}
searchForSongsInPlaylist(client, numItems, items);
playlist_print_search(client, &g_playlist, list);
freeLocateTagItemArray(numItems, items);
locate_item_list_free(list);
return COMMAND_RETURN_OK;
}
......@@ -960,7 +1005,7 @@ static enum command_return
handle_next(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
nextSongInPlaylist();
nextSongInPlaylist(&g_playlist);
return COMMAND_RETURN_OK;
}
......@@ -968,7 +1013,7 @@ static enum command_return
handle_previous(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
previousSongInPlaylist();
previousSongInPlaylist(&g_playlist);
return COMMAND_RETURN_OK;
}
......@@ -1035,7 +1080,7 @@ handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
setPlaylistRepeatStatus(status);
setPlaylistRepeatStatus(&g_playlist, status);
return COMMAND_RETURN_OK;
}
......@@ -1053,7 +1098,7 @@ handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
setPlaylistRandomStatus(status);
setPlaylistRandomStatus(&g_playlist, status);
return COMMAND_RETURN_OK;
}
......@@ -1061,7 +1106,7 @@ static enum command_return
handle_stats(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
return printStats(client);
return stats_print(client);
}
static enum command_return
......@@ -1075,9 +1120,8 @@ handle_clearerror(G_GNUC_UNUSED struct client *client,
static enum command_return
handle_list(struct client *client, int argc, char *argv[])
{
int numConditionals;
LocateTagItem *conditionals = NULL;
int tagType = getLocateTagItemType(argv[1]);
struct locate_item_list *conditionals;
int tagType = locate_parse_type(argv[1]);
int ret;
if (tagType < 0) {
......@@ -1099,25 +1143,25 @@ handle_list(struct client *client, int argc, char *argv[])
mpdTagItemKeys[TAG_ITEM_ALBUM]);
return COMMAND_RETURN_ERROR;
}
conditionals = newLocateTagItem(mpdTagItemKeys[TAG_ITEM_ARTIST],
argv[2]);
numConditionals = 1;
} else {
numConditionals =
newLocateTagItemArrayFromArgArray(argv + 2,
argc - 2, &conditionals);
if (numConditionals < 0) {
locate_item_list_parse(argv + 1, argc - 1);
conditionals = locate_item_list_new(1);
conditionals->items[0].tag = TAG_ITEM_ARTIST;
conditionals->items[0].needle = g_strdup(argv[2]);
} else {
conditionals =
locate_item_list_parse(argv + 2, argc - 2);
if (conditionals == NULL) {
command_error(client, ACK_ERROR_ARG,
"not able to parse args");
return COMMAND_RETURN_ERROR;
}
}
ret = listAllUniqueTags(client, tagType, numConditionals, conditionals);
ret = listAllUniqueTags(client, tagType, conditionals);
if (conditionals)
freeLocateTagItemArray(numConditionals, conditionals);
locate_item_list_free(conditionals);
if (ret == -1)
command_error(client, ACK_ERROR_NO_EXIST,
......@@ -1136,7 +1180,7 @@ 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 = moveSongInPlaylist(from, to);
result = moveSongInPlaylist(&g_playlist, from, to);
return print_playlist_result(client, result);
}
......@@ -1150,7 +1194,7 @@ 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 = moveSongInPlaylistById(id, to);
result = moveSongInPlaylistById(&g_playlist, id, to);
return print_playlist_result(client, result);
}
......@@ -1164,7 +1208,7 @@ 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 = swapSongsInPlaylist(song1, song2);
result = swapSongsInPlaylist(&g_playlist, song1, song2);
return print_playlist_result(client, result);
}
......@@ -1178,7 +1222,7 @@ 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 = swapSongsInPlaylistById(id1, id2);
result = swapSongsInPlaylistById(&g_playlist, id1, id2);
return print_playlist_result(client, result);
}
......@@ -1193,7 +1237,7 @@ 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 = seekSongInPlaylist(song, seek_time);
result = seekSongInPlaylist(&g_playlist, song, seek_time);
return print_playlist_result(client, result);
}
......@@ -1208,7 +1252,7 @@ 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 = seekSongInPlaylistById(id, seek_time);
result = seekSongInPlaylistById(&g_playlist, id, seek_time);
return print_playlist_result(client, result);
}
......@@ -1267,34 +1311,38 @@ static enum command_return
handle_enableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
unsigned device;
int ret;
bool ret;
if (!check_unsigned(client, &device, argv[1]))
return COMMAND_RETURN_ERROR;
ret = enableAudioDevice(device);
if (ret == -1)
ret = audio_output_enable_index(device);
if (!ret) {
command_error(client, ACK_ERROR_NO_EXIST,
"No such audio output");
return COMMAND_RETURN_ERROR;
}
return ret;
return COMMAND_RETURN_OK;
}
static enum command_return
handle_disableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
unsigned device;
int ret;
bool ret;
if (!check_unsigned(client, &device, argv[1]))
return COMMAND_RETURN_ERROR;
ret = disableAudioDevice(device);
if (ret == -1)
ret = audio_output_disable_index(device);
if (!ret) {
command_error(client, ACK_ERROR_NO_EXIST,
"No such audio output");
return COMMAND_RETURN_ERROR;
}
return ret;
return COMMAND_RETURN_OK;
}
static enum command_return
......@@ -1398,6 +1446,70 @@ handle_idle(struct client *client,
return 1;
}
#ifdef ENABLE_SQLITE
static enum command_return
handle_sticker_song(struct client *client, int argc, char *argv[])
{
struct song *song = db_get_song(argv[3]);
if (song == NULL) {
command_error(client, ACK_ERROR_NO_EXIST,
"no such song");
return COMMAND_RETURN_ERROR;
}
if (argc == 5 && strcmp(argv[1], "get") == 0) {
char *value;
value = sticker_song_get_value(song, argv[4]);
if (value == NULL) {
command_error(client, ACK_ERROR_NO_EXIST,
"no such sticker");
return COMMAND_RETURN_ERROR;
}
client_printf(client, "sticker: %s=%s\n", argv[4], value);
g_free(value);
return COMMAND_RETURN_OK;
} else if (argc == 6 && strcmp(argv[1], "set") == 0) {
bool ret;
ret = sticker_song_set_value(song, argv[4], argv[5]);
if (!ret) {
command_error(client, ACK_ERROR_SYSTEM,
"failed to set sticker vqalue");
return COMMAND_RETURN_ERROR;
}
return COMMAND_RETURN_OK;
} else {
command_error(client, ACK_ERROR_ARG, "bad request");
return COMMAND_RETURN_ERROR;
}
}
static enum command_return
handle_sticker(struct client *client, int argc, char *argv[])
{
assert(argc >= 4);
if (!sticker_enabled()) {
command_error(client, ACK_ERROR_UNKNOWN,
"sticker database is disabled");
return COMMAND_RETURN_ERROR;
}
if (strcmp(argv[2], "song") == 0)
return handle_sticker_song(client, argc, argv);
else {
command_error(client, ACK_ERROR_ARG,
"unknown sticker domain");
return COMMAND_RETURN_ERROR;
}
}
#endif
/**
* The command registry.
*
......@@ -1459,9 +1571,12 @@ static const struct command commands[] = {
{ "seek", PERMISSION_CONTROL, 2, 2, handle_seek },
{ "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid },
{ "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol },
{ "shuffle", PERMISSION_CONTROL, 0, 0, handle_shuffle },
{ "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle },
{ "stats", PERMISSION_READ, 0, 0, handle_stats },
{ "status", PERMISSION_READ, 0, 0, handle_status },
#ifdef ENABLE_SQLITE
{ "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker },
#endif
{ "stop", PERMISSION_CONTROL, 0, 0, handle_stop },
{ "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
......@@ -1473,6 +1588,17 @@ static const struct command commands[] = {
static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);
static bool
command_available(G_GNUC_UNUSED const struct command *cmd)
{
#ifdef ENABLE_SQLITE
if (strcmp(cmd->cmd, "sticker") == 0)
return sticker_enabled();
#endif
return true;
}
/* don't be fooled, this is the command handler for "commands" command */
static enum command_return
handle_commands(struct client *client,
......@@ -1484,7 +1610,8 @@ handle_commands(struct client *client,
for (unsigned i = 0; i < num_commands; ++i) {
cmd = &commands[i];
if (cmd->permission == (permission & cmd->permission))
if (cmd->permission == (permission & cmd->permission) &&
command_available(cmd))
client_printf(client, "command: %s\n", cmd->cmd);
}
......
......@@ -23,8 +23,10 @@
#include <glib.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define MAX_STRING_SIZE MPD_PATH_MAX+80
......@@ -171,6 +173,7 @@ void config_global_init(void)
registerConfigParam(CONF_FOLLOW_INSIDE_SYMLINKS, 0, 0);
registerConfigParam(CONF_FOLLOW_OUTSIDE_SYMLINKS, 0, 0);
registerConfigParam(CONF_DB_FILE, 0, 0);
registerConfigParam(CONF_STICKER_FILE, false, false);
registerConfigParam(CONF_LOG_FILE, 0, 0);
registerConfigParam(CONF_ERROR_FILE, 0, 0);
registerConfigParam(CONF_PID_FILE, 0, 0);
......@@ -347,8 +350,17 @@ void config_read_file(const char *file)
fclose(fp);
}
void
config_add_param(const char *name, struct config_param *param)
{
struct config_entry *entry = config_entry_get(name);
assert(entry != NULL);
entry->params = g_slist_append(entry->params, param);
}
struct config_param *
config_get_next_param(const char *name, struct config_param * last)
config_get_next_param(const char *name, const struct config_param * last)
{
struct config_entry *entry;
GSList *node;
......@@ -379,7 +391,7 @@ config_get_next_param(const char *name, struct config_param * last)
const char *
config_get_string(const char *name, const char *default_value)
{
struct config_param *param = config_get_param(name);
const struct config_param *param = config_get_param(name);
if (param == NULL)
return default_value;
......@@ -387,12 +399,53 @@ config_get_string(const char *name, const char *default_value)
return param->value;
}
const char *
config_get_path(const char *name)
{
struct config_param *param = config_get_param(name);
char *path;
if (param == NULL)
return NULL;
path = parsePath(param->value);
if (path == NULL)
g_error("error parsing \"%s\" at line %i\n",
name, param->line);
g_free(param->value);
return param->value = path;
}
unsigned
config_get_positive(const char *name, unsigned default_value)
{
const struct config_param *param = config_get_param(name);
long value;
char *endptr;
if (param == NULL)
return default_value;
value = strtol(param->value, &endptr, 0);
if (*endptr != 0)
g_error("Not a valid number in line %i", param->line);
if (value <= 0)
g_error("Not a positive number in line %i", param->line);
return (unsigned)value;
}
struct block_param *
getBlockParam(struct config_param * param, const char *name)
getBlockParam(const struct config_param * param, const char *name)
{
struct block_param *ret = NULL;
int i;
if (param == NULL)
return NULL;
for (i = 0; i < param->num_block_params; i++) {
if (0 == strcmp(name, param->block_params[i].name)) {
if (ret) {
......@@ -407,32 +460,9 @@ getBlockParam(struct config_param * param, const char *name)
return ret;
}
struct config_param *
parseConfigFilePath(const char *name, int force)
{
struct config_param *param = config_get_param(name);
char *path;
if (!param && force)
g_error("config parameter \"%s\" not found\n", name);
if (!param)
return NULL;
path = parsePath(param->value);
if (!path)
g_error("error parsing \"%s\" at line %i\n",
name, param->line);
g_free(param->value);
param->value = path;
return param;
}
bool config_get_bool(const char *name, bool default_value)
{
struct config_param *param = config_get_param(name);
const struct config_param *param = config_get_param(name);
int value;
if (param == NULL)
......@@ -450,8 +480,41 @@ bool config_get_bool(const char *name, bool default_value)
return !!value;
}
const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value)
{
struct block_param *bp = getBlockParam(param, name);
if (bp == NULL)
return default_value;
return bp->value;
}
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
unsigned default_value)
{
struct block_param *bp = getBlockParam(param, name);
long value;
char *endptr;
if (bp == NULL)
return default_value;
value = strtol(bp->value, &endptr, 0);
if (*endptr != 0)
g_error("Not a valid number in line %i", bp->line);
if (value < 0)
g_error("Not a positive number in line %i", bp->line);
return (unsigned)value;
}
bool
config_get_block_bool(struct config_param *param, const char *name,
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value)
{
struct block_param *bp = getBlockParam(param, name);
......
......@@ -27,6 +27,7 @@
#define CONF_FOLLOW_INSIDE_SYMLINKS "follow_inside_symlinks"
#define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks"
#define CONF_DB_FILE "db_file"
#define CONF_STICKER_FILE "sticker_file"
#define CONF_LOG_FILE "log_file"
#define CONF_ERROR_FILE "error_file"
#define CONF_PID_FILE "pid_file"
......@@ -68,6 +69,9 @@
#define CONF_BOOL_UNSET -1
#define CONF_BOOL_INVALID -2
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
struct block_param {
char *name;
char *value;
......@@ -87,10 +91,17 @@ void config_global_finish(void);
void config_read_file(const char *file);
/**
* Adds a new configuration parameter. The name must be registered
* with registerConfigParam().
*/
void
config_add_param(const char *name, struct config_param *param);
/* don't free the returned value
set _last_ to NULL to get first entry */
struct config_param *
config_get_next_param(const char *name, struct config_param *last);
config_get_next_param(const char *name, const struct config_param *last);
static inline struct config_param *
config_get_param(const char *name)
......@@ -101,16 +112,39 @@ config_get_param(const char *name)
const char *
config_get_string(const char *name, const char *default_value);
struct block_param *
getBlockParam(struct config_param *param, const char *name);
/**
* Returns an optional configuration variable which contains an
* absolute path. If there is a tilde prefix, it is expanded. Aborts
* MPD if the path is not a valid absolute path.
*/
const char *
config_get_path(const char *name);
struct config_param *
parseConfigFilePath(const char *name, int force);
unsigned
config_get_positive(const char *name, unsigned default_value);
struct block_param *
getBlockParam(const struct config_param *param, const char *name);
bool config_get_bool(const char *name, bool default_value);
const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value);
static inline char *
config_dup_block_string(const struct config_param *param, const char *name,
const char *default_value)
{
return g_strdup(config_get_block_string(param, name, default_value));
}
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
unsigned default_value);
bool
config_get_block_bool(struct config_param *param, const char *name,
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value);
struct config_param *
......
......@@ -18,7 +18,6 @@
*/
#include "crossfade.h"
#include "audio.h"
#include "pcm_mix.h"
#include "pipe.h"
#include "audio_format.h"
......@@ -29,12 +28,14 @@
unsigned cross_fade_calc(float duration, float total_time,
const struct audio_format *af,
const struct audio_format *old_format,
unsigned max_chunks)
{
unsigned int chunks;
if (duration <= 0 || duration >= total_time ||
!isCurrentAudioFormat(af))
/* we can't crossfade when the audio formats are different */
!audio_format_equals(af, old_format))
return 0;
assert(duration > 0);
......
......@@ -25,6 +25,7 @@ struct music_chunk;
unsigned cross_fade_calc(float duration, float total_time,
const struct audio_format *af,
const struct audio_format *old_format,
unsigned max_chunks);
void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
......
......@@ -17,7 +17,8 @@
*/
#include "daemon.h"
#include "conf.h"
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
......@@ -28,6 +29,60 @@
#include <sys/stat.h>
#include <fcntl.h>
#ifndef WIN32
#include <signal.h>
#include <pwd.h>
#include <grp.h>
#endif
#ifndef WIN32
/** the Unix user name which MPD runs as */
static char *user_name;
/** the Unix user id which MPD runs as */
static uid_t user_uid;
/** the Unix group id which MPD runs as */
static gid_t user_gid;
/** the absolute path of the pidfile */
static char *pidfile;
#endif
void
daemonize_kill(void)
{
#ifndef WIN32
FILE *fp;
int pid, ret;
if (pidfile == NULL)
g_error("no pid_file specified in the config file");
fp = fopen(pidfile, "r");
if (fp == NULL)
g_error("unable to open pid file \"%s\": %s",
pidfile, g_strerror(errno));
if (fscanf(fp, "%i", &pid) != 1) {
g_error("unable to read the pid from file \"%s\"",
pidfile);
}
fclose(fp);
ret = kill(pid, SIGTERM);
if (ret < 0)
g_error("unable to kill proccess %i: %s",
pid, g_strerror(errno));
exit(EXIT_SUCCESS);
#else
g_error("--kill is not available on WIN32");
#endif
}
void
daemonize_close_stdin(void)
{
......@@ -42,26 +97,53 @@ daemonize_close_stdin(void)
}
void
daemonize(Options *options)
daemonize_set_user(void)
{
#ifndef WIN32
if (user_name != NULL) {
/* get uid */
if (setgid(user_gid) == -1) {
g_error("cannot setgid for user \"%s\": %s",
user_name, g_strerror(errno));
}
#ifdef _BSD_SOURCE
/* init suplementary groups
* (must be done before we change our uid)
*/
if (initgroups(user_name, user_gid) == -1) {
g_warning("cannot init supplementary groups "
"of user \"%s\": %s",
user_name, g_strerror(errno));
}
#endif
/* set uid */
if (setuid(user_uid) == -1) {
g_error("cannot change to uid of user \"%s\": %s",
user_name, g_strerror(errno));
}
}
#endif
}
void
daemonize(bool detach)
{
#ifndef WIN32
FILE *fp = NULL;
struct config_param *pidFileParam =
parseConfigFilePath(CONF_PID_FILE, 0);
if (pidFileParam) {
if (pidfile != NULL) {
/* do this before daemon'izing so we can fail gracefully if we can't
* write to the pid file */
g_debug("opening pid file");
fp = fopen(pidFileParam->value, "w+");
fp = fopen(pidfile, "w+");
if (!fp) {
g_error("could not open %s \"%s\" (at line %i) for writing: %s",
CONF_PID_FILE, pidFileParam->value,
pidFileParam->line, strerror(errno));
g_error("could not create pid file \"%s\": %s",
pidfile, g_strerror(errno));
}
}
if (options->daemon) {
if (detach) {
int pid;
fflush(NULL);
......@@ -81,7 +163,7 @@ daemonize(Options *options)
g_debug("daemonized!");
}
if (pidFileParam) {
if (pidfile != NULL) {
g_debug("writing pid file");
fprintf(fp, "%lu\n", (unsigned long)getpid());
fclose(fp);
......@@ -91,3 +173,39 @@ daemonize(Options *options)
(void)options;
#endif
}
void
daemonize_init(const char *user, const char *_pidfile)
{
#ifndef WIN32
user_name = g_strdup(user);
if (user_name != NULL) {
struct passwd *pwd = getpwnam(user_name);
if (pwd == NULL)
g_error("no such user \"%s\"", user_name);
user_uid = pwd->pw_uid;
user_gid = pwd->pw_gid;
/* this is needed by libs such as arts */
g_setenv("HOME", pwd->pw_dir, true);
}
pidfile = g_strdup(_pidfile);
#else
(void)user;
(void)_pidfile;
#endif
}
void
daemonize_finish(void)
{
#ifndef WIN32
if (pidfile != NULL)
unlink(pidfile);
g_free(user_name);
g_free(pidfile);
#endif
}
......@@ -19,7 +19,20 @@
#ifndef DAEMON_H
#define DAEMON_H
#include "cmdline.h"
#include <stdbool.h>
void
daemonize_init(const char *user, const char *pidfile);
void
daemonize_finish(void);
/**
* Kill the MPD which is currently running, pid determined from the
* pid file.
*/
void
daemonize_kill(void);
/**
* Close stdin (fd 0) and re-open it as /dev/null.
......@@ -27,7 +40,13 @@
void
daemonize_close_stdin(void);
/**
* Change to the configured Unix user.
*/
void
daemonize_set_user(void);
void
daemonize(Options *options);
daemonize(bool detach);
#endif
......@@ -21,7 +21,6 @@
#include "directory.h"
#include "directory_save.h"
#include "song.h"
#include "conf.h"
#include "path.h"
#include "stats.h"
#include "config.h"
......@@ -39,20 +38,39 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "database"
static char *database_path;
static struct directory *music_root;
static time_t directory_dbModTime;
void
db_init(void)
db_init(const char *path)
{
music_root = directory_new("", NULL);
database_path = g_strdup(path);
if (path != NULL)
music_root = directory_new("", NULL);
}
void
db_finish(void)
{
assert((database_path == NULL) == (music_root == NULL));
if (music_root != NULL)
directory_free(music_root);
g_free(database_path);
}
void
db_clear(void)
{
assert(music_root != NULL);
directory_free(music_root);
music_root = directory_new("", NULL);
}
struct directory *
......@@ -66,6 +84,9 @@ db_get_root(void)
struct directory *
db_get_directory(const char *name)
{
if (music_root == NULL)
return NULL;
if (name == NULL)
return music_root;
......@@ -75,30 +96,37 @@ db_get_directory(const char *name)
struct song *
db_get_song(const char *file)
{
struct song *song = NULL;
struct song *song;
struct directory *directory;
char *dir = NULL;
char *duplicated = g_strdup(file);
char *shortname = strrchr(duplicated, '/');
char *duplicated, *shortname, *dir;
assert(file != NULL);
g_debug("get song: %s", file);
if (music_root == NULL)
return NULL;
duplicated = g_strdup(file);
shortname = strrchr(duplicated, '/');
if (!shortname) {
shortname = duplicated;
dir = NULL;
} else {
*shortname = '\0';
++shortname;
dir = duplicated;
}
if (!(directory = db_get_directory(dir)))
goto out;
if (!(song = songvec_find(&directory->songs, shortname)))
goto out;
assert(song->parent == directory);
directory = db_get_directory(dir);
if (directory != NULL)
song = songvec_find(&directory->songs, shortname);
else
song = NULL;
out:
free(duplicated);
assert(song == NULL || song->parent == directory);
g_free(duplicated);
return song;
}
......@@ -109,6 +137,9 @@ db_walk(const char *name,
{
struct directory *directory;
if (music_root == NULL)
return -1;
if ((directory = db_get_directory(name)) == NULL) {
struct song *song;
if ((song = db_get_song(name)) && forEachSong) {
......@@ -120,42 +151,32 @@ db_walk(const char *name,
return directory_walk(directory, forEachSong, forEachDir, data);
}
static char *
db_get_file(void)
{
struct config_param *param = parseConfigFilePath(CONF_DB_FILE, 1);
assert(param);
assert(param->value);
return param->value;
}
bool
db_check(void)
{
struct stat st;
char *dbFile = db_get_file();
assert(database_path != NULL);
/* Check if the file exists */
if (access(dbFile, F_OK)) {
if (access(database_path, F_OK)) {
/* If the file doesn't exist, we can't check if we can write
* it, so we are going to try to get the directory path, and
* see if we can write a file in that */
char *dirPath = g_path_get_dirname(dbFile);
char *dirPath = g_path_get_dirname(database_path);
/* Check that the parent part of the path is a directory */
if (stat(dirPath, &st) < 0) {
g_free(dirPath);
g_warning("Couldn't stat parent directory of db file "
"\"%s\": %s", dbFile, strerror(errno));
"\"%s\": %s", database_path, strerror(errno));
return false;
}
if (!S_ISDIR(st.st_mode)) {
g_free(dirPath);
g_warning("Couldn't create db file \"%s\" because the "
"parent path is not a directory", dbFile);
"parent path is not a directory", database_path);
return false;
}
......@@ -173,21 +194,21 @@ db_check(void)
}
/* Path exists, now check if it's a regular file */
if (stat(dbFile, &st) < 0) {
if (stat(database_path, &st) < 0) {
g_warning("Couldn't stat db file \"%s\": %s",
dbFile, strerror(errno));
database_path, strerror(errno));
return false;
}
if (!S_ISREG(st.st_mode)) {
g_warning("db file \"%s\" is not a regular file", dbFile);
g_warning("db file \"%s\" is not a regular file", database_path);
return false;
}
/* And check that we can write to it */
if (access(dbFile, R_OK | W_OK)) {
if (access(database_path, R_OK | W_OK)) {
g_warning("Can't open db file \"%s\" for reading/writing: %s",
dbFile, strerror(errno));
database_path, strerror(errno));
return false;
}
......@@ -198,9 +219,11 @@ bool
db_save(void)
{
FILE *fp;
char *dbFile = db_get_file();
struct stat st;
assert(database_path != NULL);
assert(music_root != NULL);
g_debug("removing empty directories from DB");
directory_prune_empty(music_root);
......@@ -210,10 +233,10 @@ db_save(void)
g_debug("writing DB");
fp = fopen(dbFile, "w");
fp = fopen(database_path, "w");
if (!fp) {
g_warning("unable to write to db file \"%s\": %s",
dbFile, strerror(errno));
database_path, strerror(errno));
return false;
}
......@@ -232,7 +255,7 @@ db_save(void)
while (fclose(fp) && errno == EINTR);
if (stat(dbFile, &st) == 0)
if (stat(database_path, &st) == 0)
directory_dbModTime = st.st_mtime;
return true;
......@@ -242,19 +265,19 @@ bool
db_load(void)
{
FILE *fp = NULL;
char *dbFile = db_get_file();
struct stat st;
char buffer[100];
bool foundFsCharset = false, foundVersion = false;
assert(database_path != NULL);
assert(music_root != NULL);
if (!music_root)
music_root = directory_new("", NULL);
while (!(fp = fopen(dbFile, "r")) && errno == EINTR) ;
while (!(fp = fopen(database_path, "r")) && errno == EINTR) ;
if (fp == NULL) {
g_warning("unable to open db file \"%s\": %s",
dbFile, strerror(errno));
database_path, strerror(errno));
return false;
}
......@@ -289,16 +312,14 @@ db_load(void)
foundFsCharset = true;
fsCharset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]);
tempCharset = config_get_string(CONF_FS_CHARSET, NULL);
tempCharset = path_get_fs_charset();
if (tempCharset != NULL
&& strcmp(fsCharset, tempCharset)) {
g_message("Using \"%s\" for the "
"filesystem charset "
g_message("Existing database has charset \"%s\" "
"instead of \"%s\"; "
"maybe you need to "
"recreate the db?",
fsCharset, tempCharset);
path_set_fs_charset(fsCharset);
"discarding database file",
fsCharset, tempCharset);
return false;
}
} else
g_error("unknown line in db info: %s",
......@@ -312,7 +333,7 @@ db_load(void)
stats_update();
if (stat(dbFile, &st) == 0)
if (stat(database_path, &st) == 0)
directory_dbModTime = st.st_mtime;
return true;
......
......@@ -27,9 +27,11 @@ struct directory;
/**
* Initialize the database library.
*
* @param path the absolute path of the database file
*/
void
db_init(void);
db_init(const char *path);
void
db_finish(void);
......@@ -37,13 +39,13 @@ db_finish(void);
/**
* Clear the database.
*/
static inline void
db_clear(void)
{
db_finish();
db_init();
}
void
db_clear(void);
/**
* Returns the root directory object. Returns NULL if there is no
* configured music directory.
*/
struct directory *
db_get_root(void);
......
......@@ -17,7 +17,7 @@
*/
#include "dbUtils.h"
#include "locate.h"
#include "directory.h"
#include "database.h"
#include "client.h"
......@@ -35,32 +35,16 @@
typedef struct _ListCommandItem {
int8_t tagType;
int numConditionals;
LocateTagItem *conditionals;
const struct locate_item_list *criteria;
} ListCommandItem;
typedef struct _LocateTagItemArray {
int numItems;
LocateTagItem *items;
} LocateTagItemArray;
typedef struct _SearchStats {
LocateTagItemArray locateArray;
const struct locate_item_list *criteria;
int numberOfSongs;
unsigned long playTime;
} SearchStats;
static int
countSongsInDirectory(struct directory *directory, void *data)
{
int *count = (int *)data;
*count += directory->songs.nr;
return 0;
}
static int
printDirectoryInDirectory(struct directory *directory, void *data)
{
struct client *client = data;
......@@ -81,47 +65,35 @@ printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data)
struct search_data {
struct client *client;
LocateTagItemArray array;
const struct locate_item_list *criteria;
};
static int
searchInDirectory(struct song *song, void *_data)
{
struct search_data *data = _data;
LocateTagItemArray *array = &data->array;
if (strstrSearchTags(song, array->numItems, array->items))
if (locate_song_search(song, data->criteria))
return song_print_info(data->client, song);
return 0;
}
int searchForSongsIn(struct client *client, const char *name,
int numItems, LocateTagItem * items)
int
searchForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria)
{
int ret;
int i;
char **originalNeedles = g_new(char *, numItems);
struct locate_item_list *new_list
= locate_item_list_casefold(criteria);
struct search_data data;
for (i = 0; i < numItems; i++) {
originalNeedles[i] = items[i].needle;
items[i].needle = g_utf8_casefold(originalNeedles[i], -1);
}
data.client = client;
data.array.numItems = numItems;
data.array.items = items;
data.criteria = new_list;
ret = db_walk(name, searchInDirectory, NULL, &data);
for (i = 0; i < numItems; i++) {
g_free(items[i].needle);
items[i].needle = originalNeedles[i];
}
free(originalNeedles);
locate_item_list_free(new_list);
return ret;
}
......@@ -130,22 +102,21 @@ static int
findInDirectory(struct song *song, void *_data)
{
struct search_data *data = _data;
LocateTagItemArray *array = &data->array;
if (tagItemsFoundAndMatches(song, array->numItems, array->items))
if (locate_song_match(song, data->criteria))
return song_print_info(data->client, song);
return 0;
}
int findSongsIn(struct client *client, const char *name,
int numItems, LocateTagItem * items)
int
findSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria)
{
struct search_data data;
data.client = client;
data.array.numItems = numItems;
data.array.items = items;
data.criteria = criteria;
return db_walk(name, findInDirectory, NULL, &data);
}
......@@ -161,8 +132,7 @@ searchStatsInDirectory(struct song *song, void *data)
{
SearchStats *stats = data;
if (tagItemsFoundAndMatches(song, stats->locateArray.numItems,
stats->locateArray.items)) {
if (locate_song_match(song, stats->criteria)) {
stats->numberOfSongs++;
if (song->tag->time > 0)
stats->playTime += song->tag->time;
......@@ -171,14 +141,14 @@ searchStatsInDirectory(struct song *song, void *data)
return 0;
}
int searchStatsForSongsIn(struct client *client, const char *name,
int numItems, LocateTagItem * items)
int
searchStatsForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria)
{
SearchStats stats;
int ret;
stats.locateArray.numItems = numItems;
stats.locateArray.items = items;
stats.criteria = criteria;
stats.numberOfSongs = 0;
stats.playTime = 0;
......@@ -198,7 +168,7 @@ int printAllIn(struct client *client, const char *name)
static int
directoryAddSongToPlaylist(struct song *song, G_GNUC_UNUSED void *data)
{
return addSongToPlaylist(song, NULL);
return addSongToPlaylist(&g_playlist, song, NULL);
}
struct add_data {
......@@ -237,58 +207,26 @@ directoryPrintSongInfo(struct song *song, void *data)
return 0;
}
static int
sumSongTime(struct song *song, void *data)
{
unsigned long *sum_time = (unsigned long *)data;
if (song->tag && song->tag->time >= 0)
*sum_time += song->tag->time;
return 0;
}
int printInfoForAllIn(struct client *client, const char *name)
{
return db_walk(name, directoryPrintSongInfo,
printDirectoryInDirectory, client);
}
int countSongsIn(const char *name)
{
int count = 0;
void *ptr = (void *)&count;
db_walk(name, NULL, countSongsInDirectory, ptr);
return count;
}
unsigned long sumSongTimesIn(const char *name)
{
unsigned long dbPlayTime = 0;
void *ptr = (void *)&dbPlayTime;
db_walk(name, sumSongTime, NULL, ptr);
return dbPlayTime;
}
static ListCommandItem *newListCommandItem(int tagType, int numConditionals,
LocateTagItem * conditionals)
static ListCommandItem *
newListCommandItem(int tagType, const struct locate_item_list *criteria)
{
ListCommandItem *item = g_new(ListCommandItem, 1);
item->tagType = tagType;
item->numConditionals = numConditionals;
item->conditionals = conditionals;
item->criteria = criteria;
return item;
}
static void freeListCommandItem(ListCommandItem * item)
{
free(item);
g_free(item);
}
static void
......@@ -327,20 +265,17 @@ listUniqueTagsInDirectory(struct song *song, void *_data)
struct list_tags_data *data = _data;
ListCommandItem *item = data->item;
if (tagItemsFoundAndMatches(song, item->numConditionals,
item->conditionals)) {
if (locate_song_match(song, item->criteria))
visitTag(data->client, data->set, song, item->tagType);
}
return 0;
}
int listAllUniqueTags(struct client *client, int type, int numConditionals,
LocateTagItem * conditionals)
int listAllUniqueTags(struct client *client, int type,
const struct locate_item_list *criteria)
{
int ret;
ListCommandItem *item = newListCommandItem(type, numConditionals,
conditionals);
ListCommandItem *item = newListCommandItem(type, criteria);
struct list_tags_data data = {
.client = client,
.item = item,
......
......@@ -19,9 +19,8 @@
#ifndef MPD_DB_UTILS_H
#define MPD_DB_UTILS_H
#include "locate.h"
struct client;
struct locate_item_list;
int printAllIn(struct client *client, const char *name);
......@@ -31,21 +30,23 @@ int addAllInToStoredPlaylist(const char *name, const char *utf8file);
int printInfoForAllIn(struct client *client, const char *name);
int searchForSongsIn(struct client *client, const char *name,
int numItems, LocateTagItem * items);
int findSongsIn(struct client *client, const char *name,
int numItems, LocateTagItem * items);
int
searchForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
int searchStatsForSongsIn(struct client *client, const char *name,
int numItems, LocateTagItem * items);
int
findSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
int countSongsIn(const char *name);
int
searchStatsForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
unsigned long sumSongTimesIn(const char *name);
int listAllUniqueTags(struct client *client, int type, int numConditiionals,
LocateTagItem * conditionals);
int
listAllUniqueTags(struct client *client, int type,
const struct locate_item_list *criteria);
void printSavedMemoryFromFilenames(void);
......
......@@ -23,6 +23,7 @@
#define MPD_FLAC_COMMON_H
#include "../decoder_api.h"
#include "config.h"
#include <glib.h>
......
......@@ -431,7 +431,7 @@ aac_stream_decode(struct decoder *mpd_decoder, struct input_stream *inStream)
sampleBufferLen = sampleCount * 2;
cmd = decoder_data(mpd_decoder, NULL, sampleBuffer,
cmd = decoder_data(mpd_decoder, inStream, sampleBuffer,
sampleBufferLen, file_time,
bitRate, NULL);
if (cmd == DECODE_COMMAND_SEEK)
......
......@@ -177,6 +177,9 @@ ffmpeg_helper(struct input_stream *input,
}
codec_context = format_context->streams[audio_stream]->codec;
if (codec_context->codec_name[0] != 0)
g_debug("codec '%s'", codec_context->codec_name);
codec = avcodec_find_decoder(codec_context->codec_id);
if (!codec) {
......@@ -227,10 +230,6 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
&audio_size,
packet_data, packet_size);
position = av_rescale_q(packet->pts, *time_base,
(AVRational){1, 1});
if (len < 0) {
/* if error, we skip the frame */
g_message("decoding failed\n");
......@@ -240,10 +239,14 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
packet_data += len;
packet_size -= len;
if (audio_size <= 0) {
g_message("no audio frame\n");
if (audio_size <= 0)
continue;
}
position = packet->pts != (int64_t)AV_NOPTS_VALUE
? av_rescale_q(packet->pts, *time_base,
(AVRational){1, 1})
: 0;
cmd = decoder_data(decoder, is,
audio_buf, audio_size,
position,
......@@ -261,7 +264,7 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx)
AVPacket packet;
struct audio_format audio_format;
enum decoder_command cmd;
int current, total_time;
int total_time;
total_time = 0;
......@@ -303,9 +306,10 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx)
av_free_packet(&packet);
if (cmd == DECODE_COMMAND_SEEK) {
current = decoder_seek_where(decoder) * AV_TIME_BASE;
int64_t where =
decoder_seek_where(decoder) * AV_TIME_BASE;
if (av_seek_frame(format_context, -1, current, 0) < 0)
if (av_seek_frame(format_context, -1, where, 0) < 0)
decoder_seek_error(decoder);
else
decoder_command_finished(decoder);
......@@ -347,6 +351,16 @@ static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
tag_add_item(tag, TAG_ITEM_TRACK, buffer);
}
if (f->comment[0])
tag_add_item(tag, TAG_ITEM_COMMENT, f->comment);
if (f->genre[0])
tag_add_item(tag, TAG_ITEM_GENRE, f->genre);
if (f->year > 0) {
char buffer[16];
snprintf(buffer, sizeof(buffer), "%d", f->year);
tag_add_item(tag, TAG_ITEM_DATE, buffer);
}
return true;
}
......@@ -389,6 +403,7 @@ static const char *const ffmpeg_suffixes[] = {
"wma", "asf", "wmv", "mpeg", "mpg", "avi", "vob", "mov", "qt", "swf",
"rm", "swf", "mp1", "mp2", "mp3", "mp4", "m4a", "flac", "ogg", "wav",
"au", "aiff", "aif", "ac3", "aac", "mpc", "ape",
"tta",
NULL
};
......@@ -405,6 +420,7 @@ static const char *const ffmpeg_mime_types[] = {
"application/x-ms-wmd",
"audio/mpeg",
"audio/x-wav",
"audio/x-tta",
NULL
};
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../decoder_api.h"
#include "../timer.h"
#include "../conf.h"
#include <glib.h>
#include <fluidsynth.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "fluidsynth"
/**
* Convert a fluidsynth log level to a GLib log level.
*/
static GLogLevelFlags
fluidsynth_level_to_glib(enum fluid_log_level level)
{
switch (level) {
case FLUID_PANIC:
case FLUID_ERR:
return G_LOG_LEVEL_CRITICAL;
case FLUID_WARN:
return G_LOG_LEVEL_WARNING;
case FLUID_INFO:
return G_LOG_LEVEL_INFO;
case FLUID_DBG:
case LAST_LOG_LEVEL:
return G_LOG_LEVEL_DEBUG;
}
/* invalid fluidsynth log level */
return G_LOG_LEVEL_MESSAGE;
}
/**
* The fluidsynth logging callback. It forwards messages to the GLib
* logging library.
*/
static void
fluidsynth_mpd_log_function(int level, char *message, G_GNUC_UNUSED void *data)
{
g_log(G_LOG_DOMAIN, fluidsynth_level_to_glib(level), "%s", message);
}
static bool
fluidsynth_init(void)
{
fluid_set_log_function(LAST_LOG_LEVEL,
fluidsynth_mpd_log_function, NULL);
return true;
}
static void
fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
{
static const struct audio_format audio_format = {
.sample_rate = 48000,
.bits = 16,
.channels = 2,
};
char setting_sample_rate[] = "synth.sample-rate";
/*
char setting_verbose[] = "synth.verbose";
char setting_yes[] = "yes";
*/
const char *soundfont_path;
fluid_settings_t *settings;
fluid_synth_t *synth;
fluid_player_t *player;
char *path_dup;
int ret;
Timer *timer;
enum decoder_command cmd;
soundfont_path =
config_get_string("soundfont",
"/usr/share/sounds/sf2/FluidR3_GM.sf2");
/* set up fluid settings */
settings = new_fluid_settings();
if (settings == NULL)
return;
fluid_settings_setnum(settings, setting_sample_rate, 48000);
/*
fluid_settings_setstr(settings, setting_verbose, setting_yes);
*/
/* create the fluid synth */
synth = new_fluid_synth(settings);
if (synth == NULL) {
delete_fluid_settings(settings);
return;
}
ret = fluid_synth_sfload(synth, soundfont_path, true);
if (ret < 0) {
g_warning("fluid_synth_sfload() failed");
delete_fluid_synth(synth);
delete_fluid_settings(settings);
return;
}
/* create the fluid player */
player = new_fluid_player(synth);
if (player == NULL) {
delete_fluid_synth(synth);
delete_fluid_settings(settings);
return;
}
/* temporarily duplicate the path_fs string, because
fluidsynth wants a writable string */
path_dup = g_strdup(path_fs);
ret = fluid_player_add(player, path_dup);
g_free(path_dup);
if (ret != 0) {
g_warning("fluid_player_add() failed");
delete_fluid_player(player);
delete_fluid_synth(synth);
delete_fluid_settings(settings);
return;
}
/* start the player */
ret = fluid_player_play(player);
if (ret != 0) {
g_warning("fluid_player_play() failed");
delete_fluid_player(player);
delete_fluid_synth(synth);
delete_fluid_settings(settings);
return;
}
/* set up a timer for synchronization; fluidsynth always
decodes in real time, which forces us to synchronize */
/* XXX is there any way to switch off real-time decoding? */
timer = timer_new(&audio_format);
timer_start(timer);
/* initialization complete - announce the audio format to the
MPD core */
decoder_initialized(decoder, &audio_format, false, -1);
do {
int16_t buffer[2048];
const unsigned max_frames = G_N_ELEMENTS(buffer) / 2;
/* synchronize with the fluid player */
timer_add(timer, sizeof(buffer));
timer_sync(timer);
/* read samples from fluidsynth and send them to the
MPD core */
ret = fluid_synth_write_s16(synth, max_frames,
buffer, 0, 2,
buffer, 1, 2);
/* XXX how do we see whether the player is done? We
can't access the private attribute
player->status */
if (ret != 0)
break;
cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer),
0, 0, NULL);
} while (cmd == DECODE_COMMAND_NONE);
/* clean up */
timer_free(timer);
fluid_player_stop(player);
fluid_player_join(player);
delete_fluid_player(player);
delete_fluid_synth(synth);
delete_fluid_settings(settings);
}
static struct tag *
fluidsynth_tag_dup(const char *file)
{
struct tag *tag = tag_new();
/* to be implemented */
(void)file;
return tag;
}
static const char *const fluidsynth_suffixes[] = {
"mid",
NULL
};
const struct decoder_plugin fluidsynth_decoder_plugin = {
.name = "fluidsynth",
.init = fluidsynth_init,
.file_decode = fluidsynth_file_decode,
.tag_dup = fluidsynth_tag_dup,
.suffixes = fluidsynth_suffixes,
};
......@@ -152,7 +152,7 @@ static void mod_close(mod_Data * data)
{
Player_Stop();
Player_Free(data->moduleHandle);
free(data);
g_free(data);
}
static void
......@@ -167,7 +167,6 @@ mod_decode(struct decoder *decoder, const char *path)
if (!(data = mod_open(path))) {
g_warning("failed to open mod: %s\n", path);
MikMod_Exit();
return;
}
......@@ -190,8 +189,6 @@ mod_decode(struct decoder *decoder, const char *path)
}
mod_close(data);
MikMod_Exit();
}
static struct tag *modTagDup(const char *file)
......@@ -207,7 +204,6 @@ static struct tag *modTagDup(const char *file)
if (moduleHandle == NULL) {
g_debug("modTagDup: Failed to open file: %s\n", file);
MikMod_Exit();
return NULL;
}
......@@ -223,8 +219,6 @@ static struct tag *modTagDup(const char *file)
if (title)
tag_add_item(ret, TAG_ITEM_TITLE, title);
MikMod_Exit();
return ret;
}
......@@ -247,8 +241,8 @@ static const char *const modSuffixes[] = {
NULL
};
const struct decoder_plugin modPlugin = {
.name = "mod",
const struct decoder_plugin mikmod_decoder_plugin = {
.name = "mikmod",
.init = mod_initMikMod,
.finish = mod_finishMikMod,
.file_decode = mod_decode,
......
......@@ -17,56 +17,68 @@
*/
#include "../decoder_api.h"
#include "../utils.h"
#include "../log.h"
#include <glib.h>
#include <modplug.h>
#define MODPLUG_FRAME_SIZE (4096)
#define MODPLUG_PREALLOC_BLOCK (256*1024)
#define MODPLUG_READ_BLOCK (128*1024)
#define MODPLUG_FILE_LIMIT (1024*1024*100)
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "modplug"
enum {
MODPLUG_FRAME_SIZE = 4096,
MODPLUG_PREALLOC_BLOCK = 256 * 1024,
MODPLUG_READ_BLOCK = 128 * 1024,
MODPLUG_FILE_LIMIT = 100 * 1024 * 1024,
};
static GByteArray *mod_loadfile(struct decoder *decoder, struct input_stream *is)
{
unsigned char *data;
GByteArray *bdatas;
int total_len;
int ret;
size_t ret;
if (is->size == 0) {
g_warning("file is empty");
return NULL;
}
if (is->size > MODPLUG_FILE_LIMIT) {
g_warning("file too large");
return NULL;
}
//known/unknown size, preallocate array, lets read in chunks
if (is->size) {
if (is->size > MODPLUG_FILE_LIMIT) {
g_warning("file too large\n");
return NULL;
}
if (is->size > 0) {
bdatas = g_byte_array_sized_new(is->size);
} else {
bdatas = g_byte_array_sized_new(MODPLUG_PREALLOC_BLOCK);
}
data = g_malloc(MODPLUG_READ_BLOCK);
total_len = 0;
do {
if (decoder) {
ret = decoder_read(decoder, is, data, MODPLUG_READ_BLOCK);
} else {
ret = input_stream_read(is, data, MODPLUG_READ_BLOCK);
}
if (ret > 0) {
g_byte_array_append(bdatas, data, ret);
total_len += ret;
} else {
//end of file, or read error
break;
while (true) {
ret = decoder_read(decoder, is, data, MODPLUG_READ_BLOCK);
if (ret == 0) {
if (input_stream_eof(is))
/* end of file */
break;
/* I/O error - skip this song */
g_free(data);
g_byte_array_free(bdatas, true);
return NULL;
}
if (total_len > MODPLUG_FILE_LIMIT) {
if (bdatas->len + ret > MODPLUG_FILE_LIMIT) {
g_warning("stream too large\n");
g_free(data);
g_byte_array_free(bdatas, TRUE);
return NULL;
}
} while (input_stream_eof(is));
g_byte_array_append(bdatas, data, ret);
}
g_free(data);
return bdatas;
......@@ -91,12 +103,7 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
g_warning("could not load stream\n");
return;
}
f = ModPlug_Load(bdatas->data, bdatas->len);
g_byte_array_free(bdatas, TRUE);
if (!f) {
g_warning("could not decode stream\n");
return;
}
ModPlug_GetSettings(&settings);
/* alter setting */
settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */
......@@ -106,6 +113,13 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
/* insert more setting changes here */
ModPlug_SetSettings(&settings);
f = ModPlug_Load(bdatas->data, bdatas->len);
g_byte_array_free(bdatas, TRUE);
if (!f) {
g_warning("could not decode stream\n");
return;
}
audio_format.bits = 16;
audio_format.sample_rate = 44100;
audio_format.channels = 2;
......
......@@ -179,7 +179,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track);
scale = mp4ff_time_scale(mp4fh, track);
g_free(mp4_buffer);
free(mp4_buffer);
if (scale < 0) {
g_warning("Error getting audio format of mp4 AAC track.\n");
......@@ -314,7 +314,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
file_time, bit_rate, NULL);
}
free(seek_table);
g_free(seek_table);
faacDecClose(decoder);
mp4ff_close(mp4fh);
}
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
extern "C" {
#include "../decoder_api.h"
}
#include <glib.h>
#include <sidplay/sidplay2.h>
#include <sidplay/builders/resid.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "sidplay"
static void
sidplay_file_decode(struct decoder *decoder, const char *path_fs)
{
int ret;
/* load the tune */
SidTune tune(path_fs, NULL, true);
if (!tune) {
g_warning("failed to load file");
return;
}
tune.selectSong(1);
/* initialize the player */
sidplay2 player;
int iret = player.load(&tune);
if (iret != 0) {
g_warning("sidplay2.load() failed: %s", player.error());
return;
}
/* initialize the builder */
ReSIDBuilder builder("ReSID");
if (!builder) {
g_warning("failed to initialize ReSIDBuilder");
return;
}
builder.create(player.info().maxsids);
if (!builder) {
g_warning("ReSIDBuilder.create() failed");
return;
}
builder.filter(false);
if (!builder) {
g_warning("ReSIDBuilder.filter() failed");
return;
}
/* configure the player */
sid2_config_t config = player.config();
config.clockDefault = SID2_CLOCK_PAL;
config.clockForced = true;
config.clockSpeed = SID2_CLOCK_CORRECT;
config.frequency = 48000;
config.optimisation = SID2_DEFAULT_OPTIMISATION;
config.playback = sid2_stereo;
config.precision = 16;
config.sidDefault = SID2_MOS6581;
config.sidEmulation = &builder;
config.sidModel = SID2_MODEL_CORRECT;
config.sidSamples = true;
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
config.sampleFormat = SID2_LITTLE_SIGNED;
#else
config.sampleFormat = SID2_BIG_SIGNED;
#endif
iret = player.config(config);
if (iret != 0) {
g_warning("sidplay2.config() failed: %s", player.error());
return;
}
/* initialize the MPD decoder */
struct audio_format audio_format;
audio_format.sample_rate = 48000;
audio_format.bits = 16;
audio_format.channels = 2;
decoder_initialized(decoder, &audio_format, false, -1);
/* .. and play */
enum decoder_command cmd;
do {
char buffer[4096];
size_t nbytes;
nbytes = player.play(buffer, sizeof(buffer));
if (nbytes == 0)
break;
cmd = decoder_data(decoder, NULL, buffer, nbytes,
0, 0, NULL);
} while (cmd == DECODE_COMMAND_NONE);
}
static struct tag *
sidplay_tag_dup(const char *path_fs)
{
SidTune tune(path_fs, NULL, true);
if (!tune)
return NULL;
const SidTuneInfo &info = tune.getInfo();
struct tag *tag = tag_new();
if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL)
tag_add_item(tag, TAG_ITEM_TITLE, info.infoString[0]);
if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL)
tag_add_item(tag, TAG_ITEM_ARTIST, info.infoString[1]);
return tag;
}
static const char *const sidplay_suffixes[] = {
"sid",
NULL
};
extern const struct decoder_plugin sidplay_decoder_plugin;
const struct decoder_plugin sidplay_decoder_plugin = {
"sidplay",
NULL, /* init() */
NULL, /* finish() */
NULL, /* stream_decode() */
sidplay_file_decode,
sidplay_tag_dup,
sidplay_suffixes,
NULL, /* mime_types */
};
......@@ -522,7 +522,9 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
wavpack_input_init(&isp, decoder, is);
wpc = WavpackOpenFileInputEx(
&mpd_is_reader, &isp, &isp_wvc, error, open_flags, 23
&mpd_is_reader, &isp,
open_flags & OPEN_WVC ? &isp_wvc : NULL,
error, open_flags, 23
);
if (wpc == NULL) {
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../decoder_api.h"
#include <glib.h>
#include <wildmidi_lib.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "wildmidi"
enum {
WILDMIDI_SAMPLE_RATE = 48000,
};
static bool
wildmidi_init(void)
{
int ret;
ret = WildMidi_Init("/etc/timidity/timidity.cfg",
WILDMIDI_SAMPLE_RATE, 0);
return ret == 0;
}
static void
wildmidi_finish(void)
{
WildMidi_Shutdown();
}
static void
wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
{
static const struct audio_format audio_format = {
.sample_rate = WILDMIDI_SAMPLE_RATE,
.bits = 16,
.channels = 2,
};
midi *wm;
const struct _WM_Info *info;
enum decoder_command cmd;
wm = WildMidi_Open(path_fs);
if (wm == NULL)
return;
info = WildMidi_GetInfo(wm);
if (info == NULL) {
WildMidi_Close(wm);
return;
}
decoder_initialized(decoder, &audio_format, true,
info->approx_total_samples / WILDMIDI_SAMPLE_RATE);
do {
char buffer[4096];
int len;
info = WildMidi_GetInfo(wm);
if (info == NULL)
break;
len = WildMidi_GetOutput(wm, buffer, sizeof(buffer));
if (len <= 0)
break;
cmd = decoder_data(decoder, NULL, buffer, len,
(float)info->current_sample /
(float)WILDMIDI_SAMPLE_RATE,
0, NULL);
if (cmd == DECODE_COMMAND_SEEK) {
unsigned long seek_where = WILDMIDI_SAMPLE_RATE *
decoder_seek_where(decoder);
WildMidi_SampledSeek(wm, &seek_where);
decoder_command_finished(decoder);
cmd = DECODE_COMMAND_NONE;
}
} while (cmd == DECODE_COMMAND_NONE);
WildMidi_Close(wm);
}
static struct tag *
wildmidi_tag_dup(const char *path_fs)
{
midi *wm;
const struct _WM_Info *info;
struct tag *tag;
wm = WildMidi_Open(path_fs);
if (wm == NULL)
return NULL;
info = WildMidi_GetInfo(wm);
if (info == NULL) {
WildMidi_Close(wm);
return NULL;
}
tag = tag_new();
tag->time = info->approx_total_samples / WILDMIDI_SAMPLE_RATE;
WildMidi_Close(wm);
return tag;
}
static const char *const wildmidi_suffixes[] = {
"mid",
NULL
};
const struct decoder_plugin wildmidi_decoder_plugin = {
.name = "wildmidi",
.init = wildmidi_init,
.finish = wildmidi_finish,
.file_decode = wildmidi_file_decode,
.tag_dup = wildmidi_tag_dup,
.suffixes = wildmidi_suffixes,
};
......@@ -30,7 +30,6 @@
#include "replay_gain.h"
#include "tag.h"
#include "audio_format.h"
#include "playerData.h"
#include <stdbool.h>
......
......@@ -102,8 +102,13 @@ dc_seek(struct notify *notify, double where)
}
void
dc_quit(struct notify *notify)
dc_quit(void)
{
assert(dc.thread != NULL);
dc.quit = true;
dc_command(notify, DECODE_COMMAND_STOP);
dc_command_async(DECODE_COMMAND_STOP);
g_thread_join(dc.thread);
dc.thread = NULL;
}
......@@ -45,6 +45,10 @@ enum decoder_state {
};
struct decoder_control {
/** the handle of the decoder thread, or NULL if the decoder
thread isn't running */
GThread *thread;
struct notify notify;
volatile enum decoder_state state;
......@@ -124,6 +128,6 @@ bool
dc_seek(struct notify *notify, double where);
void
dc_quit(struct notify *notify);
dc_quit(void);
#endif
......@@ -33,7 +33,10 @@ extern const struct decoder_plugin aacPlugin;
extern const struct decoder_plugin mpcPlugin;
extern const struct decoder_plugin wavpack_plugin;
extern const struct decoder_plugin modplug_plugin;
extern const struct decoder_plugin modPlugin;
extern const struct decoder_plugin mikmod_decoder_plugin;
extern const struct decoder_plugin sidplay_decoder_plugin;
extern const struct decoder_plugin fluidsynth_decoder_plugin;
extern const struct decoder_plugin wildmidi_decoder_plugin;
extern const struct decoder_plugin ffmpeg_plugin;
static const struct decoder_plugin *const decoder_plugins[] = {
......@@ -68,7 +71,16 @@ static const struct decoder_plugin *const decoder_plugins[] = {
&modplug_plugin,
#endif
#ifdef HAVE_MIKMOD
&modPlugin,
&mikmod_decoder_plugin,
#endif
#ifdef ENABLE_SIDPLAY
&sidplay_decoder_plugin,
#endif
#ifdef ENABLE_FLUIDSYNTH
&fluidsynth_decoder_plugin,
#endif
#ifdef ENABLE_WILDMIDI
&wildmidi_decoder_plugin,
#endif
#ifdef HAVE_FFMPEG
&ffmpeg_plugin,
......
......@@ -258,8 +258,10 @@ static gpointer decoder_task(G_GNUC_UNUSED gpointer arg)
void decoder_thread_start(void)
{
GError *e = NULL;
GThread *t;
if (!(t = g_thread_create(decoder_task, NULL, FALSE, &e)))
assert(dc.thread == NULL);
dc.thread = g_thread_create(decoder_task, NULL, true, &e);
if (dc.thread == NULL)
FATAL("Failed to spawn decoder task: %s\n", e->message);
}
......@@ -54,7 +54,7 @@ directory_free(struct directory *directory)
dirvec_destroy(&directory->children);
songvec_destroy(&directory->songs);
free(directory);
g_free(directory);
/* this resets last dir returned */
/*directory_get_path(NULL); */
}
......@@ -108,7 +108,7 @@ directory_get_directory(struct directory *directory, const char *name)
locate = strchr(locate + 1, '/');
}
free(duplicated);
g_free(duplicated);
return found;
}
......
......@@ -36,6 +36,9 @@ enum pipe_event {
/** must call syncPlayerAndPlaylist() */
PIPE_EVENT_PLAYLIST,
/** the current song's tag has changed */
PIPE_EVENT_TAG,
/** SIGHUP received: reload configuration, roll log file */
PIPE_EVENT_RELOAD,
......
......@@ -38,7 +38,7 @@ static const char *const idle_names[] = {
"mixer",
"output",
"options",
"elapsed",
"sticker",
NULL
};
......
......@@ -46,6 +46,9 @@ enum {
/** options have changed: crossfade, random, repeat, ... */
IDLE_OPTIONS = 0x40,
/** a sticker has been modified. */
IDLE_STICKER = 0x80,
};
/**
......
......@@ -16,29 +16,13 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "input_archive.h"
#include "archive_api.h"
#include "archive_list.h"
#include "input_archive.h"
#include "input_stream.h"
#include "gcc.h"
#include "log.h"
#include "ls.h"
#include <stdbool.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <glib.h>
typedef struct {
const struct archive_plugin *aplugin;
const struct input_plugin *iplugin;
struct archive_file *file;
} archive_context;
/**
* select correct archive plugin to handle the input stream
* may allow stacking of archive plugins. for example for handling
......@@ -50,8 +34,8 @@ typedef struct {
static bool
input_archive_open(struct input_stream *is, const char *pathname)
{
archive_context *arch_ctx;
const struct archive_plugin *arplug;
struct archive_file *file;
char *archive, *filename, *suffix, *pname;
bool opened;
......@@ -74,23 +58,10 @@ input_archive_open(struct input_stream *is, const char *pathname)
return false;
}
arch_ctx = (archive_context *) g_malloc(sizeof(archive_context));
file = arplug->open(archive);
//setup archive plugin pointer
arch_ctx->aplugin = arplug;
//open archive file
arch_ctx->file = arplug->open(archive);
//setup fileops
arplug->setup_stream(arch_ctx->file, is);
//setup input plugin backup
arch_ctx->iplugin = is->plugin;
is->plugin = &input_plugin_archive;
//internal handle
is->data = arch_ctx;
//open archive
opened = arch_ctx->iplugin->open(is, filename);
opened = arplug->open_stream(file, is, filename);
if (!opened) {
g_warning("open inarchive file %s failed\n\n",filename);
......@@ -101,53 +72,6 @@ input_archive_open(struct input_stream *is, const char *pathname)
return opened;
}
static void
input_archive_close(struct input_stream *is)
{
archive_context *arch_ctx = (archive_context *)is->data;
//close archive infile ops
arch_ctx->iplugin->close(is);
//close archive
arch_ctx->aplugin->close(arch_ctx->file);
//free private data
g_free(arch_ctx);
}
static bool
input_archive_seek(struct input_stream *is, off_t offset, int whence)
{
archive_context *arch_ctx = (archive_context *)is->data;
return arch_ctx->iplugin->seek(is, offset, whence);
}
static size_t
input_archive_read(struct input_stream *is, void *ptr, size_t size)
{
archive_context *arch_ctx = (archive_context *)is->data;
assert(ptr != NULL);
assert(size > 0);
return arch_ctx->iplugin->read(is, ptr, size);
}
static bool
input_archive_eof(struct input_stream *is)
{
archive_context *arch_ctx = (archive_context *)is->data;
return arch_ctx->iplugin->eof(is);
}
static int
input_archive_buffer(struct input_stream *is)
{
archive_context *arch_ctx = (archive_context *)is->data;
return arch_ctx->iplugin->buffer(is);
}
const struct input_plugin input_plugin_archive = {
.open = input_archive_open,
.close = input_archive_close,
.buffer = input_archive_buffer,
.read = input_archive_read,
.eof = input_archive_eof,
.seek = input_archive_seek,
};
......@@ -630,10 +630,10 @@ input_curl_easy_init(struct input_stream *is)
struct input_curl *c = is->data;
CURLcode code;
CURLMcode mcode;
struct config_param *proxy_host;
struct config_param *proxy_port;
struct config_param *proxy_user;
struct config_param *proxy_pass;
const char *proxy_host;
const char *proxy_port;
const char *proxy_user;
const char *proxy_pass;
c->eof = false;
......@@ -661,27 +661,28 @@ input_curl_easy_init(struct input_stream *is)
curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
proxy_host = config_get_param(CONF_HTTP_PROXY_HOST);
proxy_port = config_get_param(CONF_HTTP_PROXY_PORT);
proxy_user = config_get_param(CONF_HTTP_PROXY_USER);
proxy_pass = config_get_param(CONF_HTTP_PROXY_PASSWORD);
proxy_host = config_get_string(CONF_HTTP_PROXY_HOST, NULL);
proxy_port = config_get_string(CONF_HTTP_PROXY_PORT, NULL);
if (proxy_host != NULL) {
char *proxy_host_str;
if (proxy_port == NULL) {
proxy_host_str = g_strdup(proxy_host->value);
proxy_host_str = g_strdup(proxy_host);
} else {
proxy_host_str =
g_strconcat(proxy_host->value, ":", proxy_port->value, NULL);
g_strconcat(proxy_host, ":", proxy_port, NULL);
}
curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy_host_str);
g_free(proxy_host_str);
}
proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL);
proxy_pass = config_get_string(CONF_HTTP_PROXY_PASSWORD, NULL);
if ((proxy_user != NULL) && (proxy_pass != NULL)) {
char *proxy_auth_str =
g_strconcat(proxy_user->value, ":", proxy_pass->value, NULL);
g_strconcat(proxy_user, ":", proxy_pass, NULL);
curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
g_free(proxy_auth_str);
}
......@@ -908,6 +909,7 @@ input_curl_open(struct input_stream *is, const char *url)
c->buffers = g_queue_new();
c->rewind = g_queue_new();
is->plugin = &input_plugin_curl;
is->data = c;
c->multi = curl_multi_init();
......
......@@ -62,6 +62,7 @@ input_file_open(struct input_stream *is, const char *filename)
posix_fadvise(fd, (off_t)0, is->size, POSIX_FADV_SEQUENTIAL);
#endif
is->plugin = &input_plugin_file;
is->data = GINT_TO_POINTER(fd);
is->ready = true;
......@@ -115,16 +116,9 @@ input_file_eof(struct input_stream *is)
return is->offset >= is->size;
}
static int
input_file_buffer(G_GNUC_UNUSED struct input_stream *is)
{
return 0;
}
const struct input_plugin input_plugin_file = {
.open = input_file_open,
.close = input_file_close,
.buffer = input_file_buffer,
.read = input_file_read,
.eof = input_file_eof,
.seek = input_file_seek,
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "input_mms.h"
#include "input_stream.h"
#include <glib.h>
#include <libmms/mmsx.h>
#include <string.h>
#include <errno.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "jack"
struct input_mms {
mmsx_t *mms;
bool eof;
};
static bool
input_mms_open(struct input_stream *is, const char *url)
{
struct input_mms *m;
if (!g_str_has_prefix(url, "mms://") &&
!g_str_has_prefix(url, "mmsh://") &&
!g_str_has_prefix(url, "mmst://") &&
!g_str_has_prefix(url, "mmsu://"))
return false;
m = g_new(struct input_mms, 1);
m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
if (m->mms == NULL) {
g_warning("mmsx_connect() failed");
return false;
}
/* XX is this correct? at least this selects the ffmpeg
decoder, which seems to work fine*/
is->mime = g_strdup("audio/x-ms-wma");
is->data = m;
is->ready = true;
return true;
}
static size_t
input_mms_read(struct input_stream *is, void *ptr, size_t size)
{
struct input_mms *m = is->data;
int ret;
ret = mmsx_read(NULL, m->mms, ptr, size);
if (ret <= 0) {
if (ret < 0) {
is->error = errno;
g_warning("mmsx_read() failed: %s", g_strerror(errno));
}
m->eof = true;
return false;
}
is->offset += ret;
return (size_t)ret;
}
static void
input_mms_close(struct input_stream *is)
{
struct input_mms *m = is->data;
mmsx_close(m->mms);
g_free(m);
}
static bool
input_mms_eof(struct input_stream *is)
{
struct input_mms *m = is->data;
return m->eof;
}
static int
input_mms_buffer(G_GNUC_UNUSED struct input_stream *is)
{
return 0;
}
static bool
input_mms_seek(G_GNUC_UNUSED struct input_stream *is,
G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
{
return false;
}
const struct input_plugin input_plugin_mms = {
.open = input_mms_open,
.close = input_mms_close,
.buffer = input_mms_buffer,
.read = input_mms_read,
.eof = input_mms_eof,
.seek = input_mms_seek,
};
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
/*
* 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
......@@ -16,12 +16,9 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef MPD_PLAYER_DATA_H
#define MPD_PLAYER_DATA_H
#ifndef INPUT_MMS_H
#define INPUT_MMS_H
extern unsigned int buffered_chunks;
extern unsigned int buffered_before_play;
void initPlayerData(void);
extern const struct input_plugin input_plugin_mms;
#endif
......@@ -29,6 +29,10 @@
#include "input_curl.h"
#endif
#ifdef ENABLE_MMS
#include "input_mms.h"
#endif
#include <glib.h>
#include <assert.h>
......@@ -40,6 +44,9 @@ static const struct input_plugin *const input_plugins[] = {
#ifdef HAVE_CURL
&input_plugin_curl,
#endif
#ifdef ENABLE_MMS
&input_plugin_mms,
#endif
};
static const unsigned num_input_plugins =
......@@ -73,7 +80,14 @@ input_stream_open(struct input_stream *is, const char *url)
const struct input_plugin *plugin = input_plugins[i];
if (plugin->open(is, url)) {
is->plugin = plugin;
assert(is->plugin != NULL);
assert(is->plugin->open == NULL ||
is->plugin == plugin);
assert(is->plugin->close != NULL);
assert(is->plugin->read != NULL);
assert(is->plugin->eof != NULL);
assert(!is->seekable || is->plugin->seek != NULL);
return true;
}
}
......@@ -84,6 +98,9 @@ input_stream_open(struct input_stream *is, const char *url)
bool
input_stream_seek(struct input_stream *is, off_t offset, int whence)
{
if (is->plugin->seek == NULL)
return false;
return is->plugin->seek(is, offset, whence);
}
......@@ -120,5 +137,8 @@ bool input_stream_eof(struct input_stream *is)
int input_stream_buffer(struct input_stream *is)
{
if (is->plugin->buffer == NULL)
return 0;
return is->plugin->buffer(is);
}
......@@ -37,33 +37,90 @@ struct input_plugin {
};
struct input_stream {
/**
* the plugin which implements this input stream
*/
const struct input_plugin *plugin;
bool seekable;
/**
* an opaque pointer managed by the plugin
*/
void *data;
/**
* indicates whether the stream is ready for reading and
* whether the other attributes in this struct are valid
*/
bool ready;
/**
* if true, then the stream is fully seekable
*/
bool seekable;
/**
* an optional errno error code, set to non-zero after an error occured
*/
int error;
off_t size, offset;
char *mime;
void *data;
/**
* the size of the resource, or -1 if unknown
*/
off_t size;
/**
* the current offset within the stream
*/
off_t offset;
void *archive;
/**
* the MIME content type of the resource, or NULL if unknown
*/
char *mime;
};
/**
* Initializes this library and all input_stream implementations.
*/
void input_stream_global_init(void);
/**
* Deinitializes this library and all input_stream implementations.
*/
void input_stream_global_finish(void);
/* if an error occurs for these 3 functions, then -1 is returned and errno
for the input stream is set */
/**
* Opens a new input stream. You may not access it until the "ready"
* flag is set.
*
* @param is the input_stream object allocated by the caller
* @return true on success
*/
bool
input_stream_open(struct input_stream *is, const char *url);
/**
* Closes the input stream and free resources. This does not free the
* input_stream pointer itself, because it is assumed to be allocated
* by the caller.
*/
void
input_stream_close(struct input_stream *is);
/**
* Seeks to the specified position in the stream. This will most
* likely fail if the "seekable" flag is false.
*
* @param is the input_stream object
* @param offset the relative offset
* @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END
*/
bool
input_stream_seek(struct input_stream *is, off_t offset, int whence);
void input_stream_close(struct input_stream *is);
/**
* Returns true if the stream has reached end-of-file.
*/
bool input_stream_eof(struct input_stream *is);
/**
......@@ -75,10 +132,25 @@ bool input_stream_eof(struct input_stream *is);
struct tag *
input_stream_tag(struct input_stream *is);
/* return value: -1 is error, 1 inidicates stuff was buffered, 0 means nothing
was buffered */
/**
* Reads some of the stream into its buffer. The following return
* codes are defined: -1 = error, 1 = something was buffered, 0 =
* nothing was buffered.
*
* The semantics of this function are not well-defined, and it will
* eventually be removed.
*/
int input_stream_buffer(struct input_stream *is);
/**
* Reads data from the stream into the caller-supplied buffer.
* Returns 0 on error or eof (check with input_stream_eof()).
*
* @param is the input_stream object
* @param ptr the buffer to read into
* @param size the maximum number of bytes to read
* @return the number of bytes read
*/
size_t
input_stream_read(struct input_stream *is, void *ptr, size_t size);
......
......@@ -128,7 +128,7 @@ static bool ipv6Supported(void)
static void
parseListenConfigParam(G_GNUC_UNUSED unsigned int port,
struct config_param *param)
const struct config_param *param)
{
const struct sockaddr *addrp;
socklen_t addrlen;
......@@ -253,27 +253,14 @@ parseListenConfigParam(G_GNUC_UNUSED unsigned int port,
void listenOnPort(void)
{
int port = DEFAULT_PORT;
struct config_param *param =
int port = config_get_positive(CONF_PORT, DEFAULT_PORT);
const struct config_param *param =
config_get_next_param(CONF_BIND_TO_ADDRESS, NULL);
struct config_param *portParam = config_get_param(CONF_PORT);
if (portParam) {
char *test;
port = strtol(portParam->value, &test, 10);
if (port <= 0 || *test != '\0') {
g_error("%s \"%s\" specified at line %i is not a "
"positive integer",
CONF_PORT,
portParam->value, portParam->line);
}
}
boundPort = port;
do {
parseListenConfigParam(port, param);
} while ((param = config_get_next_param(CONF_BIND_TO_ADDRESS, param)));
boundPort = port;
}
void closeAllListenSockets(void)
......
......@@ -29,110 +29,122 @@
#define LOCATE_TAG_FILE_KEY_OLD "filename"
#define LOCATE_TAG_ANY_KEY "any"
int getLocateTagItemType(const char *str)
int
locate_parse_type(const char *str)
{
int i;
if (0 == strcasecmp(str, LOCATE_TAG_FILE_KEY) ||
0 == strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD))
{
0 == strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD))
return LOCATE_TAG_FILE_TYPE;
}
if (0 == strcasecmp(str, LOCATE_TAG_ANY_KEY))
{
if (0 == strcasecmp(str, LOCATE_TAG_ANY_KEY))
return LOCATE_TAG_ANY_TYPE;
}
for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
{
for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
if (0 == strcasecmp(str, mpdTagItemKeys[i]))
return i;
}
return -1;
}
static int initLocateTagItem(LocateTagItem * item,
const char *typeStr, const char *needle)
static bool
locate_item_init(struct locate_item *item,
const char *type_string, const char *needle)
{
item->tagType = getLocateTagItemType(typeStr);
item->tag = locate_parse_type(type_string);
if (item->tagType < 0)
return -1;
if (item->tag < 0)
return false;
item->needle = g_strdup(needle);
return 0;
return true;
}
LocateTagItem *newLocateTagItem(const char *typeStr, const char *needle)
struct locate_item *
locate_item_new(const char *type_string, const char *needle)
{
LocateTagItem *ret = g_new(LocateTagItem, 1);
struct locate_item *ret = g_new(struct locate_item, 1);
if (initLocateTagItem(ret, typeStr, needle) < 0) {
free(ret);
if (!locate_item_init(ret, type_string, needle)) {
g_free(ret);
ret = NULL;
}
return ret;
}
void freeLocateTagItemArray(int count, LocateTagItem * array)
void
locate_item_list_free(struct locate_item_list *list)
{
int i;
for (i = 0; i < count; i++)
free(array[i].needle);
for (unsigned i = 0; i < list->length; ++i)
g_free(list->items[i].needle);
free(array);
g_free(list);
}
int newLocateTagItemArrayFromArgArray(char *argArray[],
int numArgs, LocateTagItem ** arrayRet)
struct locate_item_list *
locate_item_list_new(unsigned length)
{
int i, j;
LocateTagItem *item;
struct locate_item_list *list;
list = g_malloc0(sizeof(*list) - sizeof(list->items[0]) +
length * sizeof(list->items[0]));
list->length = length;
if (numArgs == 0)
return 0;
return list;
}
struct locate_item_list *
locate_item_list_parse(char *argv[], int argc)
{
struct locate_item_list *list;
if (numArgs % 2 != 0)
return -1;
if (argc % 2 != 0)
return NULL;
*arrayRet = g_new(LocateTagItem, numArgs / 2);
list = locate_item_list_new(argc / 2);
for (i = 0, item = *arrayRet; i < numArgs / 2; i++, item++) {
if (initLocateTagItem
(item, argArray[i * 2], argArray[i * 2 + 1]) < 0)
goto fail;
for (unsigned i = 0; i < list->length; ++i) {
if (!locate_item_init(&list->items[i], argv[i * 2],
argv[i * 2 + 1])) {
locate_item_list_free(list);
return NULL;
}
}
return numArgs / 2;
return list;
}
struct locate_item_list *
locate_item_list_casefold(const struct locate_item_list *list)
{
struct locate_item_list *new_list = locate_item_list_new(list->length);
fail:
for (j = 0; j < i; j++) {
free((*arrayRet)[j].needle);
for (unsigned i = 0; i < list->length; i++){
new_list->items[i].needle =
g_utf8_casefold(list->items[i].needle, -1);
new_list->items[i].tag = list->items[i].tag;
}
free(*arrayRet);
*arrayRet = NULL;
return -1;
return new_list;
}
void freeLocateTagItem(LocateTagItem * item)
void
locate_item_free(struct locate_item *item)
{
free(item->needle);
free(item);
g_free(item->needle);
g_free(item);
}
static int
strstrSearchTag(struct song *song, enum tag_type type, char *str)
static bool
locate_tag_search(const struct song *song, enum tag_type type, const char *str)
{
int i;
char *duplicate;
int ret = 0;
int8_t visitedTypes[TAG_NUM_OF_ITEM_TYPES] = { 0 };
bool ret = false;
bool visited_types[TAG_NUM_OF_ITEM_TYPES];
if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) {
char *uri, *p;
......@@ -142,17 +154,19 @@ strstrSearchTag(struct song *song, enum tag_type type, char *str)
g_free(uri);
if (strstr(p, str))
ret = 1;
ret = true;
g_free(p);
if (ret == 1 || type == LOCATE_TAG_FILE_TYPE)
return ret;
}
if (!song->tag)
return 0;
return false;
memset(visited_types, 0, sizeof(visited_types));
for (i = 0; i < song->tag->numOfItems && !ret; i++) {
visitedTypes[song->tag->items[i]->type] = 1;
visited_types[song->tag->items[i]->type] = true;
if (type != LOCATE_TAG_ANY_TYPE &&
song->tag->items[i]->type != type) {
continue;
......@@ -160,7 +174,7 @@ strstrSearchTag(struct song *song, enum tag_type type, char *str)
duplicate = g_utf8_casefold(song->tag->items[i]->value, -1);
if (*str && strstr(duplicate, str))
ret = 1;
ret = true;
g_free(duplicate);
}
......@@ -168,34 +182,31 @@ strstrSearchTag(struct song *song, enum tag_type type, char *str)
* through the song's tag, it means this field is absent from
* the tag or empty. Thus, if the searched string is also
* empty (first char is a \0), then it's a match as well and
* we should return 1.
* we should return true.
*/
if (!*str && !visitedTypes[type])
return 1;
if (!*str && !visited_types[type])
return true;
return ret;
}
int
strstrSearchTags(struct song *song, int numItems, LocateTagItem *items)
bool
locate_song_search(const struct song *song,
const struct locate_item_list *criteria)
{
int i;
for (i = 0; i < numItems; i++) {
if (!strstrSearchTag(song, items[i].tagType,
items[i].needle)) {
return 0;
}
}
for (unsigned i = 0; i < criteria->length; i++)
if (!locate_tag_search(song, criteria->items[i].tag,
criteria->items[i].needle))
return false;
return 1;
return true;
}
static int
tagItemFoundAndMatches(struct song *song, enum tag_type type, char *str)
static bool
locate_tag_match(const struct song *song, enum tag_type type, const char *str)
{
int i;
int8_t visitedTypes[TAG_NUM_OF_ITEM_TYPES] = { 0 };
bool visited_types[TAG_NUM_OF_ITEM_TYPES];
if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) {
char *uri = song_get_uri(song);
......@@ -203,51 +214,48 @@ tagItemFoundAndMatches(struct song *song, enum tag_type type, char *str)
g_free(uri);
if (matches)
return 1;
return true;
if (type == LOCATE_TAG_FILE_TYPE)
return 0;
return false;
}
if (!song->tag)
return 0;
return false;
memset(visited_types, 0, sizeof(visited_types));
for (i = 0; i < song->tag->numOfItems; i++) {
visitedTypes[song->tag->items[i]->type] = 1;
visited_types[song->tag->items[i]->type] = true;
if (type != LOCATE_TAG_ANY_TYPE &&
song->tag->items[i]->type != type) {
continue;
}
if (0 == strcmp(str, song->tag->items[i]->value))
return 1;
return true;
}
/** If the search critieron was not visited during the sweep
* through the song's tag, it means this field is absent from
* the tag or empty. Thus, if the searched string is also
* empty (first char is a \0), then it's a match as well and
* we should return 1.
* we should return true.
*/
if (!*str && !visitedTypes[type])
return 1;
if (!*str && !visited_types[type])
return true;
return 0;
return false;
}
int
tagItemsFoundAndMatches(struct song *song, int numItems,
LocateTagItem * items)
bool
locate_song_match(const struct song *song,
const struct locate_item_list *criteria)
{
int i;
for (i = 0; i < numItems; i++) {
if (!tagItemFoundAndMatches(song, items[i].tagType,
items[i].needle)) {
return 0;
}
}
for (unsigned i = 0; i < criteria->length; i++)
if (!locate_tag_match(song, criteria->items[i].tag,
criteria->items[i].needle))
return false;
return 1;
return true;
}
......@@ -20,6 +20,7 @@
#define MPD_LOCATE_H
#include <stdint.h>
#include <stdbool.h>
#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10
#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20
......@@ -27,30 +28,60 @@
struct song;
/* struct used for search, find, list queries */
typedef struct _LocateTagItem {
int8_t tagType;
struct locate_item {
int8_t tag;
/* what we are looking for */
char *needle;
} LocateTagItem;
};
int getLocateTagItemType(const char *str);
/**
* An array of struct locate_item objects.
*/
struct locate_item_list {
/** number of items */
unsigned length;
/** this is a variable length array */
struct locate_item items[1];
};
int
locate_parse_type(const char *str);
/* returns NULL if not a known type */
LocateTagItem *newLocateTagItem(const char *typeString, const char *needle);
struct locate_item *
locate_item_new(const char *type_string, const char *needle);
/**
* Allocates a new struct locate_item_list, and initializes all
* members with zero bytes.
*/
struct locate_item_list *
locate_item_list_new(unsigned length);
/* return number of items or -1 on error */
int newLocateTagItemArrayFromArgArray(char *argArray[], int numArgs,
LocateTagItem ** arrayRet);
struct locate_item_list *
locate_item_list_parse(char *argv[], int argc);
void freeLocateTagItemArray(int count, LocateTagItem * array);
/**
* Duplicate the struct locate_item_list object and convert all
* needles with g_utf8_casefold().
*/
struct locate_item_list *
locate_item_list_casefold(const struct locate_item_list *list);
void freeLocateTagItem(LocateTagItem * item);
void
locate_item_list_free(struct locate_item_list *list);
int
strstrSearchTags(struct song *song, int numItems, LocateTagItem * items);
void
locate_item_free(struct locate_item *item);
int
tagItemsFoundAndMatches(struct song *song, int numItems,
LocateTagItem * items);
bool
locate_song_search(const struct song *song,
const struct locate_item_list *criteria);
bool
locate_song_match(const struct song *song,
const struct locate_item_list *criteria);
#endif
......@@ -219,7 +219,7 @@ parse_log_level(const char *value, unsigned line)
void log_init(bool verbose, bool use_stdout)
{
struct config_param *param;
const struct config_param *param;
g_get_charset(&log_charset);
......@@ -252,9 +252,8 @@ void log_init(bool verbose, bool use_stdout)
if (path == NULL)
g_error("error parsing \"%s\" at line %i\n",
CONF_LOG_FILE, param->line);
param->value = path;
log_init_file(param->value, param->line);
log_init_file(path, param->line);
}
}
}
......
......@@ -27,6 +27,12 @@ static const char *remoteUrlPrefixes[] = {
#ifdef HAVE_CURL
"http://",
#endif
#ifdef ENABLE_MMS
"mms://",
"mmsh://",
"mmst://",
"mmsu://",
#endif
NULL
};
......
......@@ -22,6 +22,7 @@
#include "idle.h"
#include "command.h"
#include "playlist.h"
#include "stored_playlist.h"
#include "database.h"
#include "update.h"
#include "player_thread.h"
......@@ -30,14 +31,13 @@
#include "conf.h"
#include "path.h"
#include "mapper.h"
#include "playerData.h"
#include "pipe.h"
#include "decoder_thread.h"
#include "decoder_control.h"
#include "player_control.h"
#include "stats.h"
#include "sig_handlers.h"
#include "audio.h"
#include "output_all.h"
#include "volume.h"
#include "log.h"
#include "permission.h"
......@@ -55,6 +55,10 @@
#include "songvec.h"
#include "tag_pool.h"
#ifdef ENABLE_SQLITE
#include "sticker.h"
#endif
#ifdef ENABLE_ARCHIVE
#include "archive_list.h"
#endif
......@@ -63,143 +67,116 @@
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#ifndef WIN32
#include <pwd.h>
#include <grp.h>
#endif
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
enum {
DEFAULT_BUFFER_SIZE = 2048,
DEFAULT_BUFFER_BEFORE_PLAY = 10,
};
GThread *main_task;
GMainLoop *main_loop;
struct notify main_notify;
static void changeToUser(void)
/**
* Returns the database. If this function returns false, this has not
* succeeded, and the caller should create the database after the
* process has been daemonized.
*/
static bool
openDB(const Options *options)
{
#ifndef WIN32
struct config_param *param = config_get_param(CONF_USER);
if (param && strlen(param->value)) {
/* get uid */
struct passwd *userpwd;
if ((userpwd = getpwnam(param->value)) == NULL) {
g_error("no such user \"%s\" at line %i",
param->value, param->line);
}
if (setgid(userpwd->pw_gid) == -1) {
g_error("cannot setgid for user \"%s\" at line %i: %s",
param->value, param->line, strerror(errno));
}
#ifdef _BSD_SOURCE
/* init suplementary groups
* (must be done before we change our uid)
*/
if (initgroups(param->value, userpwd->pw_gid) == -1) {
g_warning("cannot init supplementary groups "
"of user \"%s\" at line %i: %s",
param->value, param->line, strerror(errno));
}
#endif
/* set uid */
if (setuid(userpwd->pw_uid) == -1) {
g_error("cannot change to uid of user "
"\"%s\" at line %i: %s",
param->value, param->line, strerror(errno));
}
/* this is needed by libs such as arts */
if (userpwd->pw_dir) {
g_setenv("HOME", userpwd->pw_dir, true);
}
const char *path = config_get_path(CONF_DB_FILE);
bool ret;
if (!mapper_has_music_directory()) {
if (path != NULL)
g_message("Found " CONF_DB_FILE " setting without "
CONF_MUSIC_DIR " - disabling database");
db_init(NULL);
return true;
}
#endif
}
static void openDB(Options * options, char *argv0)
{
db_init();
if (path == NULL)
g_error(CONF_DB_FILE " setting missing");
if (options->createDB > 0 || !db_load()) {
unsigned job;
db_init(path);
if (options->createDB < 0) {
if (options->createDB > 0)
/* don't attempt to load the old database */
return false;
ret = db_load();
if (!ret) {
if (options->createDB < 0)
g_error("can't open db file and using "
"\"--no-create-db\" command line option; "
"try running \"%s --create-db\"", argv0);
}
"\"--no-create-db\" command line option");
if (!db_check())
exit(EXIT_FAILURE);
db_clear();
job = directory_update_init(NULL);
if (job == 0)
g_error("directory update failed");
/* run database update after daemonization */
return false;
}
}
static void cleanUpPidFile(void)
{
struct config_param *pidFileParam =
parseConfigFilePath(CONF_PID_FILE, 0);
if (!pidFileParam)
return;
g_debug("cleaning up pid file");
unlink(pidFileParam->value);
return true;
}
static void killFromPidFile(void)
/**
* Initialize the decoder and player core, including the music pipe.
*/
static void
initialize_decoder_and_player(void)
{
#ifndef WIN32
FILE *fp;
struct config_param *pidFileParam =
parseConfigFilePath(CONF_PID_FILE, 0);
int pid;
if (!pidFileParam) {
g_error("no pid_file specified in the config file");
}
const struct config_param *param;
char *test;
size_t buffer_size;
float perc;
unsigned buffered_chunks;
unsigned buffered_before_play;
param = config_get_param(CONF_AUDIO_BUFFER_SIZE);
if (param != NULL) {
buffer_size = strtol(param->value, &test, 10);
if (*test != '\0' || buffer_size <= 0)
g_error("buffer size \"%s\" is not a positive integer, "
"line %i\n", param->value, param->line);
} else
buffer_size = DEFAULT_BUFFER_SIZE;
buffer_size *= 1024;
buffered_chunks = buffer_size / CHUNK_SIZE;
if (buffered_chunks >= 1 << 15)
g_error("buffer size \"%li\" is too big\n", (long)buffer_size);
param = config_get_param(CONF_BUFFER_BEFORE_PLAY);
if (param != NULL) {
perc = strtod(param->value, &test);
if (*test != '%' || perc < 0 || perc > 100) {
FATAL("buffered before play \"%s\" is not a positive "
"percentage and less than 100 percent, line %i"
"\n", param->value, param->line);
}
} else
perc = DEFAULT_BUFFER_BEFORE_PLAY;
fp = fopen(pidFileParam->value, "r");
if (!fp) {
g_error("unable to open %s \"%s\": %s",
CONF_PID_FILE, pidFileParam->value, strerror(errno));
}
if (fscanf(fp, "%i", &pid) != 1) {
g_error("unable to read the pid from file \"%s\"",
pidFileParam->value);
}
fclose(fp);
buffered_before_play = (perc / 100) * buffered_chunks;
if (buffered_before_play > buffered_chunks)
buffered_before_play = buffered_chunks;
if (kill(pid, SIGTERM)) {
g_error("unable to kill proccess %i: %s",
pid, strerror(errno));
}
exit(EXIT_SUCCESS);
#else
g_error("--kill is not available on WIN32");
#endif
}
static gboolean
timer_save_state_file(G_GNUC_UNUSED gpointer data)
{
g_debug("Saving state file");
write_state_file();
return true;
pc_init(buffered_before_play);
music_pipe_init(buffered_chunks, &pc.notify);
dc_init();
}
/**
......@@ -219,8 +196,7 @@ int main(int argc, char *argv[])
{
Options options;
clock_t start;
GTimer *save_state_timer;
guint save_state_source_id;
bool create_db;
daemonize_close_stdin();
......@@ -240,16 +216,19 @@ int main(int argc, char *argv[])
parseOptions(argc, argv, &options);
daemonize_init(config_get_string(CONF_USER, NULL),
config_get_path(CONF_PID_FILE));
if (options.kill)
killFromPidFile();
daemonize_kill();
initStats();
stats_global_init();
tag_lib_init();
log_init(options.verbose, options.stdOutput);
listenOnPort();
changeToUser();
daemonize_set_user();
main_task = g_thread_self();
main_loop = g_main_loop_new(NULL, FALSE);
......@@ -257,34 +236,35 @@ int main(int argc, char *argv[])
event_pipe_init();
event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted);
event_pipe_register(PIPE_EVENT_PLAYLIST, syncPlayerAndPlaylist);
path_global_init();
mapper_init();
initPermissions();
initPlaylist();
spl_global_init();
#ifdef ENABLE_ARCHIVE
archive_plugin_init_all();
#endif
decoder_plugin_init_all();
update_global_init();
openDB(&options, argv[0]);
create_db = !openDB(&options);
#ifdef ENABLE_SQLITE
sticker_global_init(config_get_path(CONF_STICKER_FILE));
#endif
command_init();
initPlayerData();
pc_init(buffered_before_play);
music_pipe_init(buffered_chunks, &pc.notify);
dc_init();
initAudioConfig();
initAudioDriver();
initialize_decoder_and_player();
volume_init();
initAudioConfig();
audio_output_all_init();
client_manager_init();
replay_gain_global_init();
initNormalization();
input_stream_global_init();
daemonize(&options);
daemonize(options.daemon);
setup_log_output(options.stdOutput);
......@@ -292,14 +272,18 @@ int main(int argc, char *argv[])
initZeroconf();
decoder_thread_start();
player_create();
read_state_file();
save_state_timer = g_timer_new();
if (create_db) {
/* the database failed to load, or MPD was started
with --create-db: recreate a new database */
unsigned job = directory_update_init(NULL);
if (job == 0)
g_error("directory update failed");
}
save_state_source_id = g_timeout_add(5 * 60 * 1000,
timer_save_state_file, NULL);
state_file_init(config_get_path(CONF_STATE_FILE));
/* run the main loop */
......@@ -309,10 +293,7 @@ int main(int argc, char *argv[])
g_main_loop_unref(main_loop);
g_source_remove(save_state_source_id);
g_timer_destroy(save_state_timer);
write_state_file();
state_file_finish();
playerKill();
finishZeroconf();
client_manager_deinit();
......@@ -324,12 +305,16 @@ int main(int argc, char *argv[])
g_debug("db_finish took %f seconds",
((float)(clock()-start))/CLOCKS_PER_SEC);
#ifdef ENABLE_SQLITE
sticker_global_finish();
#endif
notify_deinit(&main_notify);
event_pipe_deinit();
input_stream_global_finish();
finishNormalization();
finishAudioDriver();
audio_output_all_finish();
finishAudioConfig();
volume_finish();
mapper_finish();
......@@ -344,12 +329,13 @@ int main(int argc, char *argv[])
archive_plugin_deinit_all();
#endif
music_pipe_free();
cleanUpPidFile();
config_global_finish();
tag_pool_deinit();
songvec_deinit();
dirvec_deinit();
idle_deinit();
stats_global_finish();
daemonize_finish();
close_log_files();
return EXIT_SUCCESS;
......
......@@ -39,51 +39,74 @@ static char *music_dir;
static size_t music_dir_length;
static char *playlist_dir;
static size_t playlist_dir_length;
void mapper_init(void)
/**
* Duplicate a string, chop all trailing slashes.
*/
static char *
strdup_chop_slash(const char *path_fs)
{
size_t length = strlen(path_fs);
while (length > 0 && path_fs[length - 1] == G_DIR_SEPARATOR)
--length;
return g_strndup(path_fs, length);
}
static void
mapper_set_music_dir(const char *path)
{
struct config_param *music_dir_param =
parseConfigFilePath(CONF_MUSIC_DIR, false);
struct config_param *playlist_dir_param =
parseConfigFilePath(CONF_PLAYLIST_DIR, 1);
int ret;
struct stat st;
if (music_dir_param != NULL) {
music_dir = g_strdup(music_dir_param->value);
} else {
#if GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 14)
music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC));
if (music_dir == NULL)
/* GLib failed to determine the XDG music
directory - abort */
#endif
g_error("config parameter \"%s\" not found\n", CONF_MUSIC_DIR);
}
music_dir = strdup_chop_slash(path);
music_dir_length = strlen(music_dir);
ret = stat(music_dir, &st);
if (ret < 0)
g_warning("failed to stat music directory \"%s\" (config line %i): %s\n",
music_dir_param->value, music_dir_param->line,
strerror(errno));
g_warning("failed to stat music directory \"%s\": %s",
music_dir, g_strerror(errno));
else if (!S_ISDIR(st.st_mode))
g_warning("music directory is not a directory: \"%s\" (config line %i)\n",
music_dir_param->value, music_dir_param->line);
g_warning("music directory is not a directory: \"%s\"",
music_dir);
}
playlist_dir = g_strdup(playlist_dir_param->value);
playlist_dir_length = strlen(playlist_dir);
static void
mapper_set_playlist_dir(const char *path)
{
int ret;
struct stat st;
playlist_dir = g_strdup(path);
ret = stat(playlist_dir, &st);
if (ret < 0)
g_warning("failed to stat playlist directory \"%s\" (config line %i): %s\n",
playlist_dir_param->value, playlist_dir_param->line,
strerror(errno));
g_warning("failed to stat playlist directory \"%s\": %s",
playlist_dir, g_strerror(errno));
else if (!S_ISDIR(st.st_mode))
g_warning("playlist directory is not a directory: \"%s\" (config line %i)\n",
playlist_dir_param->value, playlist_dir_param->line);
g_warning("playlist directory is not a directory: \"%s\"",
playlist_dir);
}
void mapper_init(void)
{
const char *path;
path = config_get_path(CONF_MUSIC_DIR);
if (path != NULL)
mapper_set_music_dir(path);
#if GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 14)
else {
path = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
if (path != NULL)
mapper_set_music_dir(path);
}
#endif
path = config_get_path(CONF_PLAYLIST_DIR);
if (path != NULL)
mapper_set_playlist_dir(path);
}
void mapper_finish(void)
......@@ -92,6 +115,12 @@ void mapper_finish(void)
g_free(playlist_dir);
}
bool
mapper_has_music_directory(void)
{
return music_dir != NULL;
}
char *
map_uri_fs(const char *uri)
{
......@@ -100,6 +129,9 @@ map_uri_fs(const char *uri)
assert(uri != NULL);
assert(*uri != '/');
if (music_dir == NULL)
return NULL;
uri_fs = utf8_to_fs_charset(uri);
if (uri_fs == NULL)
return NULL;
......@@ -113,6 +145,8 @@ map_uri_fs(const char *uri)
char *
map_directory_fs(const struct directory *directory)
{
assert(music_dir != NULL);
if (directory_is_root(directory))
return g_strdup(music_dir);
......@@ -124,6 +158,8 @@ map_directory_child_fs(const struct directory *directory, const char *name)
{
char *name_fs, *parent_fs, *path;
assert(music_dir != NULL);
/* check for invalid or unauthorized base names */
if (*name == 0 || strchr(name, '/') != NULL ||
strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
......@@ -160,7 +196,8 @@ map_song_fs(const struct song *song)
char *
map_fs_to_utf8(const char *path_fs)
{
if (strncmp(path_fs, music_dir, music_dir_length) == 0 &&
if (music_dir != NULL &&
strncmp(path_fs, music_dir, music_dir_length) == 0 &&
path_fs[music_dir_length] == '/')
/* remove musicDir prefix */
path_fs += music_dir_length + 1;
......@@ -168,6 +205,9 @@ map_fs_to_utf8(const char *path_fs)
/* not within musicDir */
return NULL;
while (path_fs[0] == G_DIR_SEPARATOR)
++path_fs;
return fs_charset_to_utf8(path_fs);
}
......@@ -180,9 +220,12 @@ map_spl_path(void)
char *
map_spl_utf8_to_fs(const char *name)
{
char *filename = g_strconcat(name, "." PLAYLIST_FILE_SUFFIX, NULL);
char *filename = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL);
char *path;
if (playlist_dir == NULL)
return NULL;
path = g_build_filename(playlist_dir, filename, NULL);
g_free(filename);
......
......@@ -23,7 +23,9 @@
#ifndef MPD_MAPPER_H
#define MPD_MAPPER_H
#define PLAYLIST_FILE_SUFFIX "m3u"
#include <stdbool.h>
#define PLAYLIST_FILE_SUFFIX ".m3u"
struct directory;
struct song;
......@@ -33,6 +35,12 @@ void mapper_init(void);
void mapper_finish(void);
/**
* Returns true if a music directory was configured.
*/
bool
mapper_has_music_directory(void);
/**
* Determines the absolute file system path of a relative URI. This
* is basically done by converting the URI to the file system charset
* and prepending the music directory.
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../output_api.h"
#include "../mixer_api.h"
......@@ -9,6 +26,9 @@
#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
struct alsa_mixer {
/** the base mixer class */
struct mixer base;
char *device;
char *control;
snd_mixer_t *handle;
......@@ -18,54 +38,37 @@ struct alsa_mixer {
int volume_set;
};
static struct mixer_data *
alsa_mixer_init(void)
static struct mixer *
alsa_mixer_init(const struct config_param *param)
{
struct alsa_mixer *am = g_malloc(sizeof(struct alsa_mixer));
am->device = NULL;
am->control = NULL;
struct alsa_mixer *am = g_new(struct alsa_mixer, 1);
mixer_init(&am->base, &alsa_mixer);
am->device = config_dup_block_string(param, "mixer_device", NULL);
am->control = config_dup_block_string(param, "mixer_control", NULL);
am->handle = NULL;
am->elem = NULL;
am->volume_min = 0;
am->volume_max = 0;
am->volume_set = -1;
return (struct mixer_data *)am;
}
static void
alsa_mixer_finish(struct mixer_data *data)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
if (am->device)
g_free(am->device);
if (am->control)
g_free(am->control);
g_free(am);
return &am->base;
}
static void
alsa_mixer_configure(struct mixer_data *data, struct config_param *param)
alsa_mixer_finish(struct mixer *data)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
struct block_param *bp;
if (param == NULL)
return;
if ((bp = getBlockParam(param, "mixer_device"))) {
if (am->device)
g_free(am->device);
am->device = g_strdup(bp->value);
}
if ((bp = getBlockParam(param, "mixer_control"))) {
if (am->control)
g_free(am->control);
am->control = g_strdup(bp->value);
}
g_free(am->device);
g_free(am->control);
g_free(am);
}
static void
alsa_mixer_close(struct mixer_data *data)
alsa_mixer_close(struct mixer *data)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
if (am->handle) snd_mixer_close(am->handle);
......@@ -73,7 +76,7 @@ alsa_mixer_close(struct mixer_data *data)
}
static bool
alsa_mixer_open(struct mixer_data *data)
alsa_mixer_open(struct mixer *data)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
int err;
......@@ -144,15 +147,10 @@ alsa_mixer_open(struct mixer_data *data)
}
static bool
alsa_mixer_control(struct mixer_data *data, int cmd, void *arg)
alsa_mixer_control(struct mixer *data, int cmd, void *arg)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
switch (cmd) {
case AC_MIXER_CONFIGURE:
alsa_mixer_configure(data, (struct config_param *)arg);
if (am->handle)
alsa_mixer_close(data);
return true;
case AC_MIXER_GETVOL:
{
int err;
......@@ -223,10 +221,9 @@ alsa_mixer_control(struct mixer_data *data, int cmd, void *arg)
return false;
}
struct mixer_plugin alsa_mixer = {
const struct mixer_plugin alsa_mixer = {
.init = alsa_mixer_init,
.finish = alsa_mixer_finish,
.configure = alsa_mixer_configure,
.open = alsa_mixer_open,
.control = alsa_mixer_control,
.close = alsa_mixer_close
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../output_api.h"
#include "../mixer_api.h"
......@@ -19,59 +36,43 @@
#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer"
struct oss_mixer {
/** the base mixer class */
struct mixer base;
char *device;
char *control;
int device_fd;
int volume_control;
};
static struct mixer_data *
oss_mixer_init(void)
static struct mixer *
oss_mixer_init(const struct config_param *param)
{
struct oss_mixer *om = g_malloc(sizeof(struct oss_mixer));
om->device = NULL;
om->control = NULL;
struct oss_mixer *om = g_new(struct oss_mixer, 1);
mixer_init(&om->base, &oss_mixer);
om->device = config_dup_block_string(param, "mixer_device", NULL);
om->control = config_dup_block_string(param, "mixer_control", NULL);
om->device_fd = -1;
om->volume_control = SOUND_MIXER_PCM;
return (struct mixer_data *)om;
}
static void
oss_mixer_finish(struct mixer_data *data)
{
struct oss_mixer *om = (struct oss_mixer *) data;
if (om->device)
g_free(om->device);
if (om->control)
g_free(om->control);
g_free(om);
return &om->base;
}
static void
oss_mixer_configure(struct mixer_data *data, struct config_param *param)
oss_mixer_finish(struct mixer *data)
{
struct oss_mixer *om = (struct oss_mixer *) data;
struct block_param *bp;
if (param == NULL)
return;
bp = getBlockParam(param, "mixer_device");
if (bp) {
if (om->device)
g_free(om->device);
om->device = g_strdup(bp->value);
}
bp = getBlockParam(param, "mixer_control");
if (bp) {
if (om->control)
g_free(om->control);
om->control = g_strdup(bp->value);
}
g_free(om->device);
g_free(om->control);
g_free(om);
}
static void
oss_mixer_close(struct mixer_data *data)
oss_mixer_close(struct mixer *data)
{
struct oss_mixer *om = (struct oss_mixer *) data;
if (om->device_fd != -1)
......@@ -95,7 +96,7 @@ oss_find_mixer(const char *name)
}
static bool
oss_mixer_open(struct mixer_data *data)
oss_mixer_open(struct mixer *data)
{
struct oss_mixer *om = (struct oss_mixer *) data;
const char *device = VOLUME_MIXER_OSS_DEFAULT;
......@@ -137,16 +138,10 @@ oss_mixer_open(struct mixer_data *data)
}
static bool
oss_mixer_control(struct mixer_data *data, int cmd, void *arg)
oss_mixer_control(struct mixer *data, int cmd, void *arg)
{
struct oss_mixer *om = (struct oss_mixer *) data;
switch (cmd) {
case AC_MIXER_CONFIGURE:
oss_mixer_configure(data, (struct config_param *)arg);
if (om->device_fd >= 0)
oss_mixer_close(data);
return true;
break;
case AC_MIXER_GETVOL:
{
int left, right, level;
......@@ -206,10 +201,9 @@ oss_mixer_control(struct mixer_data *data, int cmd, void *arg)
return false;
}
struct mixer_plugin oss_mixer = {
const struct mixer_plugin oss_mixer = {
.init = oss_mixer_init,
.finish = oss_mixer_finish,
.configure = oss_mixer_configure,
.open = oss_mixer_open,
.control = oss_mixer_control,
.close = oss_mixer_close
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <assert.h>
#include "mixer_api.h"
void mixer_init(struct mixer *mixer, struct mixer_plugin *plugin)
struct mixer *
mixer_new(const struct mixer_plugin *plugin, const struct config_param *param)
{
struct mixer *mixer;
assert(plugin != NULL);
assert(mixer != NULL);
mixer->plugin = plugin;
mixer->data = mixer->plugin->init();
}
void mixer_finish(struct mixer *mixer)
{
assert(mixer != NULL && mixer->plugin != NULL);
mixer->plugin->finish(mixer->data);
mixer->data = NULL;
mixer->plugin = NULL;
mixer = plugin->init(param);
assert(mixer->plugin == plugin);
return mixer;
}
void mixer_configure(struct mixer *mixer, struct config_param *param)
void
mixer_free(struct mixer *mixer)
{
assert(mixer != NULL && mixer->plugin != NULL);
mixer->plugin->configure(mixer->data, param);
assert(mixer != NULL);
assert(mixer->plugin != NULL);
mixer->plugin->finish(mixer);
}
bool mixer_open(struct mixer *mixer)
{
assert(mixer != NULL && mixer->plugin != NULL);
return mixer->plugin->open(mixer->data);
return mixer->plugin->open(mixer);
}
bool mixer_control(struct mixer *mixer, int cmd, void *arg)
{
assert(mixer != NULL && mixer->plugin != NULL);
return mixer->plugin->control(mixer->data, cmd, arg);
return mixer->plugin->control(mixer, cmd, arg);
}
void mixer_close(struct mixer *mixer)
{
assert(mixer != NULL && mixer->plugin != NULL);
mixer->plugin->close(mixer->data);
mixer->plugin->close(mixer);
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef MPD_MIXER_H
#define MPD_MIXER_H
......@@ -8,52 +25,52 @@
* list of currently implemented mixers
*/
extern struct mixer_plugin alsa_mixer;
extern struct mixer_plugin oss_mixer;
struct mixer_data;
extern const struct mixer_plugin alsa_mixer;
extern const struct mixer_plugin oss_mixer;
struct mixer_plugin {
/**
* Allocate and initialize mixer data
/**
* Alocates and configures a mixer device.
*/
struct mixer_data *(*init)(void);
struct mixer *(*init)(const struct config_param *param);
/**
* Finish and free mixer data
*/
void (*finish)(struct mixer_data *data);
/**
* Setup and configure mixer
*/
void (*configure)(struct mixer_data *data, struct config_param *param);
void (*finish)(struct mixer *data);
/**
* Open mixer device
*/
bool (*open)(struct mixer_data *data);
bool (*open)(struct mixer *data);
/**
* Control mixer device.
*/
bool (*control)(struct mixer_data *data, int cmd, void *arg);
bool (*control)(struct mixer *data, int cmd, void *arg);
/**
* Close mixer device
*/
void (*close)(struct mixer_data *data);
void (*close)(struct mixer *data);
};
struct mixer {
struct mixer_plugin *plugin;
struct mixer_data *data;
const struct mixer_plugin *plugin;
};
void mixer_init(struct mixer *mixer, struct mixer_plugin *plugin);
void mixer_finish(struct mixer *mixer);
void mixer_configure(struct mixer *mixer, struct config_param *param);
static inline void
mixer_init(struct mixer *mixer, const struct mixer_plugin *plugin)
{
mixer->plugin = plugin;
}
struct mixer *
mixer_new(const struct mixer_plugin *plugin, const struct config_param *param);
void
mixer_free(struct mixer *mixer);
bool mixer_open(struct mixer *mixer);
bool mixer_control(struct mixer *mixer, int cmd, void *arg);
void mixer_close(struct mixer *mixer);
......
......@@ -32,6 +32,7 @@ static const char default_device[] = "default";
enum {
MPD_ALSA_BUFFER_TIME_US = 500000,
MPD_ALSA_PERIOD_TIME_US = 125000,
};
#define MPD_ALSA_RETRY_NR 5
......@@ -39,68 +40,77 @@ enum {
typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer,
snd_pcm_uframes_t size);
typedef struct _AlsaData {
struct alsa_data {
/** the configured name of the ALSA device; NULL for the
default device */
char *device;
/** use memory mapped I/O? */
bool use_mmap;
/** libasound's buffer_time setting (in microseconds) */
unsigned int buffer_time;
/** libasound's period_time setting (in microseconds) */
unsigned int period_time;
/** the mode flags passed to snd_pcm_open */
int mode;
snd_pcm_t *pcmHandle;
/** the libasound PCM device handle */
snd_pcm_t *pcm;
/**
* a pointer to the libasound writei() function, which is
* snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the
* use_mmap configuration
*/
alsa_writei_t *writei;
unsigned int buffer_time;
unsigned int period_time;
int sampleSize;
int useMmap;
struct mixer mixer;
/** the size of one audio frame */
size_t frame_size;
} AlsaData;
/** the mixer object associated with this output */
struct mixer *mixer;
};
static const char *
alsa_device(const AlsaData *ad)
alsa_device(const struct alsa_data *ad)
{
return ad->device != NULL ? ad->device : default_device;
}
static AlsaData *newAlsaData(void)
static struct alsa_data *
alsa_data_new(void)
{
AlsaData *ret = g_new(AlsaData, 1);
struct alsa_data *ret = g_new(struct alsa_data, 1);
ret->device = NULL;
ret->mode = 0;
ret->pcmHandle = NULL;
ret->pcm = NULL;
ret->writei = snd_pcm_writei;
ret->useMmap = 0;
ret->buffer_time = MPD_ALSA_BUFFER_TIME_US;
ret->period_time = 0;
//use alsa mixer by default
mixer_init(&ret->mixer, &alsa_mixer);
return ret;
}
static void freeAlsaData(AlsaData * ad)
static void
alsa_data_free(struct alsa_data *ad)
{
g_free(ad->device);
mixer_finish(&ad->mixer);
free(ad);
mixer_free(ad->mixer);
g_free(ad);
}
static void
alsa_configure(AlsaData *ad, struct config_param *param)
alsa_configure(struct alsa_data *ad, const struct config_param *param)
{
struct block_param *bp;
ad->device = config_dup_block_string(param, "device", NULL);
if ((bp = getBlockParam(param, "device")))
ad->device = g_strdup(bp->value);
ad->use_mmap = config_get_block_bool(param, "use_mmap", false);
ad->useMmap = config_get_block_bool(param, "use_mmap", false);
if ((bp = getBlockParam(param, "buffer_time")))
ad->buffer_time = atoi(bp->value);
if ((bp = getBlockParam(param, "period_time")))
ad->period_time = atoi(bp->value);
ad->buffer_time = config_get_block_unsigned(param, "buffer_time",
MPD_ALSA_BUFFER_TIME_US);
ad->period_time = config_get_block_unsigned(param, "period_time",
MPD_ALSA_PERIOD_TIME_US);
#ifdef SND_PCM_NO_AUTO_RESAMPLE
if (!config_get_block_bool(param, "auto_resample", true))
......@@ -119,35 +129,35 @@ alsa_configure(AlsaData *ad, struct config_param *param)
}
static void *
alsa_initDriver(G_GNUC_UNUSED struct audio_output *ao,
G_GNUC_UNUSED const struct audio_format *audio_format,
struct config_param *param)
alsa_init(G_GNUC_UNUSED struct audio_output *ao,
G_GNUC_UNUSED const struct audio_format *audio_format,
const struct config_param *param)
{
/* no need for pthread_once thread-safety when reading config */
static int free_global_registered;
AlsaData *ad = newAlsaData();
struct alsa_data *ad = alsa_data_new();
if (!free_global_registered) {
atexit((void(*)(void))snd_config_update_free_global);
free_global_registered = 1;
}
if (param) {
alsa_configure(ad, param);
mixer_configure(&ad->mixer, param);
}
alsa_configure(ad, param);
ad->mixer = mixer_new(&alsa_mixer, param);
return ad;
}
static void alsa_finishDriver(void *data)
static void
alsa_finish(void *data)
{
AlsaData *ad = data;
struct alsa_data *ad = data;
freeAlsaData(ad);
alsa_data_free(ad);
}
static bool alsa_testDefault(void)
static bool
alsa_test_default_device(void)
{
snd_pcm_t *handle;
......@@ -163,7 +173,8 @@ static bool alsa_testDefault(void)
return true;
}
static snd_pcm_format_t get_bitformat(const struct audio_format *af)
static snd_pcm_format_t
get_bitformat(const struct audio_format *af)
{
switch (af->bits) {
case 8: return SND_PCM_FORMAT_S8;
......@@ -174,14 +185,15 @@ static snd_pcm_format_t get_bitformat(const struct audio_format *af)
return SND_PCM_FORMAT_UNKNOWN;
}
static bool alsa_openDevice(void *data, struct audio_format *audioFormat)
static bool
alsa_open(void *data, struct audio_format *audio_format)
{
AlsaData *ad = data;
struct alsa_data *ad = data;
snd_pcm_format_t bitformat;
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
unsigned int sample_rate = audioFormat->sample_rate;
unsigned int channels = audioFormat->channels;
unsigned int sample_rate = audio_format->sample_rate;
unsigned int channels = audio_format->channels;
snd_pcm_uframes_t alsa_buffer_size;
snd_pcm_uframes_t alsa_period_size;
int err;
......@@ -190,16 +202,16 @@ static bool alsa_openDevice(void *data, struct audio_format *audioFormat)
unsigned int period_time, period_time_ro;
unsigned int buffer_time;
mixer_open(&ad->mixer);
mixer_open(ad->mixer);
if ((bitformat = get_bitformat(audioFormat)) == SND_PCM_FORMAT_UNKNOWN)
if ((bitformat = get_bitformat(audio_format)) == SND_PCM_FORMAT_UNKNOWN)
g_warning("ALSA device \"%s\" doesn't support %u bit audio\n",
alsa_device(ad), audioFormat->bits);
alsa_device(ad), audio_format->bits);
err = snd_pcm_open(&ad->pcmHandle, alsa_device(ad),
err = snd_pcm_open(&ad->pcm, alsa_device(ad),
SND_PCM_STREAM_PLAYBACK, ad->mode);
if (err < 0) {
ad->pcmHandle = NULL;
ad->pcm = NULL;
goto error;
}
......@@ -209,72 +221,72 @@ configure_hw:
snd_pcm_hw_params_alloca(&hwparams);
cmd = "snd_pcm_hw_params_any";
err = snd_pcm_hw_params_any(ad->pcmHandle, hwparams);
err = snd_pcm_hw_params_any(ad->pcm, hwparams);
if (err < 0)
goto error;
if (ad->useMmap) {
err = snd_pcm_hw_params_set_access(ad->pcmHandle, hwparams,
if (ad->use_mmap) {
err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
SND_PCM_ACCESS_MMAP_INTERLEAVED);
if (err < 0) {
g_warning("Cannot set mmap'ed mode on ALSA device \"%s\": %s\n",
alsa_device(ad), snd_strerror(-err));
g_warning("Falling back to direct write mode\n");
ad->useMmap = 0;
ad->use_mmap = false;
} else
ad->writei = snd_pcm_mmap_writei;
}
if (!ad->useMmap) {
if (!ad->use_mmap) {
cmd = "snd_pcm_hw_params_set_access";
err = snd_pcm_hw_params_set_access(ad->pcmHandle, hwparams,
err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0)
goto error;
ad->writei = snd_pcm_writei;
}
err = snd_pcm_hw_params_set_format(ad->pcmHandle, hwparams, bitformat);
if (err == -EINVAL && audioFormat->bits != 16) {
err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, bitformat);
if (err == -EINVAL && audio_format->bits != 16) {
/* fall back to 16 bit, let pcm_convert.c do the conversion */
err = snd_pcm_hw_params_set_format(ad->pcmHandle, hwparams,
err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
SND_PCM_FORMAT_S16);
if (err == 0) {
g_debug("ALSA device \"%s\": converting %u bit to 16 bit\n",
alsa_device(ad), audioFormat->bits);
audioFormat->bits = 16;
alsa_device(ad), audio_format->bits);
audio_format->bits = 16;
}
}
if (err < 0) {
g_warning("ALSA device \"%s\" does not support %u bit audio: %s\n",
alsa_device(ad), audioFormat->bits, snd_strerror(-err));
alsa_device(ad), audio_format->bits, snd_strerror(-err));
goto fail;
}
err = snd_pcm_hw_params_set_channels_near(ad->pcmHandle, hwparams,
err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
&channels);
if (err < 0) {
g_warning("ALSA device \"%s\" does not support %i channels: %s\n",
alsa_device(ad), (int)audioFormat->channels,
alsa_device(ad), (int)audio_format->channels,
snd_strerror(-err));
goto fail;
}
audioFormat->channels = (int8_t)channels;
audio_format->channels = (int8_t)channels;
err = snd_pcm_hw_params_set_rate_near(ad->pcmHandle, hwparams,
err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
&sample_rate, NULL);
if (err < 0 || sample_rate == 0) {
g_warning("ALSA device \"%s\" does not support %u Hz audio\n",
alsa_device(ad), audioFormat->sample_rate);
alsa_device(ad), audio_format->sample_rate);
goto fail;
}
audioFormat->sample_rate = sample_rate;
audio_format->sample_rate = sample_rate;
if (ad->buffer_time > 0) {
buffer_time = ad->buffer_time;
cmd = "snd_pcm_hw_params_set_buffer_time_near";
err = snd_pcm_hw_params_set_buffer_time_near(ad->pcmHandle, hwparams,
err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
&buffer_time, NULL);
if (err < 0)
goto error;
......@@ -283,14 +295,14 @@ configure_hw:
if (period_time_ro > 0) {
period_time = period_time_ro;
cmd = "snd_pcm_hw_params_set_period_time_near";
err = snd_pcm_hw_params_set_period_time_near(ad->pcmHandle, hwparams,
err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams,
&period_time, NULL);
if (err < 0)
goto error;
}
cmd = "snd_pcm_hw_params";
err = snd_pcm_hw_params(ad->pcmHandle, hwparams);
err = snd_pcm_hw_params(ad->pcm, hwparams);
if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
period_time_ro = period_time_ro >> 1;
goto configure_hw;
......@@ -314,32 +326,32 @@ configure_hw:
snd_pcm_sw_params_alloca(&swparams);
cmd = "snd_pcm_sw_params_current";
err = snd_pcm_sw_params_current(ad->pcmHandle, swparams);
err = snd_pcm_sw_params_current(ad->pcm, swparams);
if (err < 0)
goto error;
cmd = "snd_pcm_sw_params_set_start_threshold";
err = snd_pcm_sw_params_set_start_threshold(ad->pcmHandle, swparams,
err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams,
alsa_buffer_size -
alsa_period_size);
if (err < 0)
goto error;
cmd = "snd_pcm_sw_params_set_avail_min";
err = snd_pcm_sw_params_set_avail_min(ad->pcmHandle, swparams,
err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams,
alsa_period_size);
if (err < 0)
goto error;
cmd = "snd_pcm_sw_params";
err = snd_pcm_sw_params(ad->pcmHandle, swparams);
err = snd_pcm_sw_params(ad->pcm, swparams);
if (err < 0)
goto error;
ad->sampleSize = audio_format_frame_size(audioFormat);
ad->frame_size = audio_format_frame_size(audio_format);
g_debug("ALSA device \"%s\" will be playing %i bit, %u channel audio at %u Hz\n",
alsa_device(ad), audioFormat->bits, channels, sample_rate);
alsa_device(ad), audio_format->bits, channels, sample_rate);
return true;
......@@ -352,13 +364,14 @@ error:
alsa_device(ad), snd_strerror(-err));
}
fail:
if (ad->pcmHandle)
snd_pcm_close(ad->pcmHandle);
ad->pcmHandle = NULL;
if (ad->pcm)
snd_pcm_close(ad->pcm);
ad->pcm = NULL;
return false;
}
static int alsa_errorRecovery(AlsaData * ad, int err)
static int
alsa_recover(struct alsa_data *ad, int err)
{
if (err == -EPIPE) {
g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad));
......@@ -366,23 +379,23 @@ static int alsa_errorRecovery(AlsaData * ad, int err)
g_debug("ALSA device \"%s\" was suspended\n", alsa_device(ad));
}
switch (snd_pcm_state(ad->pcmHandle)) {
switch (snd_pcm_state(ad->pcm)) {
case SND_PCM_STATE_PAUSED:
err = snd_pcm_pause(ad->pcmHandle, /* disable */ 0);
err = snd_pcm_pause(ad->pcm, /* disable */ 0);
break;
case SND_PCM_STATE_SUSPENDED:
err = snd_pcm_resume(ad->pcmHandle);
err = snd_pcm_resume(ad->pcm);
if (err == -EAGAIN)
return 0;
/* fall-through to snd_pcm_prepare: */
case SND_PCM_STATE_SETUP:
case SND_PCM_STATE_XRUN:
err = snd_pcm_prepare(ad->pcmHandle);
err = snd_pcm_prepare(ad->pcm);
break;
case SND_PCM_STATE_DISCONNECTED:
/* so alsa_closeDevice won't try to drain: */
snd_pcm_close(ad->pcmHandle);
ad->pcmHandle = NULL;
snd_pcm_close(ad->pcm);
ad->pcm = NULL;
break;
/* this is no error, so just keep running */
case SND_PCM_STATE_RUNNING:
......@@ -396,52 +409,56 @@ static int alsa_errorRecovery(AlsaData * ad, int err)
return err;
}
static void alsa_dropBufferedAudio(void *data)
static void
alsa_cancel(void *data)
{
AlsaData *ad = data;
struct alsa_data *ad = data;
alsa_errorRecovery(ad, snd_pcm_drop(ad->pcmHandle));
alsa_recover(ad, snd_pcm_drop(ad->pcm));
}
static void alsa_closeDevice(void *data)
static void
alsa_close(void *data)
{
AlsaData *ad = data;
struct alsa_data *ad = data;
if (ad->pcmHandle) {
if (snd_pcm_state(ad->pcmHandle) == SND_PCM_STATE_RUNNING) {
snd_pcm_drain(ad->pcmHandle);
}
snd_pcm_close(ad->pcmHandle);
ad->pcmHandle = NULL;
if (ad->pcm != NULL) {
if (snd_pcm_state(ad->pcm) == SND_PCM_STATE_RUNNING)
snd_pcm_drain(ad->pcm);
snd_pcm_close(ad->pcm);
ad->pcm = NULL;
}
mixer_close(&ad->mixer);
mixer_close(ad->mixer);
}
static bool
alsa_playAudio(void *data, const char *playChunk, size_t size)
alsa_play(void *data, const char *chunk, size_t size)
{
AlsaData *ad = data;
struct alsa_data *ad = data;
int ret;
size /= ad->sampleSize;
size /= ad->frame_size;
while (size > 0) {
ret = ad->writei(ad->pcmHandle, playChunk, size);
ret = ad->writei(ad->pcm, chunk, size);
if (ret == -EAGAIN || ret == -EINTR)
continue;
if (ret < 0) {
if (alsa_errorRecovery(ad, ret) < 0) {
if (alsa_recover(ad, ret) < 0) {
g_warning("closing ALSA device \"%s\" due to write "
"error: %s\n",
alsa_device(ad), snd_strerror(-errno));
return false;
}
continue;
}
playChunk += ret * ad->sampleSize;
chunk += ret * ad->frame_size;
size -= ret;
}
......@@ -451,18 +468,18 @@ alsa_playAudio(void *data, const char *playChunk, size_t size)
static bool
alsa_control(void *data, int cmd, void *arg)
{
AlsaData *ad = data;
return mixer_control(&ad->mixer, cmd, arg);
struct alsa_data *ad = data;
return mixer_control(ad->mixer, cmd, arg);
}
const struct audio_output_plugin alsaPlugin = {
.name = "alsa",
.test_default_device = alsa_testDefault,
.init = alsa_initDriver,
.finish = alsa_finishDriver,
.open = alsa_openDevice,
.play = alsa_playAudio,
.cancel = alsa_dropBufferedAudio,
.close = alsa_closeDevice,
.test_default_device = alsa_test_default_device,
.init = alsa_init,
.finish = alsa_finish,
.open = alsa_open,
.play = alsa_play,
.cancel = alsa_cancel,
.close = alsa_close,
.control = alsa_control
};
......@@ -27,7 +27,7 @@
static int driverInitCount;
typedef struct _AoData {
int writeSize;
size_t writeSize;
int driverId;
ao_option *options;
ao_device *device;
......@@ -77,35 +77,25 @@ static void audioOutputAo_error(const char *msg)
static void *
audioOutputAo_initDriver(struct audio_output *ao,
G_GNUC_UNUSED const struct audio_format *audio_format,
struct config_param *param)
const struct config_param *param)
{
ao_info *ai;
char *test;
AoData *ad = newAoData();
struct block_param *blockParam;
const char *value;
if ((blockParam = getBlockParam(param, "write_size"))) {
ad->writeSize = strtol(blockParam->value, &test, 10);
if (*test != '\0') {
g_error("\"%s\" is not a valid write size at line %i\n",
blockParam->value, blockParam->line);
}
} else
ad->writeSize = 1024;
ad->writeSize = config_get_block_unsigned(param, "write_size", 1024);
if (driverInitCount == 0) {
ao_initialize();
}
driverInitCount++;
blockParam = getBlockParam(param, "driver");
if (!blockParam || 0 == strcmp(blockParam->value, "default")) {
value = config_get_block_string(param, "driver", "default");
if (0 == strcmp(value, "default")) {
ad->driverId = ao_default_driver_id();
} else if ((ad->driverId = ao_driver_id(blockParam->value)) < 0) {
} else if ((ad->driverId = ao_driver_id(value)) < 0)
g_error("\"%s\" is not a valid ao driver at line %i\n",
blockParam->value, blockParam->line);
}
value, param->line);
if ((ai = ao_driver_info(ad->driverId)) == NULL) {
g_error("problems getting driver info for device defined at line %i\n"
......@@ -115,9 +105,9 @@ audioOutputAo_initDriver(struct audio_output *ao,
g_debug("using ao driver \"%s\" for \"%s\"\n", ai->short_name,
audio_output_get_name(ao));
blockParam = getBlockParam(param, "options");
if (blockParam) {
gchar **options = g_strsplit(blockParam->value, ";", 0);
value = config_get_block_string(param, "options", NULL);
if (value != NULL) {
gchar **options = g_strsplit(value, ";", 0);
for (unsigned i = 0; options[i] != NULL; ++i) {
gchar **key_value = g_strsplit(options[i], "=", 2);
......@@ -141,7 +131,7 @@ audioOutputAo_initDriver(struct audio_output *ao,
static void freeAoData(AoData * ad)
{
ao_free_options(ad->options);
free(ad);
g_free(ad);
}
static void audioOutputAo_finishDriver(void *data)
......@@ -228,8 +218,8 @@ audioOutputAo_play(void *data, const char *playChunk, size_t size)
return false;
while (size > 0) {
chunk_size = (size_t)ad->writeSize > size
? size : (size_t)ad->writeSize;
chunk_size = ad->writeSize > size
? size : ad->writeSize;
if (ao_play_deconst(ad->device, playChunk, chunk_size) == 0) {
audioOutputAo_error("Closing libao device due to play error");
......
......@@ -161,22 +161,21 @@ static bool openFifo(FifoData *fd)
static void *fifo_initDriver(G_GNUC_UNUSED struct audio_output *ao,
G_GNUC_UNUSED const struct audio_format *audio_format,
struct config_param *param)
const struct config_param *param)
{
FifoData *fd;
struct block_param *blockParam;
char *path;
char *value, *path;
blockParam = getBlockParam(param, "path");
if (!blockParam) {
value = config_dup_block_string(param, "path", NULL);
if (value == NULL)
g_error("No \"path\" parameter specified for fifo output "
"defined at line %i", param->line);
}
path = parsePath(blockParam->value);
path = parsePath(value);
g_free(value);
if (!path) {
g_error("Could not parse \"path\" parameter for fifo output "
"at line %i", blockParam->line);
"at line %i", param->line);
}
fd = newFifoData();
......
......@@ -36,39 +36,32 @@
static const size_t sample_size = sizeof(jack_default_audio_sample_t);
static const char *const port_names[2] = {
"left", "right",
};
struct jack_data {
struct audio_output *ao;
/* configuration */
char *name;
char *output_ports[2];
int ringbuffer_size;
/* for srate() only */
struct audio_format *audio_format;
/* the current audio format */
struct audio_format audio_format;
/* jack library stuff */
jack_port_t *ports[2];
jack_client_t *client;
jack_ringbuffer_t *ringbuffer[2];
int bps;
int shutdown;
bool shutdown;
};
static const char *
mpd_jack_name(const struct jack_data *jd)
{
return jd->name != NULL ? jd->name : "mpd";
}
static struct jack_data *
mpd_jack_new(void)
{
struct jack_data *ret = g_new(struct jack_data, 1);
ret->ringbuffer_size = 32768;
return ret;
return audio_output_get_name(jd->ao);
}
static void
......@@ -82,14 +75,11 @@ mpd_jack_client_free(struct jack_data *jd)
jd->client = NULL;
}
if (jd->ringbuffer[0] != NULL) {
jack_ringbuffer_free(jd->ringbuffer[0]);
jd->ringbuffer[0] = NULL;
}
if (jd->ringbuffer[1] != NULL) {
jack_ringbuffer_free(jd->ringbuffer[1]);
jd->ringbuffer[1] = NULL;
for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
if (jd->ringbuffer[i] != NULL) {
jack_ringbuffer_free(jd->ringbuffer[i]);
jd->ringbuffer[i] = NULL;
}
}
}
......@@ -98,14 +88,10 @@ mpd_jack_free(struct jack_data *jd)
{
assert(jd != NULL);
mpd_jack_client_free(jd);
g_free(jd->name);
for (unsigned i = 0; i < G_N_ELEMENTS(jd->output_ports); ++i)
g_free(jd->output_ports[i]);
free(jd);
g_free(jd);
}
static void
......@@ -116,17 +102,6 @@ mpd_jack_finish(void *data)
}
static int
mpd_jack_srate(G_GNUC_UNUSED jack_nframes_t rate, void *data)
{
struct jack_data *jd = (struct jack_data *)data;
struct audio_format *audioFormat = jd->audio_format;
audioFormat->sample_rate = (int)jack_get_sample_rate(jd->client);
return 0;
}
static int
mpd_jack_process(jack_nframes_t nframes, void *arg)
{
struct jack_data *jd = (struct jack_data *) arg;
......@@ -136,7 +111,7 @@ mpd_jack_process(jack_nframes_t nframes, void *arg)
if (nframes <= 0)
return 0;
for (unsigned i = 0; i < 2; ++i) {
for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
available = jack_ringbuffer_read_space(jd->ringbuffer[i]);
assert(available % sample_size == 0);
available /= sample_size;
......@@ -159,7 +134,7 @@ static void
mpd_jack_shutdown(void *arg)
{
struct jack_data *jd = (struct jack_data *) arg;
jd->shutdown = 1;
jd->shutdown = true;
}
static void
......@@ -171,10 +146,6 @@ set_audioformat(struct jack_data *jd, struct audio_format *audio_format)
if (audio_format->bits != 16 && audio_format->bits != 24)
audio_format->bits = 24;
jd->bps = audio_format->channels
* sizeof(jack_default_audio_sample_t)
* audio_format->sample_rate;
}
static void
......@@ -183,68 +154,52 @@ mpd_jack_error(const char *msg)
g_warning("%s", msg);
}
#ifdef HAVE_JACK_SET_INFO_FUNCTION
static void
mpd_jack_info(const char *msg)
{
g_message("%s", msg);
}
#endif
static void *
mpd_jack_init(struct audio_output *ao,
G_GNUC_UNUSED const struct audio_format *audio_format,
struct config_param *param)
const struct config_param *param)
{
struct jack_data *jd;
struct block_param *bp;
char *endptr;
int val;
char *cp = NULL;
const char *value;
jd = mpd_jack_new();
jd = g_new(struct jack_data, 1);
jd->ao = ao;
g_debug("mpd_jack_init (pid=%d)", getpid());
if (param == NULL)
return jd;
if ( (bp = getBlockParam(param, "ports")) ) {
g_debug("output_ports=%s", bp->value);
if (!(cp = strchr(bp->value, ',')))
g_error("expected comma and a second value for '%s' "
"at line %d: %s",
bp->name, bp->line, bp->value);
value = config_get_block_string(param, "ports", NULL);
if (value != NULL) {
char **ports = g_strsplit(value, ",", 0);
*cp = '\0';
jd->output_ports[0] = g_strdup(bp->value);
*cp++ = ',';
if (ports[0] == NULL || ports[1] == NULL || ports[2] != NULL)
g_error("two port names expected in line %d",
param->line);
if (!*cp)
g_error("expected a second value for '%s' at line %d: %s",
bp->name, bp->line, bp->value);
jd->output_ports[0] = ports[0];
jd->output_ports[1] = ports[1];
jd->output_ports[1] = g_strdup(cp);
if (strchr(cp,','))
g_error("Only %d values are supported for '%s' "
"at line %d",
(int)G_N_ELEMENTS(jd->output_ports),
bp->name, bp->line);
g_free(ports);
} else {
jd->output_ports[0] = NULL;
jd->output_ports[1] = NULL;
}
if ( (bp = getBlockParam(param, "ringbuffer_size")) ) {
errno = 0;
val = strtol(bp->value, &endptr, 10);
jd->ringbuffer_size =
config_get_block_unsigned(param, "ringbuffer_size", 32768);
if ( errno == 0 && endptr != bp->value) {
jd->ringbuffer_size = val < 32768 ? 32768 : val;
g_debug("ringbuffer_size=%d", jd->ringbuffer_size);
} else {
g_error("%s is not a number; ringbuf_size=%d",
bp->value, jd->ringbuffer_size);
}
}
jack_set_error_function(mpd_jack_error);
if ( (bp = getBlockParam(param, "name"))
&& (strcmp(bp->value, "mpd") != 0) ) {
jd->name = g_strdup(bp->value);
g_debug("name=%s", jd->name);
} else
jd->name = NULL;
#ifdef HAVE_JACK_SET_INFO_FUNCTION
jack_set_info_function(mpd_jack_info);
#endif
return jd;
}
......@@ -255,87 +210,84 @@ mpd_jack_test_default_device(void)
return true;
}
static int
mpd_jack_connect(struct jack_data *jd, struct audio_format *audio_format)
static bool
mpd_jack_connect(struct jack_data *jd)
{
const char **jports;
char *port_name;
const char *output_ports[2], **jports;
jd->audio_format = audio_format;
for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i)
jd->ringbuffer[i] =
jack_ringbuffer_create(jd->ringbuffer_size);
jd->shutdown = false;
if ((jd->client = jack_client_new(mpd_jack_name(jd))) == NULL) {
g_warning("jack server not running?");
return -1;
return false;
}
jack_set_error_function(mpd_jack_error);
jack_set_process_callback(jd->client, mpd_jack_process, jd);
jack_set_sample_rate_callback(jd->client, mpd_jack_srate, jd);
jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) {
jd->ports[i] = jack_port_register(jd->client, port_names[i],
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
if (jd->ports[i] == NULL) {
g_warning("Cannot register %s output port.",
port_names[i]);
return false;
}
}
if ( jack_activate(jd->client) ) {
g_warning("cannot activate client");
return -1;
return false;
}
jd->ports[0] = jack_port_register(jd->client, "left",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
if ( !jd->ports[0] ) {
g_warning("Cannot register left output port.");
return -1;
}
if (jd->output_ports[1] == NULL) {
/* no output ports were configured - ask libjack for
defaults */
jports = jack_get_ports(jd->client, NULL, NULL,
JackPortIsPhysical | JackPortIsInput);
if (jports == NULL) {
g_warning("no ports found");
return false;
}
jd->ports[1] = jack_port_register(jd->client, "right",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
if ( !jd->ports[1] ) {
g_warning("Cannot register right output port.");
return -1;
}
output_ports[0] = jports[0];
output_ports[1] = jports[1] != NULL ? jports[1] : jports[0];
/* hay que buscar que hay */
if (!jd->output_ports[1] &&
(jports = jack_get_ports(jd->client, NULL, NULL,
JackPortIsPhysical | JackPortIsInput))) {
jd->output_ports[0] = g_strdup(jports[0]);
jd->output_ports[1] = g_strdup(jports[1] != NULL
? jports[1] : jports[0]);
g_debug("output_ports: %s %s",
jd->output_ports[0], jd->output_ports[1]);
free(jports);
}
g_debug("output_ports: %s %s", jports[0], jports[1]);
} else {
/* use the configured output ports */
if ( jd->output_ports[1] ) {
const char *name = mpd_jack_name(jd);
output_ports[0] = jd->output_ports[0];
output_ports[1] = jd->output_ports[1];
jd->ringbuffer[0] = jack_ringbuffer_create(jd->ringbuffer_size);
jd->ringbuffer[1] = jack_ringbuffer_create(jd->ringbuffer_size);
memset(jd->ringbuffer[0]->buf, 0, jd->ringbuffer[0]->size);
memset(jd->ringbuffer[1]->buf, 0, jd->ringbuffer[1]->size);
jports = NULL;
}
port_name = g_malloc(sizeof(port_name[0]) * (7 + strlen(name)));
for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) {
int ret;
sprintf(port_name, "%s:left", name);
if ( (jack_connect(jd->client, port_name,
jd->output_ports[0])) != 0 ) {
g_warning("%s is not a valid Jack Client / Port",
jd->output_ports[0]);
free(port_name);
return -1;
}
sprintf(port_name, "%s:right", name);
if ( (jack_connect(jd->client, port_name,
jd->output_ports[1])) != 0 ) {
ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
output_ports[i]);
if (ret != 0) {
g_warning("%s is not a valid Jack Client / Port",
jd->output_ports[1]);
free(port_name);
return -1;
output_ports[i]);
if (jports != NULL)
free(jports);
return false;
}
free(port_name);
}
return 1;
if (jports != NULL)
free(jports);
return true;
}
static bool
......@@ -345,12 +297,13 @@ mpd_jack_open(void *data, struct audio_format *audio_format)
assert(jd != NULL);
if (jd->client == NULL && mpd_jack_connect(jd, audio_format) < 0) {
if (!mpd_jack_connect(jd)) {
mpd_jack_client_free(jd);
return false;
}
set_audioformat(jd, audio_format);
jd->audio_format = *audio_format;
return true;
}
......@@ -358,7 +311,9 @@ mpd_jack_open(void *data, struct audio_format *audio_format)
static void
mpd_jack_close(G_GNUC_UNUSED void *data)
{
/*mpd_jack_finish(audioOutput);*/
struct jack_data *jd = data;
mpd_jack_client_free(jd);
}
static void
......@@ -416,7 +371,7 @@ static void
mpd_jack_write_samples(struct jack_data *jd, const void *src,
unsigned num_samples)
{
switch (jd->audio_format->bits) {
switch (jd->audio_format.bits) {
case 16:
mpd_jack_write_samples_16(jd, (const int16_t*)src,
num_samples);
......@@ -436,14 +391,12 @@ static bool
mpd_jack_play(void *data, const char *buff, size_t size)
{
struct jack_data *jd = data;
const size_t frame_size = audio_format_frame_size(jd->audio_format);
const size_t frame_size = audio_format_frame_size(&jd->audio_format);
size_t space, space1;
if (jd->shutdown) {
g_warning("Refusing to play, because there is no client thread.");
mpd_jack_client_free(jd);
audio_output_closed(jd->ao);
return true;
return false;
}
assert(size % frame_size == 0);
......@@ -467,7 +420,7 @@ mpd_jack_play(void *data, const char *buff, size_t size)
} else {
/* XXX do something more intelligent to
synchronize */
my_usleep(10000);
my_usleep(1000);
}
}
......
......@@ -112,7 +112,7 @@ static bool mvp_testDefault(void)
static void *mvp_initDriver(G_GNUC_UNUSED struct audio_output *audio_output,
G_GNUC_UNUSED const struct audio_format *audio_format,
G_GNUC_UNUSED struct config_param *param)
G_GNUC_UNUSED const struct config_param *param)
{
MvpData *md = g_new(MvpData, 1);
md->audio_output = audio_output;
......@@ -124,7 +124,7 @@ static void *mvp_initDriver(G_GNUC_UNUSED struct audio_output *audio_output,
static void mvp_finishDriver(void *data)
{
MvpData *md = data;
free(md);
g_free(md);
}
static int mvp_setPcmParams(MvpData * md, unsigned long rate, int channels,
......@@ -245,7 +245,6 @@ static void mvp_dropBufferedAudio(void *data)
ioctl(md->fd, MVP_SET_AUD_RESET, 0x11);
close(md->fd);
md->fd = -1;
audio_output_closed(md->audio_output);
}
}
......
......@@ -21,30 +21,50 @@
#include <glib.h>
#include <assert.h>
struct null_data {
bool sync;
Timer *timer;
};
static void *
null_initDriver(G_GNUC_UNUSED struct audio_output *audioOutput,
G_GNUC_UNUSED const struct audio_format *audio_format,
G_GNUC_UNUSED struct config_param *param)
null_init(G_GNUC_UNUSED struct audio_output *audio_output,
G_GNUC_UNUSED const struct audio_format *audio_format,
G_GNUC_UNUSED const struct config_param *param)
{
struct null_data *nd = g_new(struct null_data, 1);
nd->sync = config_get_block_bool(param, "sync", true);
nd->timer = NULL;
return nd;
}
static void
null_finish(void *data)
{
struct null_data *nd = data;
assert(nd->timer == NULL);
g_free(nd);
}
static bool
null_openDevice(void *data, struct audio_format *audio_format)
null_open(void *data, struct audio_format *audio_format)
{
struct null_data *nd = data;
nd->timer = timer_new(audio_format);
if (nd->sync)
nd->timer = timer_new(audio_format);
return true;
}
static void null_closeDevice(void *data)
static void
null_close(void *data)
{
struct null_data *nd = data;
......@@ -55,11 +75,14 @@ static void null_closeDevice(void *data)
}
static bool
null_playAudio(void *data, G_GNUC_UNUSED const char *playChunk, size_t size)
null_play(void *data, G_GNUC_UNUSED const char *chunk, size_t size)
{
struct null_data *nd = data;
Timer *timer = nd->timer;
if (!nd->sync)
return true;
if (!timer->started)
timer_start(timer);
else
......@@ -70,18 +93,23 @@ null_playAudio(void *data, G_GNUC_UNUSED const char *playChunk, size_t size)
return true;
}
static void null_dropBufferedAudio(void *data)
static void
null_cancel(void *data)
{
struct null_data *nd = data;
if (!nd->sync)
return;
timer_reset(nd->timer);
}
const struct audio_output_plugin nullPlugin = {
const struct audio_output_plugin null_output_plugin = {
.name = "null",
.init = null_initDriver,
.open = null_openDevice,
.play = null_playAudio,
.cancel = null_dropBufferedAudio,
.close = null_closeDevice,
.init = null_init,
.finish = null_finish,
.open = null_open,
.close = null_close,
.play = null_play,
.cancel = null_cancel,
};
......@@ -55,7 +55,9 @@ typedef struct _OssData {
int numSupported[3];
int *unsupported[3];
int numUnsupported[3];
struct mixer mixer;
/** the mixer object associated with this output */
struct mixer *mixer;
} OssData;
enum oss_support {
......@@ -276,8 +278,6 @@ static OssData *newOssData(void)
supportParam(ret, SNDCTL_DSP_CHANNELS, 2);
supportParam(ret, SNDCTL_DSP_SAMPLESIZE, 16);
mixer_init( &ret->mixer, &oss_mixer);
return ret;
}
......@@ -290,9 +290,9 @@ static void freeOssData(OssData * od)
g_free(od->unsupported[OSS_CHANNELS]);
g_free(od->unsupported[OSS_BITS]);
mixer_finish(&od->mixer);
mixer_free(od->mixer);
free(od);
g_free(od);
}
#define OSS_STAT_NO_ERROR 0
......@@ -344,7 +344,7 @@ static bool oss_testDefault(void)
return false;
}
static void *oss_open_default(struct config_param *param)
static void *oss_open_default(const struct config_param *param)
{
int i;
int err[G_N_ELEMENTS(default_devices)];
......@@ -355,7 +355,7 @@ static void *oss_open_default(struct config_param *param)
if (ret[i] == 0) {
OssData *od = newOssData();
od->device = default_devices[i];
mixer_configure(&od->mixer, param);
od->mixer = mixer_new(&oss_mixer, param);
return od;
}
}
......@@ -390,17 +390,16 @@ static void *oss_open_default(struct config_param *param)
static void *
oss_initDriver(G_GNUC_UNUSED struct audio_output *audioOutput,
G_GNUC_UNUSED const struct audio_format *audio_format,
struct config_param *param)
const struct config_param *param)
{
if (param) {
struct block_param *bp = getBlockParam(param, "device");
if (bp) {
OssData *od = newOssData();
od->device = bp->value;
mixer_configure(&od->mixer, param);
return od;
}
const char *device = config_get_block_string(param, "device", NULL);
if (device != NULL) {
OssData *od = newOssData();
od->device = device;
od->mixer = mixer_new(&oss_mixer, param);
return od;
}
return oss_open_default(param);
}
......@@ -523,7 +522,7 @@ oss_openDevice(void *data, struct audio_format *audioFormat)
od->audio_format.bits, od->audio_format.channels,
od->audio_format.sample_rate);
mixer_open(&od->mixer);
mixer_open(od->mixer);
return ret;
}
......@@ -533,7 +532,7 @@ static void oss_closeDevice(void *data)
OssData *od = data;
oss_close(od);
mixer_close(&od->mixer);
mixer_close(od->mixer);
}
static void oss_dropBufferedAudio(void *data)
......@@ -576,7 +575,7 @@ static bool
oss_control(void *data, int cmd, void *arg)
{
OssData *od = data;
return mixer_control(&od->mixer, cmd, arg);
return mixer_control(od->mixer, cmd, arg);
}
const struct audio_output_plugin ossPlugin = {
......
......@@ -83,18 +83,17 @@ static bool osx_testDefault(void)
static void *
osx_initDriver(G_GNUC_UNUSED struct audio_output *audioOutput,
G_GNUC_UNUSED const struct audio_format *audio_format,
G_GNUC_UNUSED struct config_param *param)
G_GNUC_UNUSED const struct config_param *param)
{
return newOsxData();
}
static void freeOsxData(OsxData * od)
{
if (od->buffer)
free(od->buffer);
g_free(od->buffer);
g_mutex_free(od->mutex);
g_cond_free(od->condition);
free(od);
g_free(od);
}
static void osx_finishDriver(void *data)
......@@ -146,41 +145,7 @@ osx_render(void *vdata,
size_t bytes;
int curpos = 0;
/*DEBUG("osx_render: enter : %i\n", (int)bufferList->mNumberBuffers);
DEBUG("osx_render: ioActionFlags: %p\n", ioActionFlags);
if(ioActionFlags) {
if(*ioActionFlags & kAudioUnitRenderAction_PreRender) {
DEBUG("prerender\n");
}
if(*ioActionFlags & kAudioUnitRenderAction_PostRender) {
DEBUG("post render\n");
}
if(*ioActionFlags & kAudioUnitRenderAction_OutputIsSilence) {
DEBUG("post render\n");
}
if(*ioActionFlags & kAudioOfflineUnitRenderAction_Preflight) {
DEBUG("prefilight\n");
}
if(*ioActionFlags & kAudioOfflineUnitRenderAction_Render) {
DEBUG("render\n");
}
if(*ioActionFlags & kAudioOfflineUnitRenderAction_Complete) {
DEBUG("complete\n");
}
} */
/* while(bufferSize) {
DEBUG("osx_render: lock\n"); */
g_mutex_lock(od->mutex);
/*
DEBUG("%i:%i\n", bufferSize, od->len);
while(od->go && od->len < bufferSize &&
od->len < od->bufferSize)
{
DEBUG("osx_render: wait\n");
g_cond_wait(od->condition, od->mutex);
}
*/
bytesToCopy = MIN(od->len, bufferSize);
bufferSize = bytesToCopy;
......@@ -199,10 +164,9 @@ osx_render(void *vdata,
if (od->pos >= od->bufferSize)
od->pos = 0;
/* DEBUG("osx_render: unlock\n"); */
g_mutex_unlock(od->mutex);
g_cond_signal(od->condition);
/* } */
buffer->mDataByteSize = bufferSize;
......@@ -210,7 +174,6 @@ osx_render(void *vdata,
my_usleep(1000);
}
/* DEBUG("osx_render: leave\n"); */
return 0;
}
......@@ -302,8 +265,6 @@ osx_play(void *data, const char *playChunk, size_t size)
size_t bytes;
size_t curpos;
/* DEBUG("osx_play: enter\n"); */
if (!od->started) {
int err;
od->started = 1;
......@@ -317,7 +278,6 @@ osx_play(void *data, const char *playChunk, size_t size)
g_mutex_lock(od->mutex);
while (size) {
/* DEBUG("osx_play: lock\n"); */
curpos = od->pos + od->len;
if (curpos >= od->bufferSize)
curpos -= od->bufferSize;
......@@ -325,7 +285,6 @@ osx_play(void *data, const char *playChunk, size_t size)
bytesToCopy = MIN(od->bufferSize, size);
while (od->len > od->bufferSize - bytesToCopy) {
/* DEBUG("osx_play: wait\n"); */
g_cond_wait(od->condition, od->mutex);
}
......@@ -344,10 +303,9 @@ osx_play(void *data, const char *playChunk, size_t size)
playChunk += bytesToCopy;
}
/* DEBUG("osx_play: unlock\n"); */
g_mutex_unlock(od->mutex);
/* DEBUG("osx_play: leave\n"); */
return true;
}
......
......@@ -55,21 +55,16 @@ static void pulse_free_data(struct pulse_data *pd)
static void *
pulse_init(struct audio_output *ao,
G_GNUC_UNUSED const struct audio_format *audio_format,
struct config_param *param)
const struct config_param *param)
{
struct block_param *server = NULL;
struct block_param *sink = NULL;
struct pulse_data *pd;
if (param) {
server = getBlockParam(param, "server");
sink = getBlockParam(param, "sink");
}
pd = pulse_new_data();
pd->ao = ao;
pd->server = server != NULL ? g_strdup(server->value) : NULL;
pd->sink = sink != NULL ? g_strdup(sink->value) : NULL;
pd->server = param != NULL
? config_dup_block_string(param, "server", NULL) : NULL;
pd->sink = param != NULL
? config_dup_block_string(param, "sink", NULL) : NULL;
return pd;
}
......
......@@ -19,6 +19,8 @@
#include "shout_plugin.h"
#include <lame/lame.h>
#include <assert.h>
#include <stdlib.h>
struct lame_data {
......@@ -41,10 +43,13 @@ static int shout_mp3_encoder_clear_encoder(struct shout_data *sd)
struct shout_buffer *buf = &sd->buf;
int ret;
if ((ret = lame_encode_flush(ld->gfp, buf->data + buf->len,
buf->len)) < 0)
ret = lame_encode_flush(ld->gfp, buf->data, sizeof(buf->data));
if (ret < 0)
g_warning("error flushing lame buffers\n");
lame_close(ld->gfp);
ld->gfp = NULL;
return (ret > 0);
}
......@@ -52,8 +57,9 @@ static void shout_mp3_encoder_finish(struct shout_data *sd)
{
struct lame_data *ld = (struct lame_data *)sd->encoder_data;
lame_close(ld->gfp);
ld->gfp = NULL;
assert(ld->gfp == NULL);
g_free(ld);
}
static int shout_mp3_encoder_init_encoder(struct shout_data *sd)
......@@ -105,7 +111,7 @@ static int shout_mp3_encoder_send_metadata(struct shout_data *sd,
char artist[size];
char title[size];
int i;
struct tag *tag = sd->tag;
const struct tag *tag = sd->tag;
strncpy(artist, "", size);
strncpy(title, "", size);
......@@ -128,19 +134,18 @@ static int shout_mp3_encoder_send_metadata(struct shout_data *sd,
return 1;
}
static int shout_mp3_encoder_encode(struct shout_data *sd,
const char * chunk, size_t len)
static int
shout_mp3_encoder_encode(struct shout_data *sd, const void *chunk, size_t len)
{
const int16_t *src = (const int16_t*)chunk;
unsigned int i;
float *left, *right;
struct shout_buffer *buf = &(sd->buf);
unsigned int samples;
int bytes = audio_format_sample_size(&sd->audio_format);
struct lame_data *ld = (struct lame_data *)sd->encoder_data;
int bytes_out;
samples = len / (bytes * sd->audio_format.channels);
samples = len / audio_format_sample_size(&sd->audio_format);
left = g_malloc(sizeof(left[0]) * samples);
if (sd->audio_format.channels > 1)
right = g_malloc(sizeof(left[0]) * samples);
......@@ -158,7 +163,7 @@ static int shout_mp3_encoder_encode(struct shout_data *sd,
bytes_out = lame_encode_buffer_float(ld->gfp, left, right,
samples, buf->data,
sizeof(buf->data) - buf->len);
sizeof(buf->data));
g_free(left);
if (right != left)
......
......@@ -75,23 +75,14 @@ static void copy_tag_to_vorbis_comment(struct shout_data *sd)
static int copy_ogg_buffer_to_shout_buffer(ogg_page *og,
struct shout_buffer *buf)
{
if (sizeof(buf->data) - buf->len >= (size_t)og->header_len) {
memcpy(buf->data + buf->len,
og->header, og->header_len);
buf->len += og->header_len;
} else {
if ((size_t)og->header_len + (size_t)og->body_len > sizeof(buf->data)) {
g_warning("%s: not enough buffer space!\n", __func__);
return -1;
}
if (sizeof(buf->data) - buf->len >= (size_t)og->body_len) {
memcpy(buf->data + buf->len,
og->body, og->body_len);
buf->len += og->body_len;
} else {
g_warning("%s: not enough buffer space!\n", __func__);
return -1;
}
memcpy(buf->data, og->header, og->header_len);
memcpy(buf->data + og->header_len, og->body, og->body_len);
buf->len = og->header_len + og->body_len;
return 0;
}
......@@ -247,28 +238,30 @@ static int shout_ogg_encoder_send_metadata(struct shout_data *sd,
return 0;
}
static int shout_ogg_encoder_encode(struct shout_data *sd,
const char *chunk, size_t size)
static void
pcm16_to_ogg_buffer(float **dest, const int16_t *src,
unsigned num_samples, unsigned num_channels)
{
for (unsigned i = 0; i < num_samples; i++)
for (unsigned j = 0; j < num_channels; j++)
dest[j][i] = *src++ / 32768.0;
}
static int
shout_ogg_encoder_encode(struct shout_data *sd,
const void *chunk, size_t size)
{
struct shout_buffer *buf = &sd->buf;
unsigned int i;
int j;
float **vorbbuf;
unsigned int samples;
int bytes = audio_format_sample_size(&sd->audio_format);
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
samples = size / (bytes * sd->audio_format.channels);
vorbbuf = vorbis_analysis_buffer(&od->vd, samples);
samples = size / audio_format_frame_size(&sd->audio_format);
/* this is for only 16-bit audio */
for (i = 0; i < samples; i++) {
for (j = 0; j < sd->audio_format.channels; j++) {
vorbbuf[j][i] = (*((const int16_t *) chunk)) / 32768.0;
chunk += bytes;
}
}
pcm16_to_ogg_buffer(vorbis_analysis_buffer(&od->vd, samples),
(const int16_t *)chunk,
samples, sd->audio_format.channels);
vorbis_analysis_wrote(&od->vd, samples);
......
......@@ -23,7 +23,6 @@
#include <stdlib.h>
#include <stdio.h>
#define CONN_ATTEMPT_INTERVAL 60
#define DEFAULT_CONN_TIMEOUT 2
static int shout_init_count;
......@@ -56,16 +55,9 @@ static struct shout_data *new_shout_data(void)
ret->shout_conn = shout_new();
ret->shout_meta = shout_metadata_new();
ret->opened = 0;
ret->tag = NULL;
ret->tag_to_send = 0;
ret->bitrate = -1;
ret->quality = -2.0;
ret->timeout = DEFAULT_CONN_TIMEOUT;
ret->conn_attempts = 0;
ret->last_attempt = 0;
ret->timer = NULL;
ret->buf.len = 0;
return ret;
}
......@@ -76,12 +68,8 @@ static void free_shout_data(struct shout_data *sd)
shout_metadata_free(sd->shout_meta);
if (sd->shout_conn)
shout_free(sd->shout_conn);
if (sd->tag)
tag_free(sd->tag);
if (sd->timer)
timer_free(sd->timer);
free(sd);
g_free(sd);
}
#define check_block_param(name) { \
......@@ -94,7 +82,7 @@ static void free_shout_data(struct shout_data *sd)
static void *my_shout_init_driver(struct audio_output *audio_output,
const struct audio_format *audio_format,
struct config_param *param)
const struct config_param *param)
{
struct shout_data *sd;
char *test;
......@@ -106,6 +94,7 @@ static void *my_shout_init_driver(struct audio_output *audio_output,
unsigned protocol;
const char *user;
char *name;
const char *value;
struct block_param *block_param;
int public;
......@@ -140,45 +129,34 @@ static void *my_shout_init_driver(struct audio_output *audio_output,
public = config_get_block_bool(param, "public", false);
block_param = getBlockParam(param, "user");
if (block_param)
user = block_param->value;
else
user = "source";
user = config_get_block_string(param, "user", "source");
block_param = getBlockParam(param, "quality");
if (block_param) {
int line = block_param->line;
sd->quality = strtod(block_param->value, &test);
value = config_get_block_string(param, "quality", NULL);
if (value != NULL) {
sd->quality = strtod(value, &test);
if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) {
g_error("shout quality \"%s\" is not a number in the "
"range -1 to 10, line %i\n", block_param->value,
block_param->line);
"range -1 to 10, line %i",
value, param->line);
}
block_param = getBlockParam(param, "bitrate");
if (block_param) {
g_error("quality (line %i) and bitrate (line %i) are "
"both defined for shout output\n", line,
block_param->line);
if (config_get_block_string(param, "bitrate", NULL) != NULL) {
g_error("quality and bitrate are "
"both defined for shout output (line %i)",
param->line);
}
} else {
block_param = getBlockParam(param, "bitrate");
if (!block_param) {
value = config_get_block_string(param, "bitrate", NULL);
if (value == NULL)
g_error("neither bitrate nor quality defined for shout "
"output at line %i\n", param->line);
}
"output at line %i", param->line);
sd->bitrate = strtol(block_param->value, &test, 10);
sd->bitrate = strtol(value, &test, 10);
if (*test != '\0' || sd->bitrate <= 0) {
g_error("bitrate at line %i should be a positive integer "
"\n", block_param->line);
"\n", param->line);
}
}
......@@ -187,42 +165,29 @@ static void *my_shout_init_driver(struct audio_output *audio_output,
assert(audio_format != NULL);
sd->audio_format = *audio_format;
block_param = getBlockParam(param, "encoding");
if (block_param) {
if (0 == strcmp(block_param->value, "mp3"))
encoding = block_param->value;
else if (0 == strcmp(block_param->value, "ogg"))
encoding = block_param->value;
else
g_error("shout encoding \"%s\" is not \"ogg\" or "
"\"mp3\", line %i\n", block_param->value,
block_param->line);
} else {
encoding = "ogg";
}
encoding = config_get_block_string(param, "encoding", "ogg");
sd->encoder = shout_encoder_plugin_get(encoding);
if (sd->encoder == NULL)
g_error("couldn't find shout encoder plugin \"%s\"\n",
encoding);
block_param = getBlockParam(param, "protocol");
if (block_param) {
if (0 == strcmp(block_param->value, "shoutcast") &&
value = config_get_block_string(param, "protocol", NULL);
if (value != NULL) {
if (0 == strcmp(value, "shoutcast") &&
0 != strcmp(encoding, "mp3"))
g_error("you cannot stream \"%s\" to shoutcast, use mp3\n",
encoding);
else if (0 == strcmp(block_param->value, "shoutcast"))
else if (0 == strcmp(value, "shoutcast"))
protocol = SHOUT_PROTOCOL_ICY;
else if (0 == strcmp(block_param->value, "icecast1"))
else if (0 == strcmp(value, "icecast1"))
protocol = SHOUT_PROTOCOL_XAUDIOCAST;
else if (0 == strcmp(block_param->value, "icecast2"))
else if (0 == strcmp(value, "icecast2"))
protocol = SHOUT_PROTOCOL_HTTP;
else
g_error("shout protocol \"%s\" is not \"shoutcast\" or "
"\"icecast1\"or "
"\"icecast2\", line %i\n", block_param->value,
block_param->line);
"\"icecast2\", line %i\n",
value, param->line);
} else {
protocol = SHOUT_PROTOCOL_HTTP;
}
......@@ -234,7 +199,6 @@ static void *my_shout_init_driver(struct audio_output *audio_output,
shout_set_name(sd->shout_conn, name) != SHOUTERR_SUCCESS ||
shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS ||
shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS ||
shout_set_nonblocking(sd->shout_conn, 1) != SHOUTERR_SUCCESS ||
shout_set_format(sd->shout_conn, sd->encoder->shout_format)
!= SHOUTERR_SUCCESS ||
shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS ||
......@@ -244,24 +208,17 @@ static void *my_shout_init_driver(struct audio_output *audio_output,
}
/* optional paramters */
block_param = getBlockParam(param, "timeout");
if (block_param) {
sd->timeout = (int)strtol(block_param->value, &test, 10);
if (*test != '\0' || sd->timeout <= 0) {
g_error("shout timeout is not a positive integer, "
"line %i\n", block_param->line);
}
}
sd->timeout = config_get_block_unsigned(param, "timeout",
DEFAULT_CONN_TIMEOUT);
block_param = getBlockParam(param, "genre");
if (block_param && shout_set_genre(sd->shout_conn, block_param->value)) {
value = config_get_block_string(param, "genre", NULL);
if (value != NULL && shout_set_genre(sd->shout_conn, value)) {
g_error("error configuring shout defined at line %i: %s\n",
param->line, shout_get_error(sd->shout_conn));
}
block_param = getBlockParam(param, "description");
if (block_param && shout_set_description(sd->shout_conn,
block_param->value)) {
value = config_get_block_string(param, "description", NULL);
if (value != NULL && shout_set_description(sd->shout_conn, value)) {
g_error("error configuring shout defined at line %i: %s\n",
param->line, shout_get_error(sd->shout_conn));
}
......@@ -306,14 +263,12 @@ static int handle_shout_error(struct shout_data *sd, int err)
shout_get_host(sd->shout_conn),
shout_get_port(sd->shout_conn),
shout_get_error(sd->shout_conn));
sd->shout_error = 1;
return -1;
default:
g_warning("shout: connection to %s:%i error: %s\n",
shout_get_host(sd->shout_conn),
shout_get_port(sd->shout_conn),
shout_get_error(sd->shout_conn));
sd->shout_error = 1;
return -1;
}
......@@ -331,33 +286,28 @@ static int write_page(struct shout_data *sd)
err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len);
if (handle_shout_error(sd, err) < 0)
return -1;
sd->buf.len = 0;
return 0;
}
static void close_shout_conn(struct shout_data * sd)
{
if (sd->opened) {
if (sd->encoder->clear_encoder_func(sd))
write_page(sd);
}
sd->buf.len = 0;
if (sd->encoder->clear_encoder_func(sd))
write_page(sd);
if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
g_warning("problem closing connection to shout server: %s\n",
shout_get_error(sd->shout_conn));
}
sd->opened = false;
}
static void my_shout_finish_driver(void *data)
{
struct shout_data *sd = (struct shout_data *)data;
close_shout_conn(sd);
sd->encoder->finish_func(sd);
free_shout_data(sd);
......@@ -369,8 +319,8 @@ static void my_shout_finish_driver(void *data)
static void my_shout_drop_buffered_audio(void *data)
{
G_GNUC_UNUSED
struct shout_data *sd = (struct shout_data *)data;
timer_reset(sd->timer);
/* needs to be implemented for shout */
}
......@@ -380,65 +330,22 @@ static void my_shout_close_device(void *data)
struct shout_data *sd = (struct shout_data *)data;
close_shout_conn(sd);
if (sd->timer) {
timer_free(sd->timer);
sd->timer = NULL;
}
}
static int shout_connect(struct shout_data *sd)
{
time_t t = time(NULL);
int state = shout_get_connected(sd->shout_conn);
/* already connected */
if (state == SHOUTERR_CONNECTED)
return 0;
/* waiting to connect */
if (state == SHOUTERR_BUSY && sd->conn_attempts != 0) {
/* timeout waiting to connect */
if ((t - sd->last_attempt) > sd->timeout) {
g_warning("timeout connecting to shout server %s:%i "
"(attempt %i)\n",
shout_get_host(sd->shout_conn),
shout_get_port(sd->shout_conn),
sd->conn_attempts);
return -1;
}
return 1;
}
/* we're in some funky state, so just reset it to unconnected */
if (state != SHOUTERR_UNCONNECTED)
shout_close(sd->shout_conn);
/* throttle new connection attempts */
if (sd->conn_attempts != 0 &&
(t - sd->last_attempt) <= CONN_ATTEMPT_INTERVAL) {
return -1;
}
/* initiate a new connection */
sd->conn_attempts++;
sd->last_attempt = t;
int state;
state = shout_open(sd->shout_conn);
switch (state) {
case SHOUTERR_SUCCESS:
case SHOUTERR_CONNECTED:
return 0;
case SHOUTERR_BUSY:
return 1;
default:
g_warning("problem opening connection to shout server %s:%i "
"(attempt %i): %s\n",
g_warning("problem opening connection to shout server %s:%i: %s\n",
shout_get_host(sd->shout_conn),
shout_get_port(sd->shout_conn),
sd->conn_attempts, shout_get_error(sd->shout_conn));
shout_get_error(sd->shout_conn));
return -1;
}
}
......@@ -452,6 +359,8 @@ static int open_shout_conn(void *data)
if (status != 0)
return status;
sd->buf.len = 0;
if (sd->encoder->init_encoder_func(sd) < 0) {
shout_close(sd->shout_conn);
return -1;
......@@ -459,73 +368,27 @@ static int open_shout_conn(void *data)
write_page(sd);
sd->shout_error = 0;
sd->opened = true;
sd->tag_to_send = 1;
sd->conn_attempts = 0;
return 0;
}
static bool my_shout_open_device(void *data,
struct audio_format *audio_format)
static bool
my_shout_open_device(void *data,
G_GNUC_UNUSED struct audio_format *audio_format)
{
struct shout_data *sd = (struct shout_data *)data;
if (!sd->opened && open_shout_conn(sd) < 0)
if (open_shout_conn(sd) < 0)
return false;
if (sd->timer)
timer_free(sd->timer);
sd->timer = timer_new(audio_format);
return true;
}
static void send_metadata(struct shout_data * sd)
{
static const int size = 1024;
char song[size];
if (!sd->opened || !sd->tag)
return;
if (sd->encoder->send_metadata_func(sd, song, size)) {
shout_metadata_add(sd->shout_meta, "song", song);
if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
sd->shout_meta)) {
g_warning("error setting shout metadata\n");
return;
}
}
sd->tag_to_send = 0;
}
static bool
my_shout_play(void *data, const char *chunk, size_t size)
{
struct shout_data *sd = (struct shout_data *)data;
int status;
if (!sd->timer->started)
timer_start(sd->timer);
timer_add(sd->timer, size);
if (sd->opened && sd->tag_to_send)
send_metadata(sd);
if (!sd->opened) {
status = open_shout_conn(sd);
if (status < 0) {
return false;
} else if (status > 0) {
timer_sync(sd->timer);
return true;
}
}
sd->buf.len = 0;
if (sd->encoder->encode_func(sd, chunk, size))
return false;
......@@ -536,36 +399,33 @@ my_shout_play(void *data, const char *chunk, size_t size)
return true;
}
static void my_shout_pause(void *data)
static bool
my_shout_pause(void *data)
{
struct shout_data *sd = (struct shout_data *)data;
static const char silence[1020];
int ret;
/* play silence until the player thread sends us a command */
while (sd->opened && !audio_output_is_pending(sd->audio_output)) {
ret = my_shout_play(data, silence, sizeof(silence));
if (ret != 0)
break;
}
return my_shout_play(data, silence, sizeof(silence));
}
static void my_shout_set_tag(void *data,
const struct tag *tag)
{
struct shout_data *sd = (struct shout_data *)data;
char song[1024];
bool ret;
if (sd->tag)
tag_free(sd->tag);
sd->tag = NULL;
sd->tag_to_send = 0;
if (!tag)
return;
sd->buf.len = 0;
sd->tag = tag;
ret = sd->encoder->send_metadata_func(sd, song, sizeof(song));
if (ret) {
shout_metadata_add(sd->shout_meta, "song", song);
if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
sd->shout_meta)) {
g_warning("error setting shout metadata\n");
}
}
sd->tag = tag_dup(tag);
sd->tag_to_send = 1;
write_page(sd);
}
const struct audio_output_plugin shoutPlugin = {
......
......@@ -21,7 +21,6 @@
#include "../output_api.h"
#include "../conf.h"
#include "../timer.h"
#include <shout/shout.h>
#include <glib.h>
......@@ -37,7 +36,7 @@ struct shout_encoder_plugin {
int (*clear_encoder_func)(struct shout_data *sd);
int (*encode_func)(struct shout_data *sd,
const char *chunk, size_t len);
const void *chunk, size_t len);
void (*finish_func)(struct shout_data *sd);
int (*init_func)(struct shout_data *sd);
int (*init_encoder_func) (struct shout_data *sd);
......@@ -61,7 +60,6 @@ struct shout_data {
shout_t *shout_conn;
shout_metadata_t *shout_meta;
int shout_error;
const struct shout_encoder_plugin *encoder;
void *encoder_data;
......@@ -69,16 +67,9 @@ struct shout_data {
float quality;
int bitrate;
bool opened;
struct tag *tag;
int tag_to_send;
const struct tag *tag;
int timeout;
int conn_attempts;
time_t last_attempt;
Timer *timer;
/* the configured audio format */
struct audio_format audio_format;
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "output_all.h"
#include "output_internal.h"
#include "output_control.h"
#include <assert.h>
static struct audio_format input_audio_format;
static struct audio_output *audio_outputs;
static unsigned int num_audio_outputs;
unsigned int audio_output_count(void)
{
return num_audio_outputs;
}
struct audio_output *
audio_output_get(unsigned i)
{
assert(i < num_audio_outputs);
return &audio_outputs[i];
}
struct audio_output *
audio_output_find(const char *name)
{
for (unsigned i = 0; i < num_audio_outputs; ++i) {
struct audio_output *ao = audio_output_get(i);
if (strcmp(ao->name, name) == 0)
return ao;
}
/* name not found */
return NULL;
}
static unsigned
audio_output_config_count(void)
{
unsigned int nr = 0;
const struct config_param *param = NULL;
while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param)))
nr++;
if (!nr)
nr = 1; /* we'll always have at least one device */
return nr;
}
void
audio_output_all_init(void)
{
const struct config_param *param = NULL;
unsigned int i;
notify_init(&audio_output_client_notify);
num_audio_outputs = audio_output_config_count();
audio_outputs = g_new(struct audio_output, num_audio_outputs);
for (i = 0; i < num_audio_outputs; i++)
{
struct audio_output *output = &audio_outputs[i];
unsigned int j;
param = config_get_next_param(CONF_AUDIO_OUTPUT, param);
/* only allow param to be NULL if there just one audioOutput */
assert(param || (num_audio_outputs == 1));
if (!audio_output_init(output, param)) {
if (param)
{
g_error("problems configuring output device "
"defined at line %i\n", param->line);
}
else
{
g_error("No audio_output specified and unable to "
"detect a default audio output device\n");
}
}
/* require output names to be unique: */
for (j = 0; j < i; j++) {
if (!strcmp(output->name, audio_outputs[j].name)) {
g_error("output devices with identical "
"names: %s\n", output->name);
}
}
}
}
void
audio_output_all_finish(void)
{
unsigned int i;
for (i = 0; i < num_audio_outputs; i++) {
audio_output_finish(&audio_outputs[i]);
}
g_free(audio_outputs);
audio_outputs = NULL;
num_audio_outputs = 0;
notify_deinit(&audio_output_client_notify);
}
/**
* Determine if all (active) outputs have finished the current
* command.
*/
static bool
audio_output_all_finished(void)
{
for (unsigned i = 0; i < num_audio_outputs; ++i)
if (audio_output_is_open(&audio_outputs[i]) &&
!audio_output_command_is_finished(&audio_outputs[i]))
return false;
return true;
}
static void audio_output_wait_all(void)
{
while (!audio_output_all_finished())
notify_wait(&audio_output_client_notify);
}
/**
* Resets the "reopen" flag on all audio devices. MPD should
* immediately retry to open the device instead of waiting for the
* timeout when the user wants to start playback.
*/
static void
audio_output_all_reset_reopen(void)
{
for (unsigned i = 0; i < num_audio_outputs; ++i)
audio_outputs[i].reopen_after = 0;
}
static void
audio_output_all_update(void)
{
unsigned int i;
if (!audio_format_defined(&input_audio_format))
return;
for (i = 0; i < num_audio_outputs; ++i)
audio_output_update(&audio_outputs[i], &input_audio_format);
}
bool
audio_output_all_play(const char *buffer, size_t length)
{
bool ret = false;
unsigned int i;
assert(length > 0);
/* no partial frames allowed */
assert((length % audio_format_frame_size(&input_audio_format)) == 0);
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i)
if (audio_output_is_open(&audio_outputs[i]))
audio_output_play(&audio_outputs[i],
buffer, length);
while (true) {
bool finished = true;
for (i = 0; i < num_audio_outputs; ++i) {
struct audio_output *ao = &audio_outputs[i];
if (!audio_output_is_open(ao))
continue;
if (audio_output_command_is_finished(ao))
ret = true;
else {
finished = false;
audio_output_signal(ao);
}
}
if (finished)
break;
notify_wait(&audio_output_client_notify);
};
return ret;
}
bool
audio_output_all_open(const struct audio_format *audio_format)
{
bool ret = false;
unsigned int i;
if (!audio_outputs)
return false;
if (audio_format != NULL)
input_audio_format = *audio_format;
audio_output_all_reset_reopen();
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i) {
if (audio_outputs[i].open)
ret = true;
}
if (!ret)
/* close all devices if there was an error */
audio_output_all_close();
return ret;
}
void
audio_output_all_pause(void)
{
unsigned int i;
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i)
if (audio_output_is_open(&audio_outputs[i]))
audio_output_pause(&audio_outputs[i]);
audio_output_wait_all();
}
void
audio_output_all_cancel(void)
{
unsigned int i;
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i) {
if (audio_output_is_open(&audio_outputs[i]))
audio_output_cancel(&audio_outputs[i]);
}
audio_output_wait_all();
}
void
audio_output_all_close(void)
{
unsigned int i;
for (i = 0; i < num_audio_outputs; ++i)
audio_output_close(&audio_outputs[i]);
}
void
audio_output_all_tag(const struct tag *tag)
{
unsigned int i;
for (i = 0; i < num_audio_outputs; ++i)
if (audio_output_is_open(&audio_outputs[i]))
audio_output_send_tag(&audio_outputs[i], tag);
audio_output_wait_all();
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Functions for dealing with all configured (enabled) audion outputs
* at once.
*
*/
#ifndef OUTPUT_ALL_H
#define OUTPUT_ALL_H
#include <stdbool.h>
#include <stddef.h>
struct audio_format;
struct tag;
/**
* Global initialization: load audio outputs from the configuration
* file and initialize them.
*/
void
audio_output_all_init(void);
/**
* Global finalization: free memory occupied by audio outputs. All
*/
void
audio_output_all_finish(void);
/**
* Returns the total number of audio output devices, including those
* who are disabled right now.
*/
unsigned int audio_output_count(void);
/**
* Returns the "i"th audio output device.
*/
struct audio_output *
audio_output_get(unsigned i);
/**
* Returns the audio output device with the specified name. Returns
* NULL if the name does not exist.
*/
struct audio_output *
audio_output_find(const char *name);
/**
* Opens all audio outputs which are not disabled.
*
* @param audio_format the preferred audio format, or NULL to reuse
* the previous format
* @return true on success, false on failure
*/
bool
audio_output_all_open(const struct audio_format *audio_format);
/**
* Closes all audio outputs.
*/
void
audio_output_all_close(void);
/**
* Play a chunk of audio data.
*
* @return true on success, false if no audio output was able to play
* (all closed then)
*/
bool
audio_output_all_play(const char *data, size_t size);
/**
* Send metadata for the next chunk.
*/
void
audio_output_all_tag(const struct tag *tag);
/**
* Puts all audio outputs into pause mode. Most implementations will
* simply close it then.
*/
void
audio_output_all_pause(void);
/**
* Try to cancel data which may still be in the device's buffers.
*/
void
audio_output_all_cancel(void);
#endif
......@@ -19,21 +19,7 @@
#include "output_api.h"
#include "output_internal.h"
#include <assert.h>
const char *audio_output_get_name(const struct audio_output *ao)
{
return ao->name;
}
void audio_output_closed(struct audio_output *ao)
{
assert(ao->open);
ao->open = false;
}
bool audio_output_is_pending(const struct audio_output *ao)
{
return ao->command != AO_COMMAND_NONE;
}
......@@ -20,15 +20,13 @@
#ifndef MPD_OUTPUT_API_H
#define MPD_OUTPUT_API_H
#include "../config.h"
#include "config.h"
#include "audio_format.h"
#include "tag.h"
#include "conf.h"
#include <stdbool.h>
#define DISABLED_AUDIO_OUTPUT_PLUGIN(plugin) const struct audio_output_plugin plugin;
struct audio_output;
/**
......@@ -60,7 +58,7 @@ struct audio_output_plugin {
*/
void *(*init)(struct audio_output *ao,
const struct audio_format *audio_format,
struct config_param *param);
const struct config_param *param);
/**
* Free resources allocated by this device.
......@@ -86,8 +84,11 @@ struct audio_output_plugin {
* silence during pause, so their clients won't be
* disconnected. Plugins which do not support pausing will
* simply be closed, and have to be reopened when unpaused.
*
* @return false on error (output will be closed then), true
* for continue to pause
*/
void (*pause)(void *data);
bool (*pause)(void *data);
/**
* Try to cancel data which may still be in the device's
......@@ -127,18 +128,10 @@ enum audio_output_command {
enum audio_control_command {
AC_MIXER_GETVOL = 0,
AC_MIXER_SETVOL,
AC_MIXER_CONFIGURE,
};
struct audio_output;
const char *audio_output_get_name(const struct audio_output *ao);
void audio_output_closed(struct audio_output *ao);
/**
* Returns true if there is a command pending.
*/
bool audio_output_is_pending(const struct audio_output *ao);
#endif
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Glue functions for controlling the audio outputs over the MPD
* protocol. These functions perform extra validation on all
* parameters, because they might be from an untrusted source.
*
*/
#include "output_command.h"
#include "output_all.h"
#include "output_internal.h"
#include "idle.h"
bool
audio_output_enable_index(unsigned idx)
{
struct audio_output *ao;
if (idx >= audio_output_count())
return false;
ao = audio_output_get(idx);
ao->reopen_after = 0;
ao->enabled = true;
idle_add(IDLE_OUTPUT);
return true;
}
bool
audio_output_disable_index(unsigned idx)
{
struct audio_output *ao;
if (idx >= audio_output_count())
return false;
ao = audio_output_get(idx);
ao->enabled = false;
idle_add(IDLE_OUTPUT);
return true;
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Glue functions for controlling the audio outputs over the MPD
* protocol. These functions perform extra validation on all
* parameters, because they might be from an untrusted source.
*
*/
#ifndef OUTPUT_COMMAND_H
#define OUTPUT_COMMAND_H
#include <stdbool.h>
/**
* Enables an audio output. Returns false if the specified output
* does not exist.
*/
bool
audio_output_enable_index(unsigned idx);
/**
* Disables an audio output. Returns false if the specified output
* does not exist.
*/
bool
audio_output_disable_index(unsigned idx);
#endif
......@@ -56,22 +56,22 @@ audio_output_open(struct audio_output *audioOutput,
audioOutput->reopen_after = 0;
if (audioOutput->open &&
audio_format_equals(audioFormat, &audioOutput->inAudioFormat)) {
audio_format_equals(audioFormat, &audioOutput->in_audio_format)) {
return true;
}
audioOutput->inAudioFormat = *audioFormat;
audioOutput->in_audio_format = *audioFormat;
if (audio_format_defined(&audioOutput->reqAudioFormat)) {
/* copy reqAudioFormat to outAudioFormat only if the
if (audio_format_defined(&audioOutput->config_audio_format)) {
/* copy config_audio_format to out_audio_format only if the
device is not yet open; if it is already open,
plugin->open() may have modified outAudioFormat,
plugin->open() may have modified out_audio_format,
and the value is already ok */
if (!audioOutput->open)
audioOutput->outAudioFormat =
audioOutput->reqAudioFormat;
audioOutput->out_audio_format =
audioOutput->config_audio_format;
} else {
audioOutput->outAudioFormat = audioOutput->inAudioFormat;
audioOutput->out_audio_format = audioOutput->in_audio_format;
if (audioOutput->open)
audio_output_close(audioOutput);
}
......
......@@ -19,8 +19,6 @@
#ifndef MPD_OUTPUT_CONTROL_H
#define MPD_OUTPUT_CONTROL_H
#include "conf.h"
#include <stddef.h>
#include <stdbool.h>
......@@ -28,9 +26,10 @@ struct audio_output;
struct audio_output_plugin;
struct audio_format;
struct tag;
struct config_param;
int
audio_output_init(struct audio_output *, struct config_param *param);
audio_output_init(struct audio_output *, const struct config_param *param);
bool
audio_output_open(struct audio_output *audioOutput,
......
......@@ -20,7 +20,7 @@
#include "output_api.h"
#include "output_internal.h"
#include "output_list.h"
#include "audio.h"
#include "audio_parser.h"
#include <glib.h>
......@@ -39,7 +39,7 @@
}
int
audio_output_init(struct audio_output *ao, struct config_param *param)
audio_output_init(struct audio_output *ao, const struct config_param *param)
{
const char *name = NULL;
char *format = NULL;
......@@ -91,20 +91,26 @@ audio_output_init(struct audio_output *ao, struct config_param *param)
ao->open = false;
ao->reopen_after = 0;
pcm_convert_init(&ao->convState);
pcm_convert_init(&ao->convert_state);
if (format) {
if (0 != parseAudioConfig(&ao->reqAudioFormat, format)) {
g_error("error parsing format at line %i\n", bp->line);
}
GError *error = NULL;
bool ret;
ret = audio_format_parse(&ao->config_audio_format, format,
&error);
if (!ret)
g_error("error parsing format at line %i: %s",
bp->line, error->message);
} else
audio_format_clear(&ao->reqAudioFormat);
audio_format_clear(&ao->config_audio_format);
ao->thread = NULL;
notify_init(&ao->notify);
ao->command = AO_COMMAND_NONE;
ao->data = plugin->init(ao, format ? &ao->reqAudioFormat : NULL, param);
ao->data = plugin->init(ao, format ? &ao->config_audio_format : NULL,
param);
if (ao->data == NULL)
return 0;
......
......@@ -20,6 +20,7 @@
#ifndef MPD_OUTPUT_INTERNAL_H
#define MPD_OUTPUT_INTERNAL_H
#include "output_api.h"
#include "pcm_convert.h"
#include "notify.h"
......@@ -62,23 +63,23 @@ struct audio_output {
* The audio_format in which audio data is received from the
* player thread (which in turn receives it from the decoder).
*/
struct audio_format inAudioFormat;
struct audio_format in_audio_format;
/**
* The audio_format which is really sent to the device. This
* is basically reqAudioFormat (if configured) or
* inAudioFormat, but may have been modified by
* is basically config_audio_format (if configured) or
* in_audio_format, but may have been modified by
* plugin->open().
*/
struct audio_format outAudioFormat;
struct audio_format out_audio_format;
/**
* The audio_format which was configured. Only set if
* convertAudioFormat is true.
*/
struct audio_format reqAudioFormat;
struct audio_format config_audio_format;
struct pcm_convert_state convState;
struct pcm_convert_state convert_state;
/**
* The thread handle, or NULL if the output thread isn't
......
......@@ -21,7 +21,7 @@
#include "config.h"
extern const struct audio_output_plugin shoutPlugin;
extern const struct audio_output_plugin nullPlugin;
extern const struct audio_output_plugin null_output_plugin;
extern const struct audio_output_plugin fifoPlugin;
extern const struct audio_output_plugin alsaPlugin;
extern const struct audio_output_plugin aoPlugin;
......@@ -35,7 +35,7 @@ const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_SHOUT
&shoutPlugin,
#endif
&nullPlugin,
&null_output_plugin,
#ifdef HAVE_FIFO
&fifoPlugin,
#endif
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Protocol specific code for the audio output library.
*
*/
#include "output_print.h"
#include "output_internal.h"
#include "output_all.h"
#include "client.h"
void
printAudioDevices(struct client *client)
{
unsigned n = audio_output_count();
for (unsigned i = 0; i < n; ++i) {
const struct audio_output *ao = audio_output_get(i);
client_printf(client,
"outputid: %i\n"
"outputname: %s\n"
"outputenabled: %i\n",
i, ao->name, ao->enabled);
}
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Protocol specific code for the audio output library.
*
*/
#ifndef OUTPUT_PRINT_H
#define OUTPUT_PRINT_H
struct client;
void
printAudioDevices(struct client *client);
#endif
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Saving and loading the audio output states to/from the state file.
*
*/
#include "output_state.h"
#include "output_internal.h"
#include "output_all.h"
#include <glib.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#define AUDIO_DEVICE_STATE "audio_device_state:"
void
saveAudioDevicesState(FILE *fp)
{
unsigned n = audio_output_count();
assert(n > 0);
for (unsigned i = 0; i < n; ++i) {
const struct audio_output *ao = audio_output_get(i);
fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n",
ao->enabled, ao->name);
}
}
void
readAudioDevicesState(FILE *fp)
{
char buffer[1024];
while (fgets(buffer, sizeof(buffer), fp)) {
char *c, *name;
struct audio_output *ao;
g_strchomp(buffer);
if (!g_str_has_prefix(buffer, AUDIO_DEVICE_STATE))
continue;
c = strchr(buffer, ':');
if (!c || !(++c))
goto errline;
name = strchr(c, ':');
if (!name || !(++name))
goto errline;
ao = audio_output_find(name);
if (ao != NULL && atoi(c) == 0)
ao->enabled = false;
continue;
errline:
/* nonfatal */
g_warning("invalid line in state_file: %s\n", buffer);
}
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Saving and loading the audio output states to/from the state file.
*
*/
#ifndef OUTPUT_STATE_H
#define OUTPUT_STATE_H
#include <stdio.h>
void
readAudioDevicesState(FILE *fp);
void
saveAudioDevicesState(FILE *fp);
#endif
......@@ -38,6 +38,16 @@ static void ao_command_finished(struct audio_output *ao)
notify_signal(&audio_output_client_notify);
}
static void
ao_close(struct audio_output *ao)
{
assert(ao->open);
ao->plugin->close(ao->data);
pcm_convert_deinit(&ao->convert_state);
ao->open = false;
}
static void ao_play(struct audio_output *ao)
{
const char *data = ao->args.play.data;
......@@ -46,10 +56,10 @@ static void ao_play(struct audio_output *ao)
assert(size > 0);
if (!audio_format_equals(&ao->inAudioFormat, &ao->outAudioFormat)) {
data = pcm_convert(&ao->convState,
&ao->inAudioFormat, data, size,
&ao->outAudioFormat, &size);
if (!audio_format_equals(&ao->in_audio_format, &ao->out_audio_format)) {
data = pcm_convert(&ao->convert_state,
&ao->in_audio_format, data, size,
&ao->out_audio_format, &size);
/* under certain circumstances, pcm_convert() may
return an empty buffer - this condition should be
......@@ -62,9 +72,7 @@ static void ao_play(struct audio_output *ao)
ret = ao->plugin->play(ao->data, data, size);
if (!ret) {
ao->plugin->cancel(ao->data);
ao->plugin->close(ao->data);
pcm_convert_deinit(&ao->convState);
ao->open = false;
ao_close(ao);
}
ao_command_finished(ao);
......@@ -77,11 +85,19 @@ static void ao_pause(struct audio_output *ao)
if (ao->plugin->pause != NULL) {
/* pause is supported */
ao_command_finished(ao);
ao->plugin->pause(ao->data);
do {
bool ret;
ret = ao->plugin->pause(ao->data);
if (!ret) {
ao_close(ao);
break;
}
} while (ao->command == AO_COMMAND_NONE);
} else {
/* pause is not supported - simply close the device */
ao->plugin->close(ao->data);
ao->open = false;
ao_close(ao);
ao_command_finished(ao);
}
}
......@@ -99,14 +115,14 @@ static gpointer audio_output_task(gpointer arg)
case AO_COMMAND_OPEN:
assert(!ao->open);
pcm_convert_init(&ao->convState);
ret = ao->plugin->open(ao->data,
&ao->outAudioFormat);
&ao->out_audio_format);
assert(!ao->open);
if (ret == true)
if (ret) {
pcm_convert_init(&ao->convert_state);
ao->open = true;
else
} else
ao->reopen_after = time(NULL) + REOPEN_AFTER;
ao_command_finished(ao);
......@@ -115,9 +131,8 @@ static gpointer audio_output_task(gpointer arg)
case AO_COMMAND_CLOSE:
assert(ao->open);
ao->plugin->cancel(ao->data);
ao->plugin->close(ao->data);
ao->open = false;
ao_close(ao);
ao_command_finished(ao);
break;
......
......@@ -66,13 +66,10 @@ const char *path_get_fs_charset(void)
void path_global_init(void)
{
struct config_param *fs_charset_param =
config_get_param(CONF_FS_CHARSET);
const char *charset = NULL;
if (fs_charset_param) {
charset = fs_charset_param->value;
} else {
charset = config_get_string(CONF_FS_CHARSET, NULL);
if (charset == NULL) {
const gchar **encodings;
g_get_filename_charsets(&encodings);
......
......@@ -71,7 +71,7 @@ void initPermissions(void)
{
char *password;
unsigned permission;
struct config_param *param;
const struct config_param *param;
permission_passwords = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
......
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "playerData.h"
#include "pipe.h"
#include "conf.h"
#include "log.h"
#include <stdlib.h>
#define DEFAULT_BUFFER_SIZE 2048
#define DEFAULT_BUFFER_BEFORE_PLAY 10
unsigned int buffered_chunks;
unsigned int buffered_before_play;
void initPlayerData(void)
{
float perc = DEFAULT_BUFFER_BEFORE_PLAY;
char *test;
size_t bufferSize = DEFAULT_BUFFER_SIZE;
struct config_param *param;
param = config_get_param(CONF_AUDIO_BUFFER_SIZE);
if (param) {
bufferSize = strtol(param->value, &test, 10);
if (*test != '\0' || bufferSize <= 0) {
FATAL("buffer size \"%s\" is not a positive integer, "
"line %i\n", param->value, param->line);
}
}
bufferSize *= 1024;
buffered_chunks = bufferSize / CHUNK_SIZE;
if (buffered_chunks >= 1 << 15) {
FATAL("buffer size \"%li\" is too big\n", (long)bufferSize);
}
param = config_get_param(CONF_BUFFER_BEFORE_PLAY);
if (param) {
perc = strtod(param->value, &test);
if (*test != '%' || perc < 0 || perc > 100) {
FATAL("buffered before play \"%s\" is not a positive "
"percentage and less than 100 percent, line %i"
"\n", param->value, param->line);
}
}
buffered_before_play = (perc / 100) * buffered_chunks;
if (buffered_before_play > buffered_chunks) {
buffered_before_play = buffered_chunks;
}
}
......@@ -49,12 +49,16 @@ void pc_deinit(void)
void
pc_song_deleted(const struct song *song)
{
if (pc.errored_song == song)
if (pc.errored_song == song) {
pc.error = PLAYER_ERROR_NOERROR;
pc.errored_song = NULL;
}
}
static void player_command(enum player_command cmd)
{
assert(pc.command == PLAYER_COMMAND_NONE);
pc.command = cmd;
while (pc.command != PLAYER_COMMAND_NONE) {
notify_signal(&pc.notify);
......@@ -90,7 +94,11 @@ void playerWait(void)
void playerKill(void)
{
assert(pc.thread != NULL);
player_command(PLAYER_COMMAND_EXIT);
g_thread_join(pc.thread);
pc.thread = NULL;
idle_add(IDLE_PLAYER);
}
......
......@@ -62,6 +62,10 @@ enum player_error {
struct player_control {
unsigned int buffered_before_play;
/** the handle of the player thread, or NULL if the player
thread isn't running */
GThread *thread;
struct notify notify;
volatile enum player_command command;
volatile enum player_state state;
......
......@@ -19,7 +19,8 @@
#include "player_thread.h"
#include "player_control.h"
#include "decoder_control.h"
#include "audio.h"
#include "decoder_thread.h"
#include "output_all.h"
#include "pcm_volume.h"
#include "path.h"
#include "event_pipe.h"
......@@ -94,7 +95,8 @@ static void player_stop_decoder(void)
event_pipe_emit(PIPE_EVENT_PLAYLIST);
}
static int player_wait_for_decoder(struct player *player)
static bool
player_wait_for_decoder(struct player *player)
{
dc_command_wait(&pc.notify);
......@@ -102,7 +104,8 @@ static int player_wait_for_decoder(struct player *player)
assert(dc.next_song == NULL || dc.next_song->url != NULL);
pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_FILE;
return -1;
pc.next_song = NULL;
return false;
}
pc.total_time = pc.next_song->tag != NULL
......@@ -119,7 +122,7 @@ static int player_wait_for_decoder(struct player *player)
/* call syncPlaylistWithQueue() in the main thread */
event_pipe_emit(PIPE_EVENT_PLAYLIST);
return 0;
return true;
}
static bool player_seek_decoder(struct player *player)
......@@ -132,7 +135,13 @@ static bool player_seek_decoder(struct player *player)
player->next_song_chunk = -1;
music_pipe_clear();
dc_start_async(pc.next_song);
player_wait_for_decoder(player);
ret = player_wait_for_decoder(player);
if (!ret)
return false;
} else {
pc.next_song = NULL;
player->queued = false;
}
where = pc.seek_where;
......@@ -172,10 +181,10 @@ static void player_process_command(struct player *player)
case PLAYER_COMMAND_PAUSE:
player->paused = !player->paused;
if (player->paused) {
audio_output_pause_all();
audio_output_all_pause();
pc.state = PLAYER_STATE_PAUSE;
} else {
if (openAudioDevice(NULL)) {
if (audio_output_all_open(NULL)) {
pc.state = PLAYER_STATE_PLAY;
} else {
char *uri = song_get_uri(dc.next_song);
......@@ -194,7 +203,7 @@ static void player_process_command(struct player *player)
break;
case PLAYER_COMMAND_SEEK:
dropBufferedAudio();
audio_output_all_cancel();
if (player_seek_decoder(player)) {
player->xfade = XFADE_UNKNOWN;
......@@ -228,7 +237,7 @@ static void player_process_command(struct player *player)
}
}
static int
static bool
play_chunk(struct song *song, struct music_chunk *chunk,
const struct audio_format *format, double sizeToTime)
{
......@@ -236,7 +245,7 @@ play_chunk(struct song *song, struct music_chunk *chunk,
pc.bit_rate = chunk->bit_rate;
if (chunk->tag != NULL) {
sendMetadataToAudioDevice(chunk->tag);
audio_output_all_tag(chunk->tag);
if (!song_is_file(song)) {
/* always update the tag of remote streams */
......@@ -249,7 +258,7 @@ play_chunk(struct song *song, struct music_chunk *chunk,
/* the main thread will update the playlist
version when he receives this event */
event_pipe_emit(PIPE_EVENT_PLAYLIST);
event_pipe_emit(PIPE_EVENT_TAG);
/* notify all clients that the tag of the
current song has changed */
......@@ -258,16 +267,19 @@ play_chunk(struct song *song, struct music_chunk *chunk,
}
if (chunk->length == 0)
return 0;
return true;
pcm_volume(chunk->data, chunk->length,
format, pc.software_volume);
if (!playAudio(chunk->data, chunk->length))
return -1;
if (!audio_output_all_play(chunk->data, chunk->length)) {
pc.errored_song = dc.current_song;
pc.error = PLAYER_ERROR_AUDIO;
return false;
}
pc.total_play_time += sizeToTime * chunk->length;
return 0;
return true;
}
static void do_play(void)
......@@ -293,7 +305,7 @@ static void do_play(void)
music_pipe_set_lazy(false);
dc_start(&pc.notify, pc.next_song);
if (player_wait_for_decoder(&player) < 0) {
if (!player_wait_for_decoder(&player)) {
player_stop_decoder();
player_command_finished();
return;
......@@ -303,12 +315,12 @@ static void do_play(void)
pc.state = PLAYER_STATE_PLAY;
player_command_finished();
while (1) {
while (true) {
player_process_command(&player);
if (pc.command == PLAYER_COMMAND_STOP ||
pc.command == PLAYER_COMMAND_EXIT ||
pc.command == PLAYER_COMMAND_CLOSE_AUDIO) {
dropBufferedAudio();
audio_output_all_cancel();
break;
}
......@@ -336,7 +348,7 @@ static void do_play(void)
else if (!decoder_is_starting()) {
/* the decoder is ready and ok */
player.decoder_starting = false;
if (!openAudioDevice(&dc.out_audio_format)) {
if (!audio_output_all_open(&dc.out_audio_format)) {
char *uri = song_get_uri(dc.next_song);
g_warning("problems opening audio device "
"while playing \"%s\"", uri);
......@@ -349,7 +361,7 @@ static void do_play(void)
}
if (player.paused)
closeAudioDevice();
audio_output_all_close();
pc.total_time = dc.total_time;
pc.audio_format = dc.in_audio_format;
......@@ -372,17 +384,6 @@ static void do_play(void)
*/
#endif
if (decoder_is_idle() && !player.queued &&
player.next_song_chunk < 0 &&
pc.next_song != NULL &&
pc.command == PLAYER_COMMAND_NONE) {
/* the decoder has finished the current song;
request the next song from the playlist */
pc.next_song = NULL;
event_pipe_emit(PIPE_EVENT_PLAYLIST);
}
if (decoder_is_idle() && player.queued) {
/* the decoder has finished the current song;
make it decode the next song */
......@@ -402,6 +403,7 @@ static void do_play(void)
crossFadeChunks =
cross_fade_calc(pc.cross_fade_seconds, dc.total_time,
&dc.out_audio_format,
&play_audio_format,
music_pipe_size() -
pc.buffered_before_play);
if (crossFadeChunks > 0) {
......@@ -468,8 +470,8 @@ static void do_play(void)
}
/* play the current chunk */
if (play_chunk(player.song, beginChunk,
&play_audio_format, sizeToTime) < 0)
if (!play_chunk(player.song, beginChunk,
&play_audio_format, sizeToTime))
break;
music_pipe_shift();
......@@ -492,10 +494,8 @@ static void do_play(void)
player.xfade = XFADE_UNKNOWN;
player.next_song_chunk = -1;
if (player_wait_for_decoder(&player) < 0)
return;
event_pipe_emit(PIPE_EVENT_PLAYLIST);
if (!player_wait_for_decoder(&player))
break;
} else if (decoder_is_idle()) {
break;
} else {
......@@ -506,37 +506,47 @@ static void do_play(void)
unsigned num_frames = CHUNK_SIZE / frame_size;
/*DEBUG("waiting for decoded audio, play silence\n");*/
if (!playAudio(silence, num_frames * frame_size))
if (!audio_output_all_play(silence, num_frames * frame_size))
break;
}
}
if (player.queued) {
assert(pc.next_song != NULL);
pc.next_song = NULL;
}
player_stop_decoder();
}
static gpointer player_task(G_GNUC_UNUSED gpointer arg)
{
decoder_thread_start();
while (1) {
switch (pc.command) {
case PLAYER_COMMAND_PLAY:
case PLAYER_COMMAND_QUEUE:
assert(pc.next_song != NULL);
do_play();
break;
case PLAYER_COMMAND_STOP:
case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_PAUSE:
pc.next_song = NULL;
player_command_finished();
break;
case PLAYER_COMMAND_CLOSE_AUDIO:
closeAudioDevice();
audio_output_all_close();
player_command_finished();
break;
case PLAYER_COMMAND_EXIT:
dc_quit(&pc.notify);
closeAudioDevice();
dc_quit();
audio_output_all_close();
player_command_finished();
g_thread_exit(NULL);
break;
......@@ -557,8 +567,10 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
void player_create(void)
{
GError *e = NULL;
GThread *t;
if (!(t = g_thread_create(player_task, NULL, FALSE, &e)))
assert(pc.thread == NULL);
pc.thread = g_thread_create(player_task, NULL, true, &e);
if (pc.thread == NULL)
g_error("Failed to spawn player task: %s", e->message);
}
......@@ -16,1396 +16,354 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "playlist.h"
#include "playlist_internal.h"
#include "playlist_save.h"
#include "player_control.h"
#include "command.h"
#include "ls.h"
#include "tag.h"
#include "song.h"
#include "song_print.h"
#include "client.h"
#include "conf.h"
#include "database.h"
#include "mapper.h"
#include "path.h"
#include "stored_playlist.h"
#include "ack.h"
#include "idle.h"
#include <glib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#define PLAYLIST_STATE_STOP 0
#define PLAYLIST_STATE_PLAY 1
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"
#define PLAYLIST_PREV_UNLESS_ELAPSED 10
#define PLAYLIST_STATE_FILE_STATE "state: "
#define PLAYLIST_STATE_FILE_RANDOM "random: "
#define PLAYLIST_STATE_FILE_REPEAT "repeat: "
#define PLAYLIST_STATE_FILE_CURRENT "current: "
#define PLAYLIST_STATE_FILE_TIME "time: "
#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: "
#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin"
#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end"
#define PLAYLIST_STATE_FILE_STATE_PLAY "play"
#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause"
#define PLAYLIST_STATE_FILE_STATE_STOP "stop"
#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX
#define PLAYLIST_HASH_MULT 4
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
static GRand *g_rand;
static Playlist playlist;
static int playlist_state = PLAYLIST_STATE_STOP;
unsigned playlist_max_length = DEFAULT_PLAYLIST_MAX_LENGTH;
static int playlist_stopOnError;
static unsigned playlist_errorCount;
static int playlist_noGoToNext;
bool playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS;
static void swapOrder(int a, int b);
static void playPlaylistOrderNumber(int orderNum);
static void randomizeOrder(int start, int end);
static void incrPlaylistVersion(void)
void playlistVersionChange(struct playlist *playlist)
{
static unsigned long max = ((uint32_t) 1 << 31) - 1;
playlist.version++;
if (playlist.version >= max) {
for (unsigned i = 0; i < playlist.length; i++)
playlist.songMod[i] = 0;
playlist.version = 1;
}
queue_modify_all(&playlist->queue);
idle_add(IDLE_PLAYLIST);
}
void playlistVersionChange(void)
{
for (unsigned i = 0; i < playlist.length; i++)
playlist.songMod[i] = playlist.version;
incrPlaylistVersion();
}
static void incrPlaylistCurrent(void)
{
if (playlist.current < 0)
return;
if (playlist.current >= (int)playlist.length - 1) {
if (playlist.repeat)
playlist.current = 0;
else
playlist.current = -1;
} else
playlist.current++;
}
void initPlaylist(void)
{
char *test;
struct config_param *param;
g_rand = g_rand_new();
playlist.length = 0;
playlist.repeat = false;
playlist.version = 1;
playlist.random = false;
playlist.queued = -1;
playlist.current = -1;
param = config_get_param(CONF_MAX_PLAYLIST_LENGTH);
if (param) {
playlist_max_length = strtol(param->value, &test, 10);
if (*test != '\0')
g_error("max playlist length \"%s\" is not an integer, "
"line %i", param->value, param->line);
}
playlist_saveAbsolutePaths =
config_get_bool(CONF_SAVE_ABSOLUTE_PATHS,
DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS);
playlist.songs = g_malloc(sizeof(playlist.songs[0]) *
playlist_max_length);
playlist.songMod = g_malloc(sizeof(playlist.songMod[0]) *
playlist_max_length);
playlist.order = g_malloc(sizeof(playlist.order[0]) *
playlist_max_length);
playlist.idToPosition = g_malloc(sizeof(playlist.idToPosition[0]) *
playlist_max_length *
PLAYLIST_HASH_MULT);
playlist.positionToId = g_malloc(sizeof(playlist.positionToId[0]) *
playlist_max_length);
memset(playlist.songs, 0, sizeof(char *) * playlist_max_length);
for (unsigned i = 0; i < playlist_max_length * PLAYLIST_HASH_MULT;
i++) {
playlist.idToPosition[i] = -1;
}
}
static unsigned getNextId(void)
{
static unsigned cur = (unsigned)-1;
do {
cur++;
if (cur >= playlist_max_length * PLAYLIST_HASH_MULT) {
cur = 0;
}
} while (playlist.idToPosition[cur] != -1);
return cur;
}
void finishPlaylist(void)
{
for (unsigned i = 0; i < playlist.length; i++)
if (!song_in_database(playlist.songs[i]))
song_free(playlist.songs[i]);
playlist.length = 0;
free(playlist.songs);
playlist.songs = NULL;
free(playlist.songMod);
playlist.songMod = NULL;
free(playlist.order);
playlist.order = NULL;
free(playlist.idToPosition);
playlist.idToPosition = NULL;
free(playlist.positionToId);
playlist.positionToId = NULL;
g_rand_free(g_rand);
g_rand = NULL;
}
void clearPlaylist(void)
{
stopPlaylist();
for (unsigned i = 0; i < playlist.length; i++) {
if (!song_in_database(playlist.songs[i])) {
pc_song_deleted(playlist.songs[i]);
song_free(playlist.songs[i]);
}
playlist.idToPosition[playlist.positionToId[i]] = -1;
playlist.songs[i] = NULL;
}
playlist.length = 0;
playlist.current = -1;
incrPlaylistVersion();
}
void showPlaylist(struct client *client)
{
for (unsigned i = 0; i < playlist.length; i++) {
char *uri = song_get_uri(playlist.songs[i]);
client_printf(client, "%i:%s\n", i, uri);
g_free(uri);
}
}
static void playlist_save(FILE *fp)
{
for (unsigned i = 0; i < playlist.length; i++) {
char *uri = song_get_uri(playlist.songs[i]);
fprintf(fp, "%i:%s\n", i, uri);
g_free(uri);
}
}
void savePlaylistState(FILE *fp)
{
fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE);
switch (playlist_state) {
case PLAYLIST_STATE_PLAY:
switch (getPlayerState()) {
case PLAYER_STATE_PAUSE:
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE);
break;
default:
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PLAY);
}
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT,
playlist.order[playlist.current]);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME,
getPlayerElapsedTime());
break;
default:
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP);
break;
}
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM, playlist.random);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT, playlist.repeat);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE,
(int)(getPlayerCrossFade()));
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN);
playlist_save(fp);
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END);
}
static void loadPlaylistFromStateFile(FILE *fp, char *buffer,
int state, int current, int seek_time)
void
playlist_tag_changed(struct playlist *playlist)
{
char *temp;
int song;
if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) {
g_warning("No playlist in state file");
if (!playlist->playing)
return;
}
while (!g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
g_strchomp(buffer);
temp = strtok(buffer, ":");
if (temp == NULL) {
g_warning("Malformed playlist line in state file");
break;
}
song = atoi(temp);
if (!(temp = strtok(NULL, ""))) {
g_warning("Malformed playlist line in state file");
break;
}
if (addToPlaylist(temp, NULL) == PLAYLIST_RESULT_SUCCESS
&& current == song) {
if (state != PLAYER_STATE_STOP) {
playPlaylist(playlist.length - 1, 0);
}
if (state == PLAYER_STATE_PAUSE) {
playerPause();
}
if (state != PLAYER_STATE_STOP) {
seekSongInPlaylist(playlist.length - 1,
seek_time);
}
}
if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) {
g_warning("'%s' not found in state file",
PLAYLIST_STATE_FILE_PLAYLIST_END);
break;
}
}
}
void readPlaylistState(FILE *fp)
{
int current = -1;
int seek_time = 0;
int state = PLAYER_STATE_STOP;
char buffer[PLAYLIST_BUFFER_SIZE];
while (fgets(buffer, sizeof(buffer), fp)) {
g_strchomp(buffer);
if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_STATE)) {
if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
PLAYLIST_STATE_FILE_STATE_PLAY) == 0) {
state = PLAYER_STATE_PLAY;
} else
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
PLAYLIST_STATE_FILE_STATE_PAUSE)
== 0) {
state = PLAYER_STATE_PAUSE;
}
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) {
seek_time =
atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)]));
} else
if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_REPEAT)) {
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
"1") == 0) {
setPlaylistRepeatStatus(true);
} else
setPlaylistRepeatStatus(false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CROSSFADE)) {
setPlayerCrossFade(atoi
(&
(buffer
[strlen
(PLAYLIST_STATE_FILE_CROSSFADE)])));
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_RANDOM)) {
if (strcmp
(&
(buffer
[strlen(PLAYLIST_STATE_FILE_RANDOM)]),
"1") == 0) {
setPlaylistRandomStatus(true);
} else
setPlaylistRandomStatus(false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CURRENT)) {
current = atoi(&(buffer
[strlen
(PLAYLIST_STATE_FILE_CURRENT)]));
} else if (g_str_has_prefix(buffer,
PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
if (state == PLAYER_STATE_STOP)
current = -1;
loadPlaylistFromStateFile(fp, buffer, state,
current, seek_time);
}
}
}
static void printPlaylistSongInfo(struct client *client, unsigned song)
{
song_print_info(client, playlist.songs[song]);
client_printf(client, "Pos: %u\nId: %u\n",
song, playlist.positionToId[song]);
}
int playlistChanges(struct client *client, uint32_t version)
{
for (unsigned i = 0; i < playlist.length; i++) {
if (version > playlist.version ||
playlist.songMod[i] >= version ||
playlist.songMod[i] == 0) {
printPlaylistSongInfo(client, i);
}
}
return 0;
}
int playlistChangesPosId(struct client *client, uint32_t version)
{
for (unsigned i = 0; i < playlist.length; i++) {
if (version > playlist.version ||
playlist.songMod[i] >= version ||
playlist.songMod[i] == 0) {
client_printf(client, "cpos: %i\nId: %i\n",
i, playlist.positionToId[i]);
}
}
return 0;
}
enum playlist_result
playlistInfo(struct client *client, unsigned start, unsigned end)
{
if (end > playlist.length)
end = playlist.length;
if (start > end)
return PLAYLIST_RESULT_BAD_RANGE;
for (unsigned i = start; i < end; i++)
printPlaylistSongInfo(client, i);
return PLAYLIST_RESULT_SUCCESS;
}
static int song_id_to_position(unsigned id)
{
if (id >= PLAYLIST_HASH_MULT*playlist_max_length)
return -1;
assert(playlist.idToPosition[id] >= -1);
assert(playlist.idToPosition[id] < (int)playlist.length);
return playlist.idToPosition[id];
}
enum playlist_result playlistId(struct client *client, int id)
{
int begin = 0;
unsigned end = playlist.length;
if (id >= 0) {
begin = song_id_to_position(id);
if (begin < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
end = begin + 1;
}
for (unsigned i = begin; i < end; i++)
printPlaylistSongInfo(client, i);
return PLAYLIST_RESULT_SUCCESS;
}
assert(playlist->current >= 0);
static void swapSongs(unsigned song1, unsigned song2)
{
struct song *sTemp;
unsigned iTemp;
sTemp = playlist.songs[song1];
playlist.songs[song1] = playlist.songs[song2];
playlist.songs[song2] = sTemp;
playlist.songMod[song1] = playlist.version;
playlist.songMod[song2] = playlist.version;
playlist.idToPosition[playlist.positionToId[song1]] = song2;
playlist.idToPosition[playlist.positionToId[song2]] = song1;
iTemp = playlist.positionToId[song1];
playlist.positionToId[song1] = playlist.positionToId[song2];
playlist.positionToId[song2] = iTemp;
queue_modify(&playlist->queue, playlist->current);
idle_add(IDLE_PLAYLIST);
}
static void queueNextSongInPlaylist(void)
void
playlist_init(struct playlist *playlist)
{
if (playlist.current < (int)playlist.length - 1) {
char *uri;
playlist.queued = playlist.current + 1;
uri = song_get_uri(playlist. songs[playlist.order[playlist.queued]]);
g_debug("playlist: queue song %i:\"%s\"",
playlist.queued, uri);
g_free(uri);
queueSong(playlist.songs[playlist.order[playlist.queued]]);
} else if (playlist.length && playlist.repeat) {
char *uri;
queue_init(&playlist->queue,
config_get_positive(CONF_MAX_PLAYLIST_LENGTH,
DEFAULT_PLAYLIST_MAX_LENGTH));
if (playlist.length > 1 && playlist.random) {
randomizeOrder(0, playlist.length - 1);
}
playlist.queued = 0;
uri = song_get_uri(playlist. songs[playlist.order[playlist.queued]]);
g_debug("playlist: queue song %i:\"%s\"",
playlist.queued, uri);
g_free(uri);
queueSong(playlist.songs[playlist.order[playlist.queued]]);
}
playlist->queued = -1;
playlist->current = -1;
}
static void syncPlaylistWithQueue(void)
void
playlist_finish(struct playlist *playlist)
{
if (pc.next_song == NULL && playlist.queued != -1) {
playlist.current = playlist.queued;
playlist.queued = -1;
idle_add(IDLE_PLAYER);
}
queue_finish(&playlist->queue);
}
static void clearPlayerQueue(void)
{
assert(playlist.queued >= 0);
playlist.queued = -1;
pc_cancel();
}
#ifndef WIN32
enum playlist_result
playlist_append_file(const char *path, int uid, unsigned *added_id)
/**
* Queue a song, addressed by its order number.
*/
static void
playlist_queue_song_order(struct playlist *playlist, unsigned order)
{
int ret;
struct stat st;
struct song *song;
char *uri;
if (uid <= 0)
/* unauthenticated client */
return PLAYLIST_RESULT_DENIED;
ret = stat(path, &st);
if (ret < 0)
return PLAYLIST_RESULT_ERRNO;
if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444)
/* client is not owner */
return PLAYLIST_RESULT_DENIED;
song = song_file_load(path, NULL);
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return addSongToPlaylist(song, added_id);
}
#endif
static struct song *
song_by_url(const char *url)
{
struct song *song;
assert(queue_valid_order(&playlist->queue, order));
song = db_get_song(url);
if (song != NULL)
return song;
playlist->queued = order;
if (uri_has_scheme(url))
return song_remote_new(url);
song = queue_get_order(&playlist->queue, order);
uri = song_get_uri(song);
g_debug("queue song %i:\"%s\"", playlist->queued, uri);
g_free(uri);
return NULL;
queueSong(song);
}
enum playlist_result addToPlaylist(const char *url, unsigned *added_id)
/**
* Check if the player thread has already started playing the "queued"
* song.
*/
static void syncPlaylistWithQueue(struct playlist *playlist)
{
struct song *song;
if (pc.next_song == NULL && playlist->queued != -1) {
/* queued song has started: copy queued to current,
and notify the clients */
g_debug("add to playlist: %s", url);
playlist->current = playlist->queued;
playlist->queued = -1;
song = song_by_url(url);
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return addSongToPlaylist(song, added_id);
}
enum playlist_result
addSongToPlaylist(struct song *song, unsigned *added_id)
{
unsigned id;
if (playlist.length == playlist_max_length)
return PLAYLIST_RESULT_TOO_LARGE;
if (playlist_state == PLAYLIST_STATE_PLAY && playlist.queued >= 0 &&
playlist.current == (int)playlist.length - 1)
clearPlayerQueue();
id = getNextId();
playlist.songs[playlist.length] = song;
playlist.songMod[playlist.length] = playlist.version;
playlist.order[playlist.length] = playlist.length;
playlist.positionToId[playlist.length] = id;
playlist.idToPosition[playlist.positionToId[playlist.length]] =
playlist.length;
playlist.length++;
if (playlist.random) {
unsigned start;
/*if(playlist_state==PLAYLIST_STATE_STOP) start = 0;
else */ if (playlist.queued >= 0)
start = playlist.queued + 1;
else
start = playlist.current + 1;
if (start < playlist.length) {
unsigned swap = g_rand_int_range(g_rand, start,
playlist.length);
swapOrder(playlist.length - 1, swap);
}
idle_add(IDLE_PLAYER);
}
incrPlaylistVersion();
if (added_id)
*added_id = id;
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result swapSongsInPlaylist(unsigned song1, unsigned song2)
const struct song *
playlist_get_queued_song(struct playlist *playlist)
{
if (song1 >= playlist.length || song2 >= playlist.length)
return PLAYLIST_RESULT_BAD_RANGE;
if (playlist_state == PLAYLIST_STATE_PLAY && playlist.queued >= 0) {
unsigned queuedSong = playlist.order[playlist.queued];
unsigned currentSong = playlist.order[playlist.current];
if (queuedSong == song1 || queuedSong == song2
|| currentSong == song1 || currentSong == song2)
clearPlayerQueue();
}
swapSongs(song1, song2);
if (playlist.random) {
unsigned i, k;
int j = -1;
for (i = 0; playlist.order[i] != song1; i++) {
if (playlist.order[i] == song2)
j = i;
}
k = i;
for (; j == -1; i++)
if (playlist.order[i] == song2)
j = i;
swapOrder(k, j);
} else {
if (playlist.current == (int)song1)
playlist.current = song2;
else if (playlist.current == (int)song2)
playlist.current = song1;
}
incrPlaylistVersion();
if (!playlist->playing || playlist->queued < 0)
return NULL;
return PLAYLIST_RESULT_SUCCESS;
return queue_get_order(&playlist->queue, playlist->queued);
}
enum playlist_result swapSongsInPlaylistById(unsigned id1, unsigned id2)
void
playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
{
int song1 = song_id_to_position(id1);
int song2 = song_id_to_position(id2);
if (song1 < 0 || song2 < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
int next_order;
const struct song *next_song;
return swapSongsInPlaylist(song1, song2);
}
if (!playlist->playing)
return;
#define moveSongFromTo(from, to) { \
playlist.idToPosition[playlist.positionToId[from]] = to; \
playlist.positionToId[to] = playlist.positionToId[from]; \
playlist.songs[to] = playlist.songs[from]; \
playlist.songMod[to] = playlist.version; \
}
assert(!queue_is_empty(&playlist->queue));
assert((playlist->queued < 0) == (prev == NULL));
enum playlist_result deleteFromPlaylist(unsigned song)
{
unsigned i;
unsigned songOrder;
next_order = playlist->current >= 0
? queue_next_order(&playlist->queue, playlist->current)
: 0;
if (song >= playlist.length)
return PLAYLIST_RESULT_BAD_RANGE;
if (next_order == 0 && playlist->queue.random) {
/* shuffle the song order again, so we get a different
order each time the playlist is played
completely */
unsigned current_position =
queue_order_to_position(&playlist->queue,
playlist->current);
if (playlist_state == PLAYLIST_STATE_PLAY && playlist.queued >= 0
&& (playlist.order[playlist.queued] == song
|| playlist.order[playlist.current] == song))
clearPlayerQueue();
queue_shuffle_order(&playlist->queue);
if (!song_in_database(playlist.songs[song])) {
pc_song_deleted(playlist.songs[song]);
song_free(playlist.songs[song]);
/* make sure that the playlist->current still points to
the current song, after the song order has been
shuffled */
playlist->current =
queue_position_to_order(&playlist->queue,
current_position);
}
playlist.idToPosition[playlist.positionToId[song]] = -1;
/* delete song from songs array */
for (i = song; i < playlist.length - 1; i++) {
moveSongFromTo(i + 1, i);
}
/* now find it in the order array */
for (i = 0; i < playlist.length - 1; i++) {
if (playlist.order[i] == song)
break;
}
songOrder = i;
/* delete the entry from the order array */
for (; i < playlist.length - 1; i++)
playlist.order[i] = playlist.order[i + 1];
/* readjust values in the order array */
for (i = 0; i < playlist.length - 1; i++) {
if (playlist.order[i] > song)
playlist.order[i]--;
}
/* now take care of other misc stuff */
playlist.songs[playlist.length - 1] = NULL;
playlist.length--;
incrPlaylistVersion();
if (playlist_state != PLAYLIST_STATE_STOP
&& playlist.current == (int)songOrder) {
/*if(playlist.current>=playlist.length) return playerStop(fd);
else return playPlaylistOrderNumber(fd,playlist.current); */
playerWait();
playlist_noGoToNext = 1;
}
if (next_order >= 0)
next_song = queue_get_order(&playlist->queue, next_order);
else
next_song = NULL;
if (playlist.current > (int)songOrder) {
playlist.current--;
} else if (playlist.current >= (int)playlist.length) {
incrPlaylistCurrent();
if (prev != NULL && next_song != prev) {
/* clear the currently queued song */
pc_cancel();
playlist->queued = -1;
}
if (playlist.queued > (int)songOrder) {
playlist.queued--;
if (next_order >= 0) {
if (next_song != prev)
playlist_queue_song_order(playlist, next_order);
else
playlist->queued = next_order;
}
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result deleteFromPlaylistById(unsigned id)
{
int song = song_id_to_position(id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return deleteFromPlaylist(song);
}
void
deleteASongFromPlaylist(const struct song *song)
{
if (NULL == playlist.songs)
return;
for (int i = playlist.length - 1; i >= 0; --i)
if (song == playlist.songs[i])
deleteFromPlaylist(i);
pc_song_deleted(song);
}
void stopPlaylist(void)
{
g_debug("playlist: stop");
playerWait();
playlist.queued = -1;
playlist_state = PLAYLIST_STATE_STOP;
playlist_noGoToNext = 0;
if (playlist.random)
randomizeOrder(0, playlist.length - 1);
}
static void playPlaylistOrderNumber(int orderNum)
playPlaylistOrderNumber(struct playlist *playlist, int orderNum)
{
struct song *song;
char *uri;
playlist_state = PLAYLIST_STATE_PLAY;
playlist_noGoToNext = 0;
playlist.queued = -1;
playlist->playing = true;
playlist->queued = -1;
uri = song_get_uri(playlist.songs[playlist.order[orderNum]]);
g_debug("playlist: play %i:\"%s\"", orderNum, uri);
g_free(uri);
playerPlay(playlist.songs[playlist.order[orderNum]]);
playlist.current = orderNum;
}
song = queue_get_order(&playlist->queue, orderNum);
enum playlist_result playPlaylist(int song, int stopOnError)
{
unsigned i = song;
clearPlayerError();
if (song == -1) {
if (playlist.length == 0)
return PLAYLIST_RESULT_SUCCESS;
if (playlist_state == PLAYLIST_STATE_PLAY) {
playerSetPause(0);
return PLAYLIST_RESULT_SUCCESS;
}
if (playlist.current >= 0 &&
playlist.current < (int)playlist.length) {
i = playlist.current;
} else {
i = 0;
}
} else if (song < 0 || song >= (int)playlist.length) {
return PLAYLIST_RESULT_BAD_RANGE;
}
if (playlist.random) {
if (song == -1 && playlist_state == PLAYLIST_STATE_PLAY) {
randomizeOrder(0, playlist.length - 1);
} else {
if (song >= 0)
for (i = 0; song != (int)playlist.order[i];
i++) ;
if (playlist_state == PLAYLIST_STATE_STOP) {
playlist.current = 0;
}
swapOrder(i, playlist.current);
i = playlist.current;
}
}
playlist_stopOnError = stopOnError;
playlist_errorCount = 0;
playPlaylistOrderNumber(i);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result playPlaylistById(int id, int stopOnError)
{
int song;
if (id == -1) {
return playPlaylist(id, stopOnError);
}
song = song_id_to_position(id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
uri = song_get_uri(song);
g_debug("play %i:\"%s\"", orderNum, uri);
g_free(uri);
return playPlaylist(song, stopOnError);
playerPlay(song);
playlist->current = orderNum;
}
static void syncCurrentPlayerDecodeMetadata(void)
{
struct song *song;
int songNum;
if (playlist_state != PLAYLIST_STATE_PLAY)
return;
songNum = playlist.order[playlist.current];
song = playlist.songs[songNum];
if (song != playlist.prev_song) {
/* song change: initialize playlist.prev_{song,tag} */
playlist.prev_song = song;
playlist.prev_tag = song->tag;
} else if (song->tag != playlist.prev_tag) {
/* tag change: update playlist */
static void
playPlaylistIfPlayerStopped(struct playlist *playlist);
playlist.songMod[songNum] = playlist.version;
incrPlaylistVersion();
playlist.prev_tag = song->tag;
}
}
void syncPlayerAndPlaylist(void)
/**
* 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 syncPlayerAndPlaylist(struct playlist *playlist)
{
if (playlist_state != PLAYLIST_STATE_PLAY)
if (!playlist->playing)
/* this event has reached us out of sync: we aren't
playing anymore; ignore the event */
return;
if (getPlayerState() == PLAYER_STATE_STOP)
playPlaylistIfPlayerStopped();
/* 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 */
playPlaylistIfPlayerStopped(playlist);
else {
syncPlaylistWithQueue();
/* check if the player thread has already started
playing the queued song */
syncPlaylistWithQueue(playlist);
/* make sure the queued song is always set (if
possible) */
if (pc.next_song == NULL)
queueNextSongInPlaylist();
playlist_update_queued_song(playlist, NULL);
}
syncCurrentPlayerDecodeMetadata();
}
static void currentSongInPlaylist(void)
/**
* The player has stopped for some reason. Check the error, and
* decide whether to re-start playback
*/
static void
playPlaylistIfPlayerStopped(struct playlist *playlist)
{
if (playlist_state != PLAYLIST_STATE_PLAY)
return;
playlist_stopOnError = 0;
enum player_error error;
syncPlaylistWithQueue();
assert(playlist->playing);
assert(getPlayerState() == PLAYER_STATE_STOP);
if (playlist.current >= 0 && playlist.current < (int)playlist.length)
playPlaylistOrderNumber(playlist.current);
error = getPlayerError();
if (error == PLAYER_ERROR_NOERROR)
playlist->error_count = 0;
else
stopPlaylist();
}
void nextSongInPlaylist(void)
{
if (playlist_state != PLAYLIST_STATE_PLAY)
return;
syncPlaylistWithQueue();
playlist_stopOnError = 0;
if (playlist.current < (int)playlist.length - 1) {
playPlaylistOrderNumber(playlist.current + 1);
} else if (playlist.length && playlist.repeat) {
if (playlist.random)
randomizeOrder(0, playlist.length - 1);
playPlaylistOrderNumber(0);
} else {
incrPlaylistCurrent();
stopPlaylist();
}
}
void playPlaylistIfPlayerStopped(void)
{
if (getPlayerState() == PLAYER_STATE_STOP) {
int error = getPlayerError();
if (error == PLAYER_ERROR_NOERROR)
playlist_errorCount = 0;
else
playlist_errorCount++;
if (playlist_state == PLAYLIST_STATE_PLAY
&& ((playlist_stopOnError && error != PLAYER_ERROR_NOERROR)
|| error == PLAYER_ERROR_AUDIO
|| error == PLAYER_ERROR_SYSTEM
|| playlist_errorCount >= playlist.length)) {
stopPlaylist();
} else if (playlist_noGoToNext)
currentSongInPlaylist();
else
nextSongInPlaylist();
}
}
bool getPlaylistRepeatStatus(void)
{
return playlist.repeat;
}
bool getPlaylistRandomStatus(void)
{
return playlist.random;
}
void setPlaylistRepeatStatus(bool status)
{
if (playlist_state == PLAYLIST_STATE_PLAY &&
playlist.repeat && !status && playlist.queued == 0)
clearPlayerQueue();
playlist.repeat = status;
idle_add(IDLE_OPTIONS);
}
enum playlist_result moveSongInPlaylist(unsigned from, int to)
{
unsigned i;
struct song *tmpSong;
unsigned tmpId;
unsigned currentSong;
if (from >= playlist.length)
return PLAYLIST_RESULT_BAD_RANGE;
if ((to >= 0 && to >= (int)playlist.length) ||
(to < 0 && abs(to) > (int)playlist.length))
return PLAYLIST_RESULT_BAD_RANGE;
if ((int)from == to) /* no-op */
return PLAYLIST_RESULT_SUCCESS;
/*
* (to < 0) => move to offset from current song
* (-playlist.length == to) => move to position BEFORE current song
*/
currentSong = playlist.order[playlist.current];
if (to < 0 && playlist.current >= 0) {
if (currentSong == from)
/* no-op, can't be moved to offset of itself */
return PLAYLIST_RESULT_SUCCESS;
to = (currentSong + abs(to)) % playlist.length;
}
if (playlist_state == PLAYLIST_STATE_PLAY && playlist.queued >= 0) {
int queuedSong = playlist.order[playlist.queued];
if (queuedSong == (int)from || queuedSong == to
|| currentSong == from || (int)currentSong == to)
clearPlayerQueue();
}
tmpSong = playlist.songs[from];
tmpId = playlist.positionToId[from];
/* move songs to one less in from->to */
for (i = from; (int)i < to; i++) {
moveSongFromTo(i + 1, i);
}
/* move songs to one more in to->from */
for (i = from; (int)i > to; i--) {
moveSongFromTo(i - 1, i);
}
/* put song at _to_ */
playlist.idToPosition[tmpId] = to;
playlist.positionToId[to] = tmpId;
playlist.songs[to] = tmpSong;
playlist.songMod[to] = playlist.version;
/* now deal with order */
if (playlist.random) {
for (i = 0; i < playlist.length; i++) {
if (playlist.order[i] > from &&
(int)playlist.order[i] <= to) {
playlist.order[i]--;
} else if (playlist.order[i] < from &&
(int)playlist.order[i] >= to) {
playlist.order[i]++;
} else if (from == playlist.order[i]) {
playlist.order[i] = to;
}
}
}
++playlist->error_count;
if ((playlist->stop_on_error && error != PLAYER_ERROR_NOERROR) ||
error == PLAYER_ERROR_AUDIO || error == PLAYER_ERROR_SYSTEM ||
playlist->error_count >= queue_length(&playlist->queue))
/* too many errors, or critical error: stop
playback */
stopPlaylist(playlist);
else
{
if (playlist.current == (int)from)
playlist.current = to;
else if (playlist.current > (int)from &&
playlist.current <= to) {
playlist.current--;
} else if (playlist.current >= to &&
playlist.current < (int)from) {
playlist.current++;
}
/* this first if statement isn't necessary since the queue
* would have been cleared out if queued == from */
if (playlist.queued == (int)from)
playlist.queued = to;
else if (playlist.queued > (int)from && playlist.queued <= to) {
playlist.queued--;
} else if (playlist.queued>= to && playlist.queued < (int)from) {
playlist.queued++;
}
}
incrPlaylistVersion();
return PLAYLIST_RESULT_SUCCESS;
/* continue playback at the next song */
nextSongInPlaylist(playlist);
}
enum playlist_result moveSongInPlaylistById(unsigned id1, int to)
bool
getPlaylistRepeatStatus(const struct playlist *playlist)
{
int song = song_id_to_position(id1);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return moveSongInPlaylist(song, to);
}
static void orderPlaylist(void)
{
unsigned i;
if (playlist.current >= 0 && playlist.current < (int)playlist.length)
playlist.current = playlist.order[playlist.current];
if (playlist_state == PLAYLIST_STATE_PLAY && playlist.queued >= 0)
clearPlayerQueue();
for (i = 0; i < playlist.length; i++) {
playlist.order[i] = i;
}
return playlist->queue.repeat;
}
static void swapOrder(int a, int b)
bool
getPlaylistRandomStatus(const struct playlist *playlist)
{
int bak = playlist.order[a];
playlist.order[a] = playlist.order[b];
playlist.order[b] = bak;
return playlist->queue.random;
}
static void randomizeOrder(int start, int end)
void setPlaylistRepeatStatus(struct playlist *playlist, bool status)
{
int i;
int ri;
g_debug("playlist: randomize from %i to %i\n", start, end);
if (playlist_state == PLAYLIST_STATE_PLAY &&
playlist.queued >= start && playlist.queued <= end)
clearPlayerQueue();
for (i = start; i <= end; i++) {
ri = g_rand_int_range(g_rand, i, end + 1);
if (ri == playlist.current)
playlist.current = i;
else if (i == playlist.current)
playlist.current = ri;
swapOrder(i, ri);
}
}
void setPlaylistRandomStatus(bool status)
{
if (status == playlist.random)
if (status == playlist->queue.repeat)
return;
playlist.random = status;
if (playlist.random) {
/*if(playlist_state==PLAYLIST_STATE_PLAY) {
randomizeOrder(playlist.current+1,
playlist.length-1);
}
else */ randomizeOrder(0, playlist.length - 1);
if (playlist.current >= 0 &&
playlist.current < (int)playlist.length) {
swapOrder(playlist.current, 0);
playlist.current = 0;
}
} else
orderPlaylist();
playlist->queue.repeat = 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_get_queued_song(playlist));
idle_add(IDLE_OPTIONS);
}
void previousSongInPlaylist(void)
static void orderPlaylist(struct playlist *playlist)
{
static time_t lastTime;
time_t diff = time(NULL) - lastTime;
lastTime += diff;
if (playlist_state != PLAYLIST_STATE_PLAY)
return;
syncPlaylistWithQueue();
if (diff && getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) {
playPlaylistOrderNumber(playlist.current);
} else {
if (playlist.current > 0) {
playPlaylistOrderNumber(playlist.current - 1);
} else if (playlist.repeat) {
playPlaylistOrderNumber(playlist.length - 1);
} else {
playPlaylistOrderNumber(playlist.current);
}
}
}
if (playlist->current >= 0)
/* update playlist.current, order==position now */
playlist->current = queue_order_to_position(&playlist->queue,
playlist->current);
void shufflePlaylist(void)
{
unsigned i;
int ri;
if (playlist.length > 1) {
if (playlist_state == PLAYLIST_STATE_PLAY) {
if (playlist.queued >= 0)
clearPlayerQueue();
/* put current playing song first */
swapSongs(0, playlist.order[playlist.current]);
if (playlist.random) {
int j;
for (j = 0; 0 != playlist.order[j]; j++) ;
playlist.current = j;
} else
playlist.current = 0;
i = 1;
} else {
i = 0;
playlist.current = -1;
}
/* shuffle the rest of the list */
for (; i < playlist.length; i++) {
ri = g_rand_int_range(g_rand, i, playlist.length);
swapSongs(i, ri);
}
incrPlaylistVersion();
}
queue_restore_order(&playlist->queue);
}
enum playlist_result savePlaylist(const char *utf8file)
void setPlaylistRandomStatus(struct playlist *playlist, bool status)
{
FILE *fp;
char *path;
if (!is_valid_playlist_name(utf8file))
return PLAYLIST_RESULT_BAD_NAME;
path = map_spl_utf8_to_fs(utf8file);
if (g_file_test(path, G_FILE_TEST_EXISTS)) {
g_free(path);
return PLAYLIST_RESULT_LIST_EXISTS;
}
while (!(fp = fopen(path, "w")) && errno == EINTR);
g_free(path);
if (fp == NULL)
return PLAYLIST_RESULT_ERRNO;
for (unsigned i = 0; i < playlist.length; i++)
playlist_print_song(fp, playlist.songs[i]);
while (fclose(fp) && errno == EINTR) ;
const struct song *queued;
idle_add(IDLE_STORED_PLAYLIST);
return PLAYLIST_RESULT_SUCCESS;
}
int getPlaylistCurrentSong(void)
{
if (playlist.current >= 0 &&
playlist.current < (int)playlist.length) {
return playlist.order[playlist.current];
}
return -1;
}
unsigned long getPlaylistVersion(void)
{
return playlist.version;
}
if (status == playlist->queue.random)
return;
int getPlaylistLength(void)
{
return playlist.length;
}
queued = playlist_get_queued_song(playlist);
enum playlist_result seekSongInPlaylist(unsigned song, float seek_time)
{
unsigned i;
int ret;
playlist->queue.random = status;
if (song >= playlist.length)
return PLAYLIST_RESULT_BAD_RANGE;
if (playlist->queue.random) {
/* shuffle the queue order, but preserve
playlist->current */
if (playlist.random)
for (i = 0; song != playlist.order[i]; i++) ;
else
i = song;
int current_position =
playlist->playing && playlist->current >= 0
? (int)queue_order_to_position(&playlist->queue,
playlist->current)
: -1;
clearPlayerError();
playlist_stopOnError = 1;
playlist_errorCount = 0;
queue_shuffle_order(&playlist->queue);
if (playlist_state == PLAYLIST_STATE_PLAY) {
if (playlist.queued >= 0)
clearPlayerQueue();
if (current_position >= 0) {
/* make sure the current song is the first in
the order list, so the whole rest of the
playlist is played after that */
unsigned current_order =
queue_position_to_order(&playlist->queue,
current_position);
queue_swap_order(&playlist->queue, 0, current_order);
playlist->current = 0;
} else
playlist->current = -1;
} else
playPlaylistOrderNumber(i);
if (playlist.current != (int)i) {
playPlaylistOrderNumber(i);
}
ret = playerSeek(playlist.songs[playlist.order[i]], seek_time);
if (ret < 0)
return PLAYLIST_RESULT_NOT_PLAYING;
return PLAYLIST_RESULT_SUCCESS;
}
orderPlaylist(playlist);
enum playlist_result seekSongInPlaylistById(unsigned id, float seek_time)
{
int song = song_id_to_position(id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
playlist_update_queued_song(playlist, queued);
return seekSongInPlaylist(song, seek_time);
}
unsigned getPlaylistSongId(unsigned song)
{
return playlist.positionToId[song];
idle_add(IDLE_OPTIONS);
}
int PlaylistInfo(struct client *client, const char *utf8file, int detail)
int getPlaylistCurrentSong(const struct playlist *playlist)
{
GPtrArray *list;
if (!(list = spl_load(utf8file)))
return -1;
for (unsigned i = 0; i < list->len; ++i) {
const char *temp = g_ptr_array_index(list, i);
int wrote = 0;
if (detail) {
struct song *song = db_get_song(temp);
if (song) {
song_print_info(client, song);
wrote = 1;
}
}
if (!wrote) {
client_printf(client, SONG_FILE "%s\n", temp);
}
}
if (playlist->current >= 0)
return queue_order_to_position(&playlist->queue,
playlist->current);
spl_free(list);
return 0;
return -1;
}
enum playlist_result loadPlaylist(const char *utf8file)
int getPlaylistNextSong(const struct playlist *playlist)
{
GPtrArray *list;
if (!(list = spl_load(utf8file)))
return PLAYLIST_RESULT_NO_SUCH_LIST;
for (unsigned i = 0; i < list->len; ++i) {
const char *temp = g_ptr_array_index(list, i);
if ((addToPlaylist(temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
/* for windows compatibility, convert slashes */
char *temp2 = g_strdup(temp);
char *p = temp2;
while (*p) {
if (*p == '\\')
*p = '/';
p++;
}
if ((addToPlaylist(temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
g_warning("can't add file \"%s\"", temp2);
}
free(temp2);
}
if (playlist->current >= 0)
{
if (playlist->current + 1 < (int)queue_length(&playlist->queue))
return queue_order_to_position(&playlist->queue,
playlist->current + 1);
else if (playlist->queue.repeat == 1)
return queue_order_to_position(&playlist->queue, 0);
}
spl_free(list);
return PLAYLIST_RESULT_SUCCESS;
return -1;
}
void searchForSongsInPlaylist(struct client *client,
unsigned numItems, LocateTagItem * items)
unsigned long
getPlaylistVersion(const struct playlist *playlist)
{
unsigned i;
char **originalNeedles = g_malloc(numItems * sizeof(char *));
for (i = 0; i < numItems; i++) {
originalNeedles[i] = items[i].needle;
items[i].needle = g_utf8_casefold(originalNeedles[i], -1);
}
for (i = 0; i < playlist.length; i++) {
if (strstrSearchTags(playlist.songs[i], numItems, items))
printPlaylistSongInfo(client, i);
}
for (i = 0; i < numItems; i++) {
g_free(items[i].needle);
items[i].needle = originalNeedles[i];
}
free(originalNeedles);
return playlist->queue.version;
}
void findSongsInPlaylist(struct client *client,
unsigned numItems, LocateTagItem * items)
int
getPlaylistLength(const struct playlist *playlist)
{
for (unsigned i = 0; i < playlist.length; i++) {
if (tagItemsFoundAndMatches(playlist.songs[i], numItems, items))
printPlaylistSongInfo(client, i);
}
return queue_length(&playlist->queue);
}
/*
* Not supporting '/' was done out of laziness, and we should really
* strive to support it in the future.
*
* Not supporting '\r' and '\n' is done out of protocol limitations (and
* arguably laziness), but bending over head over heels to modify the
* protocol (and compatibility with all clients) to support idiots who
* put '\r' and '\n' in filenames isn't going to happen, either.
*/
int is_valid_playlist_name(const char *utf8path)
unsigned
getPlaylistSongId(const struct playlist *playlist, unsigned song)
{
return strchr(utf8path, '/') == NULL &&
strchr(utf8path, '\n') == NULL &&
strchr(utf8path, '\r') == NULL;
return queue_position_to_id(&playlist->queue, song);
}
......@@ -19,15 +19,13 @@
#ifndef MPD_PLAYLIST_H
#define MPD_PLAYLIST_H
#include "locate.h"
#include "queue.h"
#include <stdbool.h>
#include <stdio.h>
#define PLAYLIST_COMMENT '#'
struct client;
enum playlist_result {
PLAYLIST_RESULT_SUCCESS,
PLAYLIST_RESULT_ERRNO,
......@@ -38,42 +36,83 @@ enum playlist_result {
PLAYLIST_RESULT_BAD_NAME,
PLAYLIST_RESULT_BAD_RANGE,
PLAYLIST_RESULT_NOT_PLAYING,
PLAYLIST_RESULT_TOO_LARGE
PLAYLIST_RESULT_TOO_LARGE,
PLAYLIST_RESULT_DISABLED,
};
typedef struct _Playlist {
struct song **songs;
/* holds version a song was modified on */
uint32_t *songMod;
unsigned *order;
unsigned *positionToId;
int *idToPosition;
unsigned length;
struct playlist {
/**
* The song queue - it contains the "real" playlist.
*/
struct queue queue;
/**
* This value is true if the player is currently playing (or
* should be playing).
*/
bool playing;
/**
* If true, then any error is fatal; if false, MPD will
* attempt to play the next song on non-fatal errors. During
* seeking, this flag is set.
*/
bool stop_on_error;
/**
* Number of errors since playback was started. If this
* number exceeds the length of the playlist, MPD gives up,
* because all songs have been tried.
*/
unsigned error_count;
/**
* The "current song pointer". This is the song which is
* played when we get the "play" command. It is also the song
* which is currently being played.
*/
int current;
int queued;
bool repeat;
bool random;
uint32_t version;
/** used by syncCurrentPlayerDecodeMetadata() to detect tag changes */
const struct song *prev_song;
/** used by syncCurrentPlayerDecodeMetadata() to detect tag changes */
const struct tag *prev_tag;
} Playlist;
extern bool playlist_saveAbsolutePaths;
/**
* The "next" song to be played, when the current one
* finishes. The decoder thread may start decoding and
* buffering it, while the "current" song is still playing.
*
* This variable is only valid if #playing is true.
*/
int queued;
};
extern unsigned playlist_max_length;
/** the global playlist object */
extern struct playlist g_playlist;
void initPlaylist(void);
void finishPlaylist(void);
void
playlist_init(struct playlist *playlist);
void
playlist_finish(struct playlist *playlist);
void
playlist_tag_changed(struct playlist *playlist);
/**
* Returns the "queue" object of the global playlist instance.
*/
static inline const struct queue *
playlist_get_queue(const struct playlist *playlist)
{
return &playlist->queue;
}
void readPlaylistState(FILE *);
void savePlaylistState(FILE *);
void clearPlaylist(void);
void clearPlaylist(struct playlist *playlist);
#ifndef WIN32
/**
......@@ -81,97 +120,84 @@ void clearPlaylist(void);
* but only if the file's owner is equal to the specified uid.
*/
enum playlist_result
playlist_append_file(const char *path, int uid, unsigned *added_id);
playlist_append_file(struct playlist *playlist, const char *path, int uid,
unsigned *added_id);
#endif
enum playlist_result addToPlaylist(const char *file, unsigned *added_id);
enum playlist_result
addSongToPlaylist(struct song *song, unsigned *added_id);
addToPlaylist(struct playlist *playlist, const char *file, unsigned *added_id);
void showPlaylist(struct client *client);
enum playlist_result deleteFromPlaylist(unsigned song);
enum playlist_result deleteFromPlaylistById(unsigned song);
/**
* Send detailed information about a range of songs in the playlist to
* a client.
*
* @param client the client which has requested information
* @param start the index of the first song (including)
* @param end the index of the last song (excluding)
*/
enum playlist_result
playlistInfo(struct client *client, unsigned start, unsigned end);
addSongToPlaylist(struct playlist *playlist,
struct song *song, unsigned *added_id);
enum playlist_result playlistId(struct client *client, int song);
enum playlist_result
deleteFromPlaylist(struct playlist *playlist, unsigned song);
void stopPlaylist(void);
enum playlist_result
deleteFromPlaylistById(struct playlist *playlist, unsigned song);
enum playlist_result playPlaylist(int song, int stopOnError);
void stopPlaylist(struct playlist *playlist);
enum playlist_result playPlaylistById(int song, int stopOnError);
enum playlist_result
playPlaylist(struct playlist *playlist, int song);
void nextSongInPlaylist(void);
enum playlist_result
playPlaylistById(struct playlist *playlist, int song);
void syncPlayerAndPlaylist(void);
void nextSongInPlaylist(struct playlist *playlist);
void previousSongInPlaylist(void);
void syncPlayerAndPlaylist(struct playlist *playlist);
void shufflePlaylist(void);
void previousSongInPlaylist(struct playlist *playlist);
enum playlist_result savePlaylist(const char *utf8file);
void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end);
void
deleteASongFromPlaylist(const struct song *song);
enum playlist_result moveSongInPlaylist(unsigned from, int to);
enum playlist_result moveSongInPlaylistById(unsigned id, int to);
enum playlist_result swapSongsInPlaylist(unsigned song1, unsigned song2);
deleteASongFromPlaylist(struct playlist *playlist, const struct song *song);
enum playlist_result swapSongsInPlaylistById(unsigned id1, unsigned id2);
enum playlist_result loadPlaylist(const char *utf8file);
bool getPlaylistRepeatStatus(void);
void setPlaylistRepeatStatus(bool status);
enum playlist_result
moveSongInPlaylist(struct playlist *playlist, unsigned from, int to);
bool getPlaylistRandomStatus(void);
enum playlist_result
moveSongInPlaylistById(struct playlist *playlist, unsigned id, int to);
void setPlaylistRandomStatus(bool status);
enum playlist_result
swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2);
int getPlaylistCurrentSong(void);
enum playlist_result
swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2);
unsigned getPlaylistSongId(unsigned song);
bool
getPlaylistRepeatStatus(const struct playlist *playlist);
int getPlaylistLength(void);
void setPlaylistRepeatStatus(struct playlist *playlist, bool status);
unsigned long getPlaylistVersion(void);
bool
getPlaylistRandomStatus(const struct playlist *playlist);
void playPlaylistIfPlayerStopped(void);
void setPlaylistRandomStatus(struct playlist *playlist, bool status);
enum playlist_result seekSongInPlaylist(unsigned song, float seek_time);
int getPlaylistCurrentSong(const struct playlist *playlist);
enum playlist_result seekSongInPlaylistById(unsigned id, float seek_time);
int getPlaylistNextSong(const struct playlist *playlist);
void playlistVersionChange(void);
unsigned
getPlaylistSongId(const struct playlist *playlist, unsigned song);
int playlistChanges(struct client *client, uint32_t version);
int getPlaylistLength(const struct playlist *playlist);
int playlistChangesPosId(struct client *client, uint32_t version);
unsigned long
getPlaylistVersion(const struct playlist *playlist);
int PlaylistInfo(struct client *client, const char *utf8file, int detail);
enum playlist_result
seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time);
void searchForSongsInPlaylist(struct client *client,
unsigned numItems, LocateTagItem * items);
enum playlist_result
seekSongInPlaylistById(struct playlist *playlist,
unsigned id, float seek_time);
void findSongsInPlaylist(struct client *client,
unsigned numItems, LocateTagItem * items);
void playlistVersionChange(struct playlist *playlist);
int is_valid_playlist_name(const char *utf8path);
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Functions for controlling playback on the playlist level.
*
*/
#include "playlist_internal.h"
#include "player_control.h"
#include <glib.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"
enum {
/**
* When the "prev" command is received, rewind the current
* track if this number of seconds has already elapsed.
*/
PLAYLIST_PREV_UNLESS_ELAPSED = 10,
};
void stopPlaylist(struct playlist *playlist)
{
if (!playlist->playing)
return;
assert(playlist->current >= 0);
g_debug("stop");
playerWait();
playlist->queued = -1;
playlist->playing = false;
if (playlist->queue.random) {
/* shuffle the playlist, so the next playback will
result in a new random order */
unsigned current_position =
queue_order_to_position(&playlist->queue,
playlist->current);
queue_shuffle_order(&playlist->queue);
/* make sure that "current" stays valid, and the next
"play" command plays the same song again */
playlist->current =
queue_position_to_order(&playlist->queue,
current_position);
}
}
enum playlist_result playPlaylist(struct playlist *playlist, int song)
{
unsigned i = song;
clearPlayerError();
if (song == -1) {
/* play any song ("current" song, or the first song */
if (queue_is_empty(&playlist->queue))
return PLAYLIST_RESULT_SUCCESS;
if (playlist->playing) {
/* already playing: unpause playback, just in
case it was paused, and return */
playerSetPause(0);
return PLAYLIST_RESULT_SUCCESS;
}
/* select a song: "current" song, or the first one */
i = playlist->current >= 0
? playlist->current
: 0;
} else if (!queue_valid_position(&playlist->queue, song))
return PLAYLIST_RESULT_BAD_RANGE;
if (playlist->queue.random) {
if (song >= 0)
/* "i" is currently the song position (which
would be equal to the order number in
no-random mode); convert it to a order
number, because random mode is enabled */
i = queue_position_to_order(&playlist->queue, song);
if (!playlist->playing)
playlist->current = 0;
/* swap the new song with the previous "current" one,
so playback continues as planned */
queue_swap_order(&playlist->queue,
i, playlist->current);
i = playlist->current;
}
playlist->stop_on_error = false;
playlist->error_count = 0;
playPlaylistOrderNumber(playlist, i);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
playPlaylistById(struct playlist *playlist, int id)
{
int song;
if (id == -1) {
return playPlaylist(playlist, id);
}
song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return playPlaylist(playlist, song);
}
void
nextSongInPlaylist(struct playlist *playlist)
{
int next_order;
if (!playlist->playing)
return;
assert(!queue_is_empty(&playlist->queue));
assert(queue_valid_order(&playlist->queue, playlist->current));
playlist->stop_on_error = false;
/* determine the next song from the queue's order list */
next_order = queue_next_order(&playlist->queue, playlist->current);
if (next_order < 0) {
/* no song after this one: stop playback */
stopPlaylist(playlist);
return;
}
if (next_order == 0 && playlist->queue.random) {
/* The queue told us that the next song is the first
song. This means we are in repeat mode. Shuffle
the queue order, so this time, the user hears the
songs in a different than before */
assert(playlist->queue.repeat);
queue_shuffle_order(&playlist->queue);
/* note that playlist->current and playlist->queued are
now invalid, but playPlaylistOrderNumber() will
discard them anyway */
}
playPlaylistOrderNumber(playlist, next_order);
}
void previousSongInPlaylist(struct playlist *playlist)
{
static time_t lastTime;
time_t diff = time(NULL) - lastTime;
lastTime += diff;
if (!playlist->playing)
return;
if (diff && getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) {
/* re-start playing the current song (just like the
"prev" button on CD players) */
playPlaylistOrderNumber(playlist, playlist->current);
} else {
if (playlist->current > 0) {
/* play the preceding song */
playPlaylistOrderNumber(playlist,
playlist->current - 1);
} else if (playlist->queue.repeat) {
/* play the last song in "repeat" mode */
playPlaylistOrderNumber(playlist,
queue_length(&playlist->queue) - 1);
} else {
/* re-start playing the current song if it's
the first one */
playPlaylistOrderNumber(playlist, playlist->current);
}
}
}
enum playlist_result
seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
{
const struct song *queued;
unsigned i;
int ret;
if (!queue_valid_position(&playlist->queue, song))
return PLAYLIST_RESULT_BAD_RANGE;
queued = playlist_get_queued_song(playlist);
if (playlist->queue.random)
i = queue_position_to_order(&playlist->queue, song);
else
i = song;
clearPlayerError();
playlist->stop_on_error = true;
playlist->error_count = 0;
if (!playlist->playing || (unsigned)playlist->current != i) {
/* seeking is not within the current song - first
start playing the new song */
playPlaylistOrderNumber(playlist, i);
queued = NULL;
}
ret = playerSeek(queue_get_order(&playlist->queue, i), seek_time);
if (ret < 0) {
playlist->queued = -1;
playlist_update_queued_song(playlist, NULL);
return PLAYLIST_RESULT_NOT_PLAYING;
}
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
seekSongInPlaylistById(struct playlist *playlist, unsigned id, float seek_time)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return seekSongInPlaylist(playlist, song, seek_time);
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Functions for editing the playlist (adding, removing, reordering
* songs in the queue).
*
*/
#include "playlist_internal.h"
#include "player_control.h"
#include "database.h"
#include "ls.h"
#include "song.h"
#include "idle.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
static void incrPlaylistVersion(struct playlist *playlist)
{
queue_increment_version(&playlist->queue);
idle_add(IDLE_PLAYLIST);
}
void clearPlaylist(struct playlist *playlist)
{
stopPlaylist(playlist);
/* 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);
}
queue_clear(&playlist->queue);
playlist->current = -1;
incrPlaylistVersion(playlist);
}
#ifndef WIN32
enum playlist_result
playlist_append_file(struct playlist *playlist, const char *path, int uid,
unsigned *added_id)
{
int ret;
struct stat st;
struct song *song;
if (uid <= 0)
/* unauthenticated client */
return PLAYLIST_RESULT_DENIED;
ret = stat(path, &st);
if (ret < 0)
return PLAYLIST_RESULT_ERRNO;
if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444)
/* client is not owner */
return PLAYLIST_RESULT_DENIED;
song = song_file_load(path, NULL);
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return addSongToPlaylist(playlist, song, added_id);
}
#endif
static struct song *
song_by_url(const char *url)
{
struct song *song;
song = db_get_song(url);
if (song != NULL)
return song;
if (uri_has_scheme(url))
return song_remote_new(url);
return NULL;
}
enum playlist_result
addToPlaylist(struct playlist *playlist, const char *url, unsigned *added_id)
{
struct song *song;
g_debug("add to playlist: %s", url);
song = song_by_url(url);
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return addSongToPlaylist(playlist, song, added_id);
}
enum playlist_result
addSongToPlaylist(struct playlist *playlist,
struct song *song, unsigned *added_id)
{
const struct song *queued;
unsigned id;
if (queue_is_full(&playlist->queue))
return PLAYLIST_RESULT_TOO_LARGE;
queued = playlist_get_queued_song(playlist);
id = queue_append(&playlist->queue, song);
if (playlist->queue.random) {
/* shuffle the new song into the list of remaining
songs to play */
unsigned start;
if (playlist->queued >= 0)
start = playlist->queued + 1;
else
start = playlist->current + 1;
if (start < queue_length(&playlist->queue))
queue_shuffle_order_last(&playlist->queue, start,
queue_length(&playlist->queue));
}
incrPlaylistVersion(playlist);
playlist_update_queued_song(playlist, queued);
if (added_id)
*added_id = id;
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
{
const struct song *queued;
if (!queue_valid_position(&playlist->queue, song1) ||
!queue_valid_position(&playlist->queue, song2))
return PLAYLIST_RESULT_BAD_RANGE;
queued = playlist_get_queued_song(playlist);
queue_swap(&playlist->queue, song1, song2);
if (playlist->queue.random) {
/* update the queue order, so that playlist->current
still points to the current song order */
queue_swap_order(&playlist->queue,
queue_position_to_order(&playlist->queue,
song1),
queue_position_to_order(&playlist->queue,
song2));
} else {
/* correct the "current" song order */
if (playlist->current == (int)song1)
playlist->current = song2;
else if (playlist->current == (int)song2)
playlist->current = song1;
}
incrPlaylistVersion(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2)
{
int song1 = queue_id_to_position(&playlist->queue, id1);
int song2 = queue_id_to_position(&playlist->queue, id2);
if (song1 < 0 || song2 < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return swapSongsInPlaylist(playlist, song1, song2);
}
enum playlist_result
deleteFromPlaylist(struct playlist *playlist, unsigned song)
{
const struct song *queued;
unsigned songOrder;
if (song >= queue_length(&playlist->queue))
return PLAYLIST_RESULT_BAD_RANGE;
queued = playlist_get_queued_song(playlist);
songOrder = queue_position_to_order(&playlist->queue, song);
if (playlist->playing && playlist->current == (int)songOrder) {
bool paused = getPlayerState() == PLAYER_STATE_PAUSE;
/* the current song is going to be deleted: stop the player */
playerWait();
playlist->playing = false;
/* see which song is going to be played instead */
playlist->current = queue_next_order(&playlist->queue,
playlist->current);
if (playlist->current == (int)songOrder)
playlist->current = -1;
if (playlist->current >= 0 && !paused)
/* play the song after the deleted one */
playPlaylistOrderNumber(playlist, playlist->current);
else
/* no songs left to play, stop playback
completely */
stopPlaylist(playlist);
queued = NULL;
} else if (playlist->current == (int)songOrder)
/* there's a "current song" but we're not playing
currently - clear "current" */
playlist->current = -1;
/* now do it: remove the song */
if (!song_in_database(queue_get(&playlist->queue, song)))
pc_song_deleted(queue_get(&playlist->queue, song));
queue_delete(&playlist->queue, song);
incrPlaylistVersion(playlist);
/* update the "current" and "queued" variables */
if (playlist->current > (int)songOrder) {
playlist->current--;
}
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
deleteFromPlaylistById(struct playlist *playlist, unsigned id)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return deleteFromPlaylist(playlist, song);
}
void
deleteASongFromPlaylist(struct playlist *playlist, const struct song *song)
{
for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i)
if (song == queue_get(&playlist->queue, i))
deleteFromPlaylist(playlist, i);
pc_song_deleted(song);
}
enum playlist_result
moveSongInPlaylist(struct playlist *playlist, unsigned from, int to)
{
const struct song *queued;
int currentSong;
if (!queue_valid_position(&playlist->queue, from))
return PLAYLIST_RESULT_BAD_RANGE;
if ((to >= 0 && to >= (int)queue_length(&playlist->queue)) ||
(to < 0 && abs(to) > (int)queue_length(&playlist->queue)))
return PLAYLIST_RESULT_BAD_RANGE;
if ((int)from == to) /* no-op */
return PLAYLIST_RESULT_SUCCESS;
queued = playlist_get_queued_song(playlist);
/*
* (to < 0) => move to offset from current song
* (-playlist.length == to) => move to position BEFORE current song
*/
currentSong = playlist->current >= 0
? (int)queue_order_to_position(&playlist->queue,
playlist->current)
: -1;
if (to < 0 && playlist->current >= 0) {
if ((unsigned)currentSong == from)
/* no-op, can't be moved to offset of itself */
return PLAYLIST_RESULT_SUCCESS;
to = (currentSong + abs(to)) % queue_length(&playlist->queue);
}
queue_move(&playlist->queue, from, to);
if (!playlist->queue.random) {
/* update current/queued */
if (playlist->current == (int)from)
playlist->current = to;
else if (playlist->current > (int)from &&
playlist->current <= to) {
playlist->current--;
} else if (playlist->current >= to &&
playlist->current < (int)from) {
playlist->current++;
}
}
incrPlaylistVersion(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
moveSongInPlaylistById(struct playlist *playlist, unsigned id1, int to)
{
int song = queue_id_to_position(&playlist->queue, id1);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return moveSongInPlaylist(playlist, song, to);
}
void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end)
{
const struct song *queued;
if (end > queue_length(&playlist->queue))
/* correct the "end" offset */
end = queue_length(&playlist->queue);
if ((start+1) >= end)
/* needs at least two entries. */
return;
queued = playlist_get_queued_song(playlist);
if (playlist->playing && playlist->current >= 0) {
unsigned current_position;
current_position = queue_order_to_position(&playlist->queue,
playlist->current);
if (current_position >= start && current_position < end)
{
/* put current playing song first */
queue_swap(&playlist->queue, start, current_position);
if (playlist->queue.random) {
playlist->current =
queue_position_to_order(&playlist->queue, start);
} else
playlist->current = start;
/* start shuffle after the current song */
start++;
}
} else {
/* no playback currently: reset playlist->current */
playlist->current = -1;
}
queue_shuffle_range(&playlist->queue, start, end);
incrPlaylistVersion(playlist);
playlist_update_queued_song(playlist, queued);
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* The manager of the global "struct playlist" instance (g_playlist).
*
*/
#include "playlist.h"
#include "playlist_state.h"
#include "event_pipe.h"
struct playlist g_playlist;
static void
playlist_tag_event(void)
{
playlist_tag_changed(&g_playlist);
}
static void
playlist_event(void)
{
syncPlayerAndPlaylist(&g_playlist);
}
void initPlaylist(void)
{
playlist_init(&g_playlist);
event_pipe_register(PIPE_EVENT_TAG, playlist_tag_event);
event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event);
}
void finishPlaylist(void)
{
playlist_finish(&g_playlist);
}
void savePlaylistState(FILE *fp)
{
playlist_state_save(fp, &g_playlist);
}
void readPlaylistState(FILE *fp)
{
playlist_state_restore(fp, &g_playlist);
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Internal header for the components of the playlist code.
*
*/
#ifndef PLAYLIST_INTERNAL_H
#define PLAYLIST_INTERNAL_H
#include "playlist.h"
/**
* Returns the song object which is currently queued. Returns none if
* there is none (yet?) or if MPD isn't playing.
*/
const struct song *
playlist_get_queued_song(struct playlist *playlist);
/**
* Updates the "queued song". Calculates the next song according to
* the current one (if MPD isn't playing, it takes the first song),
* and queues this song. Clears the old queued song if there was one.
*
* @param prev the song which was previously queued, as determined by
* playlist_get_queued_song()
*/
void
playlist_update_queued_song(struct playlist *playlist,
const struct song *prev);
void
playPlaylistOrderNumber(struct playlist *playlist, int orderNum);
#endif
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "playlist_print.h"
#include "queue_print.h"
#include "stored_playlist.h"
#include "song_print.h"
#include "song.h"
#include "database.h"
#include "client.h"
void
playlist_print_uris(struct client *client, const struct playlist *playlist)
{
const struct queue *queue = &playlist->queue;
queue_print_uris(client, queue, 0, queue_length(queue));
}
bool
playlist_print_info(struct client *client, const struct playlist *playlist,
unsigned start, unsigned end)
{
const struct queue *queue = &playlist->queue;
if (end > queue_length(queue))
/* correct the "end" offset */
end = queue_length(queue);
if (start > end)
/* an invalid "start" offset is fatal */
return false;
queue_print_info(client, queue, start, end);
return true;
}
bool
playlist_print_id(struct client *client, const struct playlist *playlist,
unsigned id)
{
const struct queue *queue = &playlist->queue;
int position;
position = queue_id_to_position(queue, id);
if (position < 0)
/* no such song */
return false;
return playlist_print_info(client, playlist, position, position + 1);
}
bool
playlist_print_current(struct client *client, const struct playlist *playlist)
{
int current_position = getPlaylistCurrentSong(playlist);
if (current_position < 0)
return false;
queue_print_info(client, &playlist->queue,
current_position, current_position + 1);
return true;
}
void
playlist_print_find(struct client *client, const struct playlist *playlist,
const struct locate_item_list *list)
{
queue_find(client, &playlist->queue, list);
}
void
playlist_print_search(struct client *client, const struct playlist *playlist,
const struct locate_item_list *list)
{
queue_search(client, &playlist->queue, list);
}
void
playlist_print_changes_info(struct client *client,
const struct playlist *playlist,
uint32_t version)
{
queue_print_changes_info(client, &playlist->queue, version);
}
void
playlist_print_changes_position(struct client *client,
const struct playlist *playlist,
uint32_t version)
{
queue_print_changes_position(client, &playlist->queue, version);
}
bool
spl_print(struct client *client, const char *name_utf8, bool detail)
{
GPtrArray *list;
list = spl_load(name_utf8);
if (list == NULL)
return false;
for (unsigned i = 0; i < list->len; ++i) {
const char *temp = g_ptr_array_index(list, i);
bool wrote = false;
if (detail) {
struct song *song = db_get_song(temp);
if (song) {
song_print_info(client, song);
wrote = true;
}
}
if (!wrote) {
client_printf(client, SONG_FILE "%s\n", temp);
}
}
spl_free(list);
return true;
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef PLAYLIST_PRINT_H
#define PLAYLIST_PRINT_H
#include <stdbool.h>
#include <stdint.h>
struct client;
struct playlist;
struct locate_item_list;
/**
* Sends the whole playlist to the client, song URIs only.
*/
void
playlist_print_uris(struct client *client, const struct playlist *playlist);
/**
* Sends a range of the playlist to the client, including all known
* information about the songs. The "end" offset is decreased
* automatically if it is too large; passing UINT_MAX is allowed.
* This function however fails when the start offset is invalid.
*/
bool
playlist_print_info(struct client *client, const struct playlist *playlist,
unsigned start, unsigned end);
/**
* Sends the song with the specified id to the client.
*
* @return true on suite, false if there is no such song
*/
bool
playlist_print_id(struct client *client, const struct playlist *playlist,
unsigned id);
/**
* Sends the current song to the client.
*
* @return true on success, false if there is no current song
*/
bool
playlist_print_current(struct client *client, const struct playlist *playlist);
/**
* Find songs in the playlist.
*/
void
playlist_print_find(struct client *client, const struct playlist *playlist,
const struct locate_item_list *list);
/**
* Search for songs in the playlist.
*/
void
playlist_print_search(struct client *client, const struct playlist *playlist,
const struct locate_item_list *list);
/**
* Print detailed changes since the specified playlist version.
*/
void
playlist_print_changes_info(struct client *client,
const struct playlist *playlist,
uint32_t version);
/**
* Print changes since the specified playlist version, position only.
*/
void
playlist_print_changes_position(struct client *client,
const struct playlist *playlist,
uint32_t version);
/**
* Send the stored playlist to the client.
*
* @param client the client which requested the playlist
* @param name_utf8 the name of the stored playlist in UTF-8 encoding
* @param detail true if all details should be printed
* @return true on success, false if the playlist does not exist
*/
bool
spl_print(struct client *client, const char *name_utf8, bool detail);
#endif
......@@ -17,12 +17,13 @@
*/
#include "playlist_save.h"
#include "playlist.h"
#include "stored_playlist.h"
#include "song.h"
#include "mapper.h"
#include "path.h"
#include "ls.h"
#include "database.h"
#include "idle.h"
#include <glib.h>
......@@ -62,3 +63,73 @@ playlist_print_uri(FILE *file, const char *uri)
g_free(s);
}
}
enum playlist_result
spl_save_queue(const char *name_utf8, const struct queue *queue)
{
char *path_fs;
FILE *file;
if (!spl_valid_name(name_utf8))
return PLAYLIST_RESULT_BAD_NAME;
path_fs = map_spl_utf8_to_fs(name_utf8);
if (path_fs == NULL)
return PLAYLIST_RESULT_DISABLED;
if (g_file_test(path_fs, G_FILE_TEST_EXISTS)) {
g_free(path_fs);
return PLAYLIST_RESULT_LIST_EXISTS;
}
file = fopen(path_fs, "w");
g_free(path_fs);
if (file == NULL)
return PLAYLIST_RESULT_ERRNO;
for (unsigned i = 0; i < queue_length(queue); i++)
playlist_print_song(file, queue_get(queue, i));
fclose(file);
idle_add(IDLE_STORED_PLAYLIST);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
spl_save_playlist(const char *name_utf8, const struct playlist *playlist)
{
return spl_save_queue(name_utf8, &playlist->queue);
}
enum playlist_result
playlist_load_spl(struct playlist *playlist, const char *name_utf8)
{
GPtrArray *list;
list = spl_load(name_utf8);
if (list == NULL)
return PLAYLIST_RESULT_NO_SUCH_LIST;
for (unsigned i = 0; i < list->len; ++i) {
const char *temp = g_ptr_array_index(list, i);
if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
/* for windows compatibility, convert slashes */
char *temp2 = g_strdup(temp);
char *p = temp2;
while (*p) {
if (*p == '\\')
*p = '/';
p++;
}
if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
g_warning("can't add file \"%s\"", temp2);
}
g_free(temp2);
}
}
spl_free(list);
return PLAYLIST_RESULT_SUCCESS;
}
......@@ -19,6 +19,8 @@
#ifndef MPD_PLAYLIST_SAVE_H
#define MPD_PLAYLIST_SAVE_H
#include "playlist.h"
#include <stdio.h>
struct song;
......@@ -29,4 +31,23 @@ playlist_print_song(FILE *fp, const struct song *song);
void
playlist_print_uri(FILE *fp, const char *uri);
/**
* Saves a queue object into a stored playlist file.
*/
enum playlist_result
spl_save_queue(const char *name_utf8, const struct queue *queue);
/**
* Saves a playlist object into a stored playlist file.
*/
enum playlist_result
spl_save_playlist(const char *name_utf8, const struct playlist *playlist);
/**
* Loads a stored playlist file, and append all songs to the global
* playlist.
*/
enum playlist_result
playlist_load_spl(struct playlist *playlist, const char *name_utf8);
#endif
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Saving and loading the playlist to/from the state file.
*
*/
#include "playlist_state.h"
#include "playlist.h"
#include "player_control.h"
#include "queue_save.h"
#include "path.h"
#include <string.h>
#include <stdlib.h>
#define PLAYLIST_STATE_FILE_STATE "state: "
#define PLAYLIST_STATE_FILE_RANDOM "random: "
#define PLAYLIST_STATE_FILE_REPEAT "repeat: "
#define PLAYLIST_STATE_FILE_CURRENT "current: "
#define PLAYLIST_STATE_FILE_TIME "time: "
#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: "
#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin"
#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end"
#define PLAYLIST_STATE_FILE_STATE_PLAY "play"
#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause"
#define PLAYLIST_STATE_FILE_STATE_STOP "stop"
#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX
void
playlist_state_save(FILE *fp, const struct playlist *playlist)
{
fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE);
if (playlist->playing) {
switch (getPlayerState()) {
case PLAYER_STATE_PAUSE:
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE);
break;
default:
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PLAY);
}
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT,
queue_order_to_position(&playlist->queue,
playlist->current));
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME,
getPlayerElapsedTime());
} else
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM,
playlist->queue.random);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT,
playlist->queue.repeat);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE,
(int)(getPlayerCrossFade()));
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN);
queue_save(fp, &playlist->queue);
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END);
}
static void
playlist_state_load(FILE *fp, struct playlist *playlist,
char *buffer,
int state, int current, int seek_time)
{
int song;
if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) {
g_warning("No playlist in state file");
return;
}
while (!g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
g_strchomp(buffer);
song = queue_load_song(&playlist->queue, buffer);
if (song >= 0 && song == current) {
if (state != PLAYER_STATE_STOP) {
playPlaylist(playlist, queue_length(&playlist->queue) - 1);
}
if (state == PLAYER_STATE_PAUSE) {
playerPause();
}
if (state != PLAYER_STATE_STOP) {
seekSongInPlaylist(playlist,
queue_length(&playlist->queue) - 1,
seek_time);
}
}
if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) {
g_warning("'%s' not found in state file",
PLAYLIST_STATE_FILE_PLAYLIST_END);
break;
}
}
queue_increment_version(&playlist->queue);
}
void
playlist_state_restore(FILE *fp, struct playlist *playlist)
{
int current = -1;
int seek_time = 0;
int state = PLAYER_STATE_STOP;
char buffer[PLAYLIST_BUFFER_SIZE];
bool random_mode = false;
while (fgets(buffer, sizeof(buffer), fp)) {
g_strchomp(buffer);
if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_STATE)) {
if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
PLAYLIST_STATE_FILE_STATE_PLAY) == 0) {
state = PLAYER_STATE_PLAY;
} else
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
PLAYLIST_STATE_FILE_STATE_PAUSE)
== 0) {
state = PLAYER_STATE_PAUSE;
}
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) {
seek_time =
atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)]));
} else
if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_REPEAT)) {
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
"1") == 0) {
setPlaylistRepeatStatus(playlist, true);
} else
setPlaylistRepeatStatus(playlist, false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CROSSFADE)) {
setPlayerCrossFade(atoi
(&
(buffer
[strlen
(PLAYLIST_STATE_FILE_CROSSFADE)])));
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_RANDOM)) {
random_mode =
strcmp(buffer + strlen(PLAYLIST_STATE_FILE_RANDOM),
"1") == 0;
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CURRENT)) {
current = atoi(&(buffer
[strlen
(PLAYLIST_STATE_FILE_CURRENT)]));
} else if (g_str_has_prefix(buffer,
PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
if (state == PLAYER_STATE_STOP)
current = -1;
playlist_state_load(fp, playlist, buffer, state,
current, seek_time);
}
}
setPlaylistRandomStatus(playlist, random_mode);
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Saving and loading the playlist to/from the state file.
*
*/
#ifndef PLAYLIST_STATE_H
#define PLAYLIST_STATE_H
#include <stdio.h>
struct playlist;
void
playlist_state_save(FILE *fp, const struct playlist *playlist);
void
playlist_state_restore(FILE *fp, struct playlist *playlist);
#endif
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "queue.h"
#include "song.h"
unsigned
queue_generate_id(const struct queue *queue)
{
static unsigned cur = (unsigned)-1;
do {
cur++;
if (cur >= queue->max_length * QUEUE_HASH_MULT)
cur = 0;
} while (queue->idToPosition[cur] != -1);
return cur;
}
int
queue_next_order(const struct queue *queue, unsigned order)
{
assert(order < queue->length);
if (order + 1 < queue->length)
return order + 1;
else if (queue->repeat)
/* restart at first song */
return 0;
else
/* end of queue */
return -1;
}
void
queue_increment_version(struct queue *queue)
{
static unsigned long max = ((uint32_t) 1 << 31) - 1;
queue->version++;
if (queue->version >= max) {
for (unsigned i = 0; i < queue->length; i++)
queue->items[i].version = 0;
queue->version = 1;
}
}
void
queue_modify(struct queue *queue, unsigned order)
{
unsigned position;
assert(order < queue->length);
position = queue->order[order];
queue->items[position].version = queue->version;
queue_increment_version(queue);
}
void
queue_modify_all(struct queue *queue)
{
for (unsigned i = 0; i < queue->length; i++)
queue->items[i].version = queue->version;
queue_increment_version(queue);
}
unsigned
queue_append(struct queue *queue, struct song *song)
{
unsigned id = queue_generate_id(queue);
assert(!queue_is_full(queue));
queue->items[queue->length] = (struct queue_item){
.song = song,
.id = id,
.version = queue->version,
};
queue->order[queue->length] = queue->length;
queue->idToPosition[id] = queue->length;
++queue->length;
return id;
}
void
queue_swap(struct queue *queue, unsigned position1, unsigned position2)
{
struct queue_item tmp;
unsigned id1 = queue->items[position1].id;
unsigned id2 = queue->items[position2].id;
tmp = queue->items[position1];
queue->items[position1] = queue->items[position2];
queue->items[position2] = tmp;
queue->items[position1].version = queue->version;
queue->items[position2].version = queue->version;
queue->idToPosition[id1] = position2;
queue->idToPosition[id2] = position1;
}
static void
queue_move_song_to(struct queue *queue, unsigned from, unsigned to)
{
unsigned from_id = queue->items[from].id;
queue->items[to] = queue->items[from];
queue->items[to].version = queue->version;
queue->idToPosition[from_id] = to;
}
void
queue_move(struct queue *queue, unsigned from, unsigned to)
{
struct queue_item item = queue->items[from];
/* move songs to one less in from->to */
for (unsigned i = from; i < to; i++)
queue_move_song_to(queue, i + 1, i);
/* move songs to one more in to->from */
for (unsigned i = from; i > to; i--)
queue_move_song_to(queue, i - 1, i);
/* put song at _to_ */
queue->idToPosition[item.id] = to;
queue->items[to] = item;
queue->items[to].version = queue->version;
/* now deal with order */
if (queue->random) {
for (unsigned i = 0; i < queue->length; i++) {
if (queue->order[i] > from && queue->order[i] <= to)
queue->order[i]--;
else if (queue->order[i] < from &&
queue->order[i] >= to)
queue->order[i]++;
else if (from == queue->order[i])
queue->order[i] = to;
}
}
}
void
queue_delete(struct queue *queue, unsigned position)
{
struct song *song;
unsigned id, order;
assert(position < queue->length);
song = queue_get(queue, position);
if (!song_in_database(song))
song_free(song);
id = queue_position_to_id(queue, position);
order = queue_position_to_order(queue, position);
--queue->length;
/* release the song id */
queue->idToPosition[id] = -1;
/* delete song from songs array */
for (unsigned i = position; i < queue->length; i++)
queue_move_song_to(queue, i + 1, i);
/* delete the entry from the order array */
for (unsigned i = order; i < queue->length; i++)
queue->order[i] = queue->order[i + 1];
/* readjust values in the order array */
for (unsigned i = 0; i < queue->length; i++)
if (queue->order[i] > position)
--queue->order[i];
}
void
queue_clear(struct queue *queue)
{
for (unsigned i = 0; i < queue->length; i++) {
struct queue_item *item = &queue->items[i];
if (!song_in_database(item->song))
song_free(item->song);
queue->idToPosition[item->id] = -1;
}
queue->length = 0;
}
void
queue_init(struct queue *queue, unsigned max_length)
{
queue->max_length = max_length;
queue->length = 0;
queue->version = 1;
queue->repeat = false;
queue->random = false;
queue->items = g_new(struct queue_item, max_length);
queue->order = g_malloc(sizeof(queue->order[0]) *
max_length);
queue->idToPosition = g_malloc(sizeof(queue->idToPosition[0]) *
max_length * QUEUE_HASH_MULT);
for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i)
queue->idToPosition[i] = -1;
queue->rand = g_rand_new();
}
void
queue_finish(struct queue *queue)
{
queue_clear(queue);
g_free(queue->items);
g_free(queue->order);
g_free(queue->idToPosition);
g_rand_free(queue->rand);
}
void
queue_shuffle_order(struct queue *queue)
{
assert(queue->random);
for (unsigned i = 0; i < queue->length; i++)
queue_swap_order(queue, i,
g_rand_int_range(queue->rand, i,
queue->length));
}
void
queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end)
{
queue_swap_order(queue, end - 1,
g_rand_int_range(queue->rand, start, end));
}
void
queue_shuffle_range(struct queue *queue, unsigned start, unsigned end)
{
assert(start <= end);
assert(end <= queue->length);
for (unsigned i = start; i < end; i++) {
unsigned ri = g_rand_int_range(queue->rand, i, end);
queue_swap(queue, i, ri);
}
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef QUEUE_H
#define QUEUE_H
#include <glib.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
enum {
/**
* reserve max_length * QUEUE_HASH_MULT elements in the id
* number space
*/
QUEUE_HASH_MULT = 4,
};
/**
* One element of the queue: basically a song plus some queue specific
* information attached.
*/
struct queue_item {
struct song *song;
/** the unique id of this item in the queue */
unsigned id;
/** when was this item last changed? */
uint32_t version;
};
/**
* A queue of songs. This is the backend of the playlist: it contains
* an ordered list of songs.
*
* Songs can be addressed in three possible ways:
*
* - the position in the queue
* - the unique id (which stays the same, regardless of moves)
* - the order number (which only differs from "position" in random mode)
*/
struct queue {
/** configured maximum length of the queue */
unsigned max_length;
/** number of songs in the queue */
unsigned length;
/** the current version number */
uint32_t version;
/** all songs in "position" order */
struct queue_item *items;
/** map order numbers to positions */
unsigned *order;
/** map song ids to posiitons */
int *idToPosition;
/** repeat playback when the end of the queue has been
reached? */
bool repeat;
/** play back songs in random order? */
bool random;
/** random number generator for shuffle and random mode */
GRand *rand;
};
static inline unsigned
queue_length(const struct queue *queue)
{
assert(queue->length <= queue->max_length);
return queue->length;
}
/**
* Determine if the queue is empty, i.e. there are no songs.
*/
static inline bool
queue_is_empty(const struct queue *queue)
{
return queue->length == 0;
}
/**
* Determine if the maximum number of songs has been reached.
*/
static inline bool
queue_is_full(const struct queue *queue)
{
assert(queue->length <= queue->max_length);
return queue->length >= queue->max_length;
}
/**
* Is that a valid position number?
*/
static inline bool
queue_valid_position(const struct queue *queue, unsigned position)
{
return position < queue->length;
}
/**
* Is that a valid order number?
*/
static inline bool
queue_valid_order(const struct queue *queue, unsigned order)
{
return order < queue->length;
}
static inline int
queue_id_to_position(const struct queue *queue, unsigned id)
{
if (id >= queue->max_length * QUEUE_HASH_MULT)
return -1;
assert(queue->idToPosition[id] >= -1);
assert(queue->idToPosition[id] < (int)queue->length);
return queue->idToPosition[id];
}
static inline int
queue_position_to_id(const struct queue *queue, unsigned position)
{
assert(position < queue->length);
return queue->items[position].id;
}
static inline unsigned
queue_order_to_position(const struct queue *queue, unsigned order)
{
assert(order < queue->length);
return queue->order[order];
}
static inline unsigned
queue_position_to_order(const struct queue *queue, unsigned position)
{
assert(position < queue->length);
for (unsigned i = 0;; ++i) {
assert(i < queue->length);
if (queue->order[i] == position)
return i;
}
}
/**
* Returns the song at the specified position.
*/
static inline struct song *
queue_get(const struct queue *queue, unsigned position)
{
assert(position < queue->length);
return queue->items[position].song;
}
/**
* Returns the song at the specified order number.
*/
static inline struct song *
queue_get_order(const struct queue *queue, unsigned order)
{
return queue_get(queue, queue_order_to_position(queue, order));
}
/**
* Is the song at the specified position newer than the specified
* version?
*/
static inline bool
queue_song_newer(const struct queue *queue, unsigned position,
uint32_t version)
{
assert(position < queue->length);
return version > queue->version ||
queue->items[position].version >= version ||
queue->items[position].version == 0;
}
/**
* Initialize a queue object.
*/
void
queue_init(struct queue *queue, unsigned max_length);
/**
* Deinitializes a queue object. It does not free the queue pointer
* itself.
*/
void
queue_finish(struct queue *queue);
/**
* Generate a non-existing id number.
*/
unsigned
queue_generate_id(const struct queue *queue);
/**
* Returns the order number following the specified one. This takes
* end of queue and "repeat" mode into account.
*
* @return the next order number, or -1 to stop playback
*/
int
queue_next_order(const struct queue *queue, unsigned order);
/**
* Increments the queue's version number. This handles integer
* overflow well.
*/
void
queue_increment_version(struct queue *queue);
/**
* Marks the specified song as "modified" and increments the version
* number.
*/
void
queue_modify(struct queue *queue, unsigned order);
/**
* Marks all songs as "modified" and increments the version number.
*/
void
queue_modify_all(struct queue *queue);
/**
* Appends a song to the queue and returns its position. Prior to
* that, the caller must check if the queue is already full.
*
* If a song is not in the database (determined by
* song_in_database()), it is freed when removed from the queue.
*/
unsigned
queue_append(struct queue *queue, struct song *song);
/**
* Swaps two songs, addressed by their position.
*/
void
queue_swap(struct queue *queue, unsigned position1, unsigned position2);
/**
* Swaps two songs, addressed by their order number.
*/
static inline void
queue_swap_order(struct queue *queue, unsigned order1, unsigned order2)
{
unsigned tmp = queue->order[order1];
queue->order[order1] = queue->order[order2];
queue->order[order2] = tmp;
}
/**
* Moves a song to a new position.
*/
void
queue_move(struct queue *queue, unsigned from, unsigned to);
/**
* Removes a song from the playlist.
*/
void
queue_delete(struct queue *queue, unsigned position);
/**
* Removes all songs from the playlist.
*/
void
queue_clear(struct queue *queue);
/**
* Initializes the "order" array, and restores "normal" order.
*/
static inline void
queue_restore_order(struct queue *queue)
{
for (unsigned i = 0; i < queue->length; ++i)
queue->order[i] = i;
}
/**
* Shuffles the virtual order of songs, but does not move them
* physically. This is used in random mode.
*/
void
queue_shuffle_order(struct queue *queue);
/**
* Shuffles the virtual order of the last song in the specified
* (order) range. This is used in random mode after a song has been
* appended by queue_append().
*/
void
queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end);
/**
* Shuffles a (position) range in the queue. The songs are physically
* shuffled, not by using the "order" mapping.
*/
void
queue_shuffle_range(struct queue *queue, unsigned start, unsigned end);
#endif
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "queue_print.h"
#include "queue.h"
#include "song.h"
#include "song_print.h"
#include "locate.h"
#include "client.h"
void
queue_print_song_info(struct client *client, const struct queue *queue,
unsigned position)
{
song_print_info(client, queue_get(queue, position));
client_printf(client, "Pos: %u\nId: %u\n",
position, queue_position_to_id(queue, position));
}
void
queue_print_info(struct client *client, const struct queue *queue,
unsigned start, unsigned end)
{
assert(start <= end);
assert(end <= queue_length(queue));
for (unsigned i = start; i < end; ++i)
queue_print_song_info(client, queue, i);
}
void
queue_print_uris(struct client *client, const struct queue *queue,
unsigned start, unsigned end)
{
assert(start <= end);
assert(end <= queue_length(queue));
for (unsigned i = start; i < end; ++i) {
const struct song *song = queue_get(queue, i);
char *uri = song_get_uri(song);
client_printf(client, "%i:%s\n", i, uri);
g_free(uri);
}
}
void
queue_print_changes_info(struct client *client, const struct queue *queue,
uint32_t version)
{
for (unsigned i = 0; i < queue_length(queue); i++) {
if (queue_song_newer(queue, i, version))
queue_print_song_info(client, queue, i);
}
}
void
queue_print_changes_position(struct client *client, const struct queue *queue,
uint32_t version)
{
for (unsigned i = 0; i < queue_length(queue); i++)
if (queue_song_newer(queue, i, version))
client_printf(client, "cpos: %i\nId: %i\n",
i, queue_position_to_id(queue, i));
}
void
queue_search(struct client *client, const struct queue *queue,
const struct locate_item_list *criteria)
{
unsigned i;
struct locate_item_list *new_list =
locate_item_list_casefold(criteria);
for (i = 0; i < queue_length(queue); i++) {
const struct song *song = queue_get(queue, i);
if (locate_song_search(song, new_list))
queue_print_song_info(client, queue, i);
}
locate_item_list_free(new_list);
}
void
queue_find(struct client *client, const struct queue *queue,
const struct locate_item_list *criteria)
{
for (unsigned i = 0; i < queue_length(queue); i++) {
const struct song *song = queue_get(queue, i);
if (locate_song_match(song, criteria))
queue_print_song_info(client, queue, i);
}
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* This library sends information about songs in the queue to the
* client.
*/
#ifndef QUEUE_PRINT_H
#define QUEUE_PRINT_H
#include <stdint.h>
struct client;
struct queue;
struct locate_item_list;
void
queue_print_song_info(struct client *client, const struct queue *queue,
unsigned position);
/**
* Send detailed information about a range of songs in the queue to a
* client.
*
* @param client the client which has requested information
* @param start the index of the first song (including)
* @param end the index of the last song (excluding)
*/
void
queue_print_info(struct client *client, const struct queue *queue,
unsigned start, unsigned end);
void
queue_print_uris(struct client *client, const struct queue *queue,
unsigned start, unsigned end);
void
queue_print_changes_info(struct client *client, const struct queue *queue,
uint32_t version);
void
queue_print_changes_position(struct client *client, const struct queue *queue,
uint32_t version);
void
queue_search(struct client *client, const struct queue *queue,
const struct locate_item_list *criteria);
void
queue_find(struct client *client, const struct queue *queue,
const struct locate_item_list *criteria);
#endif
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "queue_save.h"
#include "queue.h"
#include "song.h"
#include "ls.h"
#include "database.h"
#include <stdlib.h>
void
queue_save(FILE *fp, const struct queue *queue)
{
for (unsigned i = 0; i < queue_length(queue); i++) {
const struct song *song = queue_get(queue, i);
char *uri = song_get_uri(song);
fprintf(fp, "%i:%s\n", i, uri);
g_free(uri);
}
}
static struct song *
get_song(const char *uri)
{
struct song *song;
song = db_get_song(uri);
if (song != NULL)
return song;
if (uri_has_scheme(uri))
return song_remote_new(uri);
return NULL;
}
int
queue_load_song(struct queue *queue, const char *line)
{
long ret;
char *endptr;
struct song *song;
if (queue_is_full(queue))
return -1;
ret = strtol(line, &endptr, 10);
if (ret < 0 || *endptr != ':' || endptr[1] == 0) {
g_warning("Malformed playlist line in state file");
return -1;
}
line = endptr + 1;
song = get_song(line);
if (song == NULL)
return -1;
queue_append(queue, song);
return ret;
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* This library saves the queue into the state file, and also loads it
* back into memory.
*/
#ifndef QUEUE_SAVE_H
#define QUEUE_SAVE_H
#include <stdio.h>
struct queue;
void
queue_save(FILE *fp, const struct queue *queue);
/**
* Loads one song from the state file line and returns its number.
* Returns -1 on failure.
*/
int
queue_load_song(struct queue *queue, const char *line);
#endif
......@@ -38,7 +38,7 @@ static float replay_gain_preamp = 1.0;
void replay_gain_global_init(void)
{
struct config_param *param = config_get_param(CONF_REPLAYGAIN);
const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
if (!param)
return;
......
......@@ -20,8 +20,6 @@
#include "ls.h"
#include "directory.h"
#include "mapper.h"
#include "path.h"
#include "playlist.h"
#include "decoder_list.h"
#include "decoder_api.h"
#include "tag_id3.h"
......
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "song_sticker.h"
#include "song.h"
#include "sticker.h"
#include <glib.h>
#include <assert.h>
char *
sticker_song_get_value(const struct song *song, const char *name)
{
char *uri, *value;
assert(song != NULL);
assert(song_in_database(song));
uri = song_get_uri(song);
value = sticker_load_value("song", uri, name);
g_free(uri);
return value;
}
bool
sticker_song_set_value(const struct song *song,
const char *name, const char *value)
{
char *uri;
bool ret;
assert(song != NULL);
assert(song_in_database(song));
uri = song_get_uri(song);
ret = sticker_store_value("song", uri, name, value);
g_free(uri);
return ret;
}
bool
sticker_song_delete(const struct song *song)
{
char *uri;
bool ret;
assert(song != NULL);
assert(song_in_database(song));
uri = song_get_uri(song);
ret = sticker_delete("song", uri);
g_free(uri);
return ret;
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef SONG_STICKER_H
#define SONG_STICKER_H
#include <stdbool.h>
struct song;
/**
* Returns one value from a song's sticker record. The caller must
* free the return value with g_free().
*/
char *
sticker_song_get_value(const struct song *song, const char *name);
/**
* Sets a sticker value in the specified song. Overwrites existing
* values.
*/
bool
sticker_song_set_value(const struct song *song,
const char *name, const char *value);
/**
* Deletes a sticker from the database. All values are deleted.
*/
bool
sticker_song_delete(const struct song *song);
#endif
......@@ -16,14 +16,13 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../config.h"
#include "state_file.h"
#include "conf.h"
#include "audio.h"
#include "output_state.h"
#include "playlist.h"
#include "volume.h"
#include <glib.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
......@@ -39,29 +38,24 @@ static struct _sf_cb {
{ readPlaylistState, savePlaylistState },
};
static const char *sfpath;
static char *state_file_path;
static void get_state_file_path(void)
{
struct config_param *param;
if (sfpath)
return;
param = parseConfigFilePath(CONF_STATE_FILE, 0);
if (param)
sfpath = (const char *)param->value;
}
/** the GLib source id for the save timer */
static guint save_state_source_id;
void write_state_file(void)
static void
state_file_write(void)
{
unsigned int i;
FILE *fp;
if (!sfpath)
if (state_file_path == NULL)
return;
fp = fopen(sfpath, "w");
fp = fopen(state_file_path, "w");
if (G_UNLIKELY(!fp)) {
g_warning("failed to create %s: %s",
sfpath, strerror(errno));
state_file_path, strerror(errno));
return;
}
......@@ -71,19 +65,20 @@ void write_state_file(void)
while(fclose(fp) && errno == EINTR) /* nothing */;
}
void read_state_file(void)
static void
state_file_read(void)
{
unsigned int i;
FILE *fp;
get_state_file_path();
if (!sfpath)
return;
assert(state_file_path != NULL);
g_debug("Saving state file");
while (!(fp = fopen(sfpath, "r")) && errno == EINTR);
fp = fopen(state_file_path, "r");
if (G_UNLIKELY(!fp)) {
g_warning("failed to open %s: %s",
sfpath, strerror(errno));
state_file_path, strerror(errno));
return;
}
for (i = 0; i < G_N_ELEMENTS(sf_callbacks); i++) {
......@@ -93,3 +88,41 @@ void read_state_file(void)
while(fclose(fp) && errno == EINTR) /* nothing */;
}
/**
* This function is called every 5 minutes by the GLib main loop, and
* saves the state file.
*/
static gboolean
timer_save_state_file(G_GNUC_UNUSED gpointer data)
{
state_file_write();
return true;
}
void
state_file_init(const char *path)
{
assert(state_file_path == NULL);
if (path == NULL)
return;
state_file_path = g_strdup(path);
state_file_read();
save_state_source_id = g_timeout_add(5 * 60 * 1000,
timer_save_state_file, NULL);
}
void
state_file_finish(void)
{
if (save_state_source_id != 0)
g_source_remove(save_state_source_id);
if (state_file_path != NULL)
state_file_write();
g_free(state_file_path);
}
......@@ -19,9 +19,12 @@
#ifndef MPD_STATE_FILE_H
#define MPD_STATE_FILE_H
#include <glib.h>
void
state_file_init(const char *path);
void
state_file_finish(void);
void write_state_file(void);
void read_state_file(void);
#endif /* STATE_FILE_H */
......@@ -24,61 +24,82 @@
#include "client.h"
#include "player_control.h"
#include "strset.h"
#include "dbUtils.h"
Stats stats;
struct stats stats;
void initStats(void)
void stats_global_init(void)
{
stats.daemonStart = time(NULL);
stats.numberOfSongs = 0;
stats.timer = g_timer_new();
}
void stats_update(void)
void stats_global_finish(void)
{
stats.numberOfSongs = countSongsIn(NULL);
stats.dbPlayTime = sumSongTimesIn(NULL);
g_timer_destroy(stats.timer);
}
struct visit_data {
enum tag_type type;
struct strset *set;
struct strset *artists;
struct strset *albums;
};
static int
visit_tag_items(struct song *song, void *_data)
static void
visit_tag(struct visit_data *data, const struct tag *tag)
{
const struct visit_data *data = _data;
unsigned i;
if (tag->time > 0)
stats.song_duration += tag->time;
for (unsigned i = 0; i < tag->numOfItems; ++i) {
const struct tag_item *item = tag->items[i];
switch (item->type) {
case TAG_ITEM_ARTIST:
strset_add(data->artists, item->value);
break;
if (song->tag == NULL)
return 0;
case TAG_ITEM_ALBUM:
strset_add(data->albums, item->value);
break;
for (i = 0; i < (unsigned)song->tag->numOfItems; ++i) {
const struct tag_item *item = song->tag->items[i];
if (item->type == data->type)
strset_add(data->set, item->value);
default:
break;
}
}
}
static int
stats_collect_song(struct song *song, void *_data)
{
struct visit_data *data = _data;
++stats.song_count;
if (song->tag != NULL)
visit_tag(data, song->tag);
return 0;
}
static unsigned int getNumberOfTagItems(int type)
void stats_update(void)
{
struct visit_data data = {
.type = type,
.set = strset_new(),
};
unsigned int ret;
struct visit_data data;
stats.song_count = 0;
stats.song_duration = 0;
stats.artist_count = 0;
data.artists = strset_new();
data.albums = strset_new();
db_walk(NULL, stats_collect_song, NULL, &data);
db_walk(NULL, visit_tag_items, NULL, &data);
stats.artist_count = strset_size(data.artists);
stats.album_count = strset_size(data.albums);
ret = strset_size(data.set);
strset_free(data.set);
return ret;
strset_free(data.artists);
strset_free(data.albums);
}
int printStats(struct client *client)
int stats_print(struct client *client)
{
client_printf(client,
"artists: %u\n"
......@@ -88,12 +109,12 @@ int printStats(struct client *client)
"playtime: %li\n"
"db_playtime: %li\n"
"db_update: %li\n",
getNumberOfTagItems(TAG_ITEM_ARTIST),
getNumberOfTagItems(TAG_ITEM_ALBUM),
stats.numberOfSongs,
time(NULL) - stats.daemonStart,
stats.artist_count,
stats.album_count,
stats.song_count,
(long)g_timer_elapsed(stats.timer, NULL),
(long)(getPlayerTotalPlayTime() + 0.5),
stats.dbPlayTime,
stats.song_duration,
db_get_mtime());
return 0;
}
......@@ -19,22 +19,35 @@
#ifndef MPD_STATS_H
#define MPD_STATS_H
#include <glib.h>
struct client;
typedef struct _Stats {
unsigned long daemonStart;
int numberOfSongs;
unsigned long dbPlayTime;
/*unsigned long playTime;
unsigned long songsPlayed; */
} Stats;
struct stats {
GTimer *timer;
/** number of song files in the music directory */
unsigned song_count;
/** sum of all song durations in the music directory (in
seconds) */
unsigned long song_duration;
/** number of distinct artist names in the music directory */
unsigned artist_count;
/** number of distinct album names in the music directory */
unsigned album_count;
};
extern struct stats stats;
extern Stats stats;
void stats_global_init(void);
void initStats(void);
void stats_global_finish(void);
void stats_update(void);
int printStats(struct client *client);
int stats_print(struct client *client);
#endif
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "sticker.h"
#include "idle.h"
#include <glib.h>
#include <sqlite3.h>
#include <assert.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "sticker"
static const char sticker_sql_create[] =
"CREATE TABLE IF NOT EXISTS sticker("
" type VARCHAR NOT NULL, "
" uri VARCHAR NOT NULL, "
" name VARCHAR NOT NULL, "
" value VARCHAR NOT NULL"
");"
"CREATE UNIQUE INDEX IF NOT EXISTS"
" sticker_value ON sticker(type, uri, name);"
"";
static const char sticker_sql_get[] =
"SELECT value FROM sticker WHERE type=? AND uri=? AND name=?";
static const char sticker_sql_update[] =
"UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?";
static const char sticker_sql_insert[] =
"INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)";
static const char sticker_sql_delete[] =
"DELETE FROM sticker WHERE type=? AND uri=?";
static sqlite3 *sticker_db;
static sqlite3_stmt *sticker_stmt_get, *sticker_stmt_update,
*sticker_stmt_insert, *sticker_stmt_delete;
static sqlite3_stmt *
sticker_prepare(const char *sql)
{
int ret;
sqlite3_stmt *stmt;
ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL);
if (ret != SQLITE_OK)
g_error("sqlite3_prepare_v2() failed: %s",
sqlite3_errmsg(sticker_db));
return stmt;
}
void
sticker_global_init(const char *path)
{
int ret;
if (path == NULL)
/* not configured */
return;
/* open/create the sqlite database */
ret = sqlite3_open(path, &sticker_db);
if (ret != SQLITE_OK)
g_error("Failed to open sqlite database '%s': %s",
path, sqlite3_errmsg(sticker_db));
/* create the table and index */
ret = sqlite3_exec(sticker_db, sticker_sql_create, NULL, NULL, NULL);
if (ret != SQLITE_OK)
g_error("Failed to create sticker table: %s",
sqlite3_errmsg(sticker_db));
/* prepare the statements we're going to use */
sticker_stmt_get = sticker_prepare(sticker_sql_get);
sticker_stmt_update = sticker_prepare(sticker_sql_update);
sticker_stmt_insert = sticker_prepare(sticker_sql_insert);
sticker_stmt_delete = sticker_prepare(sticker_sql_delete);
if (sticker_stmt_get == NULL || sticker_stmt_update == NULL ||
sticker_stmt_insert == NULL || sticker_stmt_delete == NULL)
g_error("Failed to prepare sqlite statements");
}
void
sticker_global_finish(void)
{
if (sticker_db == NULL)
/* not configured */
return;
sqlite3_finalize(sticker_stmt_delete);
sqlite3_finalize(sticker_stmt_update);
sqlite3_finalize(sticker_stmt_insert);
sqlite3_close(sticker_db);
}
bool
sticker_enabled(void)
{
return sticker_db != NULL;
}
char *
sticker_load_value(const char *type, const char *uri, const char *name)
{
int ret;
char *value;
assert(sticker_enabled());
assert(type != NULL);
assert(uri != NULL);
assert(name != NULL);
if (*name == 0)
return NULL;
sqlite3_reset(sticker_stmt_get);
ret = sqlite3_bind_text(sticker_stmt_get, 1, type, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
ret = sqlite3_bind_text(sticker_stmt_get, 2, uri, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
ret = sqlite3_bind_text(sticker_stmt_get, 3, name, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
do {
ret = sqlite3_step(sticker_stmt_get);
} while (ret == SQLITE_BUSY);
if (ret == SQLITE_ROW) {
/* record found */
value = g_strdup((const char*)sqlite3_column_text(sticker_stmt_get, 0));
} else if (ret == SQLITE_DONE) {
/* no record found */
value = NULL;
} else {
/* error */
g_warning("sqlite3_step() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
sqlite3_reset(sticker_stmt_get);
sqlite3_clear_bindings(sticker_stmt_get);
return value;
}
static bool
sticker_update_value(const char *type, const char *uri,
const char *name, const char *value)
{
int ret;
assert(type != NULL);
assert(uri != NULL);
assert(name != NULL);
assert(*name != 0);
assert(value != NULL);
assert(sticker_enabled());
sqlite3_reset(sticker_stmt_update);
ret = sqlite3_bind_text(sticker_stmt_update, 1, value, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
ret = sqlite3_bind_text(sticker_stmt_update, 2, type, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
ret = sqlite3_bind_text(sticker_stmt_update, 3, uri, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
ret = sqlite3_bind_text(sticker_stmt_update, 4, name, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
do {
ret = sqlite3_step(sticker_stmt_update);
} while (ret == SQLITE_BUSY);
if (ret != SQLITE_DONE) {
g_warning("sqlite3_step() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
ret = sqlite3_changes(sticker_db);
sqlite3_reset(sticker_stmt_update);
sqlite3_clear_bindings(sticker_stmt_update);
idle_add(IDLE_STICKER);
return ret > 0;
}
static bool
sticker_insert_value(const char *type, const char *uri,
const char *name, const char *value)
{
int ret;
assert(type != NULL);
assert(uri != NULL);
assert(name != NULL);
assert(*name != 0);
assert(value != NULL);
assert(sticker_enabled());
sqlite3_reset(sticker_stmt_insert);
ret = sqlite3_bind_text(sticker_stmt_insert, 1, type, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
ret = sqlite3_bind_text(sticker_stmt_insert, 2, uri, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
ret = sqlite3_bind_text(sticker_stmt_insert, 3, name, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
ret = sqlite3_bind_text(sticker_stmt_insert, 4, value, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
do {
ret = sqlite3_step(sticker_stmt_insert);
} while (ret == SQLITE_BUSY);
if (ret != SQLITE_DONE) {
g_warning("sqlite3_step() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
sqlite3_reset(sticker_stmt_insert);
sqlite3_clear_bindings(sticker_stmt_insert);
idle_add(IDLE_STICKER);
return true;
}
bool
sticker_store_value(const char *type, const char *uri,
const char *name, const char *value)
{
assert(sticker_enabled());
assert(type != NULL);
assert(uri != NULL);
assert(name != NULL);
assert(value != NULL);
if (*name == 0)
return false;
return sticker_update_value(type, uri, name, value) ||
sticker_insert_value(type, uri, name, value);
}
bool
sticker_delete(const char *type, const char *uri)
{
int ret;
assert(sticker_enabled());
assert(type != NULL);
assert(uri != NULL);
sqlite3_reset(sticker_stmt_delete);
ret = sqlite3_bind_text(sticker_stmt_delete, 1, type, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
ret = sqlite3_bind_text(sticker_stmt_delete, 2, uri, -1, NULL);
if (ret != SQLITE_OK) {
g_warning("sqlite3_bind_text() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
do {
ret = sqlite3_step(sticker_stmt_delete);
} while (ret == SQLITE_BUSY);
if (ret != SQLITE_DONE) {
g_warning("sqlite3_step() failed: %s",
sqlite3_errmsg(sticker_db));
return false;
}
sqlite3_reset(sticker_stmt_delete);
sqlite3_clear_bindings(sticker_stmt_delete);
idle_add(IDLE_STICKER);
return true;
}
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* This is the sticker database library. It is the backend of all the
* sticker code in MPD.
*
* "Stickers" are pieces of information attached to existing MPD
* objects (e.g. song files, directories, albums). Clients can create
* arbitrary name/value pairs. MPD itself does not assume any special
* meaning in them.
*
* The goal is to allow clients to share additional (possibly dynamic)
* information about songs, which is neither stored on the client (not
* available to other clients), nor stored in the song files (MPD has
* no write access).
*
* Client developers should create a standard for common sticker
* names, to ensure interoperability.
*
* Examples: song ratings; statistics; deferred tag writes; lyrics;
* ...
*
*/
#ifndef STICKER_H
#define STICKER_H
#include <stdbool.h>
/**
* Opens the sticker database (if path is not NULL).
*/
void
sticker_global_init(const char *path);
/**
* Close the sticker database.
*/
void
sticker_global_finish(void);
/**
* Returns true if the sticker database is configured and available.
*/
bool
sticker_enabled(void);
/**
* Returns one value from an object's sticker record. The caller must
* free the return value with g_free().
*/
char *
sticker_load_value(const char *type, const char *uri, const char *name);
/**
* Sets a sticker value in the specified object. Overwrites existing
* values.
*/
bool
sticker_store_value(const char *type, const char *uri,
const char *name, const char *value);
/**
* Deletes a sticker from the database. All sticker values of the
* specified object are deleted.
*/
bool
sticker_delete(const char *type, const char *uri);
#endif
......@@ -24,6 +24,7 @@
#include "ls.h"
#include "database.h"
#include "idle.h"
#include "conf.h"
#include <assert.h>
#include <sys/types.h>
......@@ -33,6 +34,39 @@
#include <string.h>
#include <errno.h>
static unsigned playlist_max_length;
bool playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS;
void
spl_global_init(void)
{
playlist_max_length = config_get_positive(CONF_MAX_PLAYLIST_LENGTH,
DEFAULT_PLAYLIST_MAX_LENGTH);
playlist_saveAbsolutePaths =
config_get_bool(CONF_SAVE_ABSOLUTE_PATHS,
DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS);
}
bool
spl_valid_name(const char *name_utf8)
{
/*
* Not supporting '/' was done out of laziness, and we should
* really strive to support it in the future.
*
* Not supporting '\r' and '\n' is done out of protocol
* limitations (and arguably laziness), but bending over head
* over heels to modify the protocol (and compatibility with
* all clients) to support idiots who put '\r' and '\n' in
* filenames isn't going to happen, either.
*/
return strchr(name_utf8, '/') == NULL &&
strchr(name_utf8, '\n') == NULL &&
strchr(name_utf8, '\r') == NULL;
}
static struct stored_playlist_info *
load_playlist_info(const char *parent_path_fs, const char *name_fs)
{
......@@ -42,14 +76,11 @@ load_playlist_info(const char *parent_path_fs, const char *name_fs)
struct stat st;
struct stored_playlist_info *playlist;
if (name_length < 1 + sizeof(PLAYLIST_FILE_SUFFIX) ||
if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) ||
memchr(name_fs, '\n', name_length) != NULL)
return NULL;
if (name_fs[name_length - sizeof(PLAYLIST_FILE_SUFFIX)] != '.' ||
memcmp(name_fs + name_length - sizeof(PLAYLIST_FILE_SUFFIX) + 1,
PLAYLIST_FILE_SUFFIX,
sizeof(PLAYLIST_FILE_SUFFIX) - 1) != 0)
if (!g_str_has_suffix(name_fs, PLAYLIST_FILE_SUFFIX))
return NULL;
path_fs = g_build_filename(parent_path_fs, name_fs, NULL);
......@@ -58,8 +89,8 @@ load_playlist_info(const char *parent_path_fs, const char *name_fs)
if (ret < 0 || !S_ISREG(st.st_mode))
return NULL;
name = g_strdup(name_fs);
name[name_length - sizeof(PLAYLIST_FILE_SUFFIX)] = 0;
name = g_strndup(name_fs,
name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX));
name_utf8 = fs_charset_to_utf8(name);
g_free(name);
if (name_utf8 == NULL)
......@@ -80,6 +111,9 @@ spl_list(void)
GPtrArray *list;
struct stored_playlist_info *playlist;
if (parent_path_fs == NULL)
return NULL;
dir = opendir(parent_path_fs);
if (dir == NULL)
return NULL;
......@@ -118,6 +152,8 @@ spl_save(GPtrArray *list, const char *utf8path)
assert(utf8path != NULL);
path_fs = map_spl_utf8_to_fs(utf8path);
if (path_fs == NULL)
return PLAYLIST_RESULT_DISABLED;
while (!(file = fopen(path_fs, "w")) && errno == EINTR);
g_free(path_fs);
......@@ -141,10 +177,12 @@ spl_load(const char *utf8path)
char buffer[MPD_PATH_MAX];
char *path_fs;
if (!is_valid_playlist_name(utf8path))
if (!spl_valid_name(utf8path))
return NULL;
path_fs = map_spl_utf8_to_fs(utf8path);
if (path_fs == NULL)
return NULL;
while (!(file = fopen(path_fs, "r")) && errno == EINTR);
g_free(path_fs);
......@@ -260,10 +298,12 @@ spl_clear(const char *utf8path)
char *path_fs;
FILE *file;
if (!is_valid_playlist_name(utf8path))
if (!spl_valid_name(utf8path))
return PLAYLIST_RESULT_BAD_NAME;
path_fs = map_spl_utf8_to_fs(utf8path);
if (path_fs == NULL)
return PLAYLIST_RESULT_DISABLED;
while (!(file = fopen(path_fs, "w")) && errno == EINTR);
g_free(path_fs);
......@@ -283,6 +323,9 @@ spl_delete(const char *name_utf8)
int ret;
path_fs = map_spl_utf8_to_fs(name_utf8);
if (path_fs == NULL)
return PLAYLIST_RESULT_DISABLED;
ret = unlink(path_fs);
g_free(path_fs);
if (ret < 0)
......@@ -326,10 +369,12 @@ spl_append_song(const char *utf8path, struct song *song)
struct stat st;
char *path_fs;
if (!is_valid_playlist_name(utf8path))
if (!spl_valid_name(utf8path))
return PLAYLIST_RESULT_BAD_NAME;
path_fs = map_spl_utf8_to_fs(utf8path);
if (path_fs == NULL)
return PLAYLIST_RESULT_DISABLED;
while (!(file = fopen(path_fs, "a")) && errno == EINTR);
g_free(path_fs);
......@@ -403,14 +448,16 @@ spl_rename(const char *utf8from, const char *utf8to)
char *from_path_fs, *to_path_fs;
static enum playlist_result ret;
if (!is_valid_playlist_name(utf8from) ||
!is_valid_playlist_name(utf8to))
if (!spl_valid_name(utf8from) || !spl_valid_name(utf8to))
return PLAYLIST_RESULT_BAD_NAME;
from_path_fs = map_spl_utf8_to_fs(utf8from);
to_path_fs = map_spl_utf8_to_fs(utf8to);
ret = spl_rename_internal(from_path_fs, to_path_fs);
if (from_path_fs != NULL && to_path_fs != NULL)
ret = spl_rename_internal(from_path_fs, to_path_fs);
else
ret = PLAYLIST_RESULT_DISABLED;
g_free(from_path_fs);
g_free(to_path_fs);
......
......@@ -22,6 +22,7 @@
#include "playlist.h"
#include <glib.h>
#include <stdbool.h>
#include <time.h>
struct song;
......@@ -32,6 +33,21 @@ struct stored_playlist_info {
time_t mtime;
};
extern bool playlist_saveAbsolutePaths;
/**
* Perform some global initialization, e.g. load configuration values.
*/
void
spl_global_init(void);
/**
* Determines whether the specified string is a valid name for a
* stored playlist.
*/
bool
spl_valid_name(const char *name_utf8);
/**
* Returns a list of stored_playlist_info struct pointers. Returns
* NULL if an error occured.
......
......@@ -64,12 +64,12 @@ void strset_free(struct strset *set)
while (slot != NULL) {
next = slot->next;
free(slot);
g_free(slot);
slot = next;
}
}
free(set);
g_free(set);
}
void strset_add(struct strset *set, const char *value)
......
......@@ -53,7 +53,13 @@ const char *mpdTagItemKeys[TAG_NUM_OF_ITEM_TYPES] = {
"Composer",
"Performer",
"Comment",
"Disc"
"Disc",
/* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */
[TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID",
[TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID",
[TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID",
[TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TACKID",
};
int8_t ignoreTagItems[TAG_NUM_OF_ITEM_TYPES];
......@@ -65,11 +71,11 @@ static size_t items_size(const struct tag *tag)
void tag_lib_init(void)
{
const char *value;
int quit = 0;
char *temp;
char *s;
char *c;
struct config_param *param;
int i;
/* parse the "metadata_to_use" config parameter below */
......@@ -77,17 +83,16 @@ void tag_lib_init(void)
memset(ignoreTagItems, 0, TAG_NUM_OF_ITEM_TYPES);
ignoreTagItems[TAG_ITEM_COMMENT] = 1; /* ignore comments by default */
param = config_get_param(CONF_METADATA_TO_USE);
if (!param)
value = config_get_string(CONF_METADATA_TO_USE, NULL);
if (value == NULL)
return;
memset(ignoreTagItems, 1, TAG_NUM_OF_ITEM_TYPES);
if (0 == strcasecmp(param->value, "none"))
if (0 == strcasecmp(value, "none"))
return;
temp = c = s = g_strdup(param->value);
temp = c = s = g_strdup(value);
while (!quit) {
if (*s == ',' || *s == '\0') {
if (*s == '\0')
......@@ -100,8 +105,8 @@ void tag_lib_init(void)
}
}
if (strlen(c) && i == TAG_NUM_OF_ITEM_TYPES) {
FATAL("error parsing metadata item \"%s\" at "
"line %i\n", c, param->line);
FATAL("error parsing metadata item \"%s\"\n",
c);
}
s++;
c = s;
......@@ -109,7 +114,7 @@ void tag_lib_init(void)
s++;
}
free(temp);
g_free(temp);
}
struct tag *tag_ape_load(const char *file)
......@@ -228,8 +233,7 @@ struct tag *tag_ape_load(const char *file)
fail:
if (fp)
fclose(fp);
if (buffer)
free(buffer);
g_free(buffer);
return ret;
}
......@@ -259,7 +263,7 @@ static void deleteItem(struct tag *tag, int idx)
if (tag->numOfItems > 0) {
tag->items = g_realloc(tag->items, items_size(tag));
} else {
free(tag->items);
g_free(tag->items);
tag->items = NULL;
}
}
......@@ -291,11 +295,10 @@ void tag_free(struct tag *tag)
assert(bulk.busy);
bulk.busy = 0;
#endif
} else if (tag->items) {
free(tag->items);
}
} else
g_free(tag->items);
free(tag);
g_free(tag);
}
struct tag *tag_dup(const struct tag *tag)
......
......@@ -39,6 +39,12 @@ enum tag_type {
TAG_ITEM_PERFORMER,
TAG_ITEM_COMMENT,
TAG_ITEM_DISC,
TAG_MUSICBRAINZ_ARTISTID,
TAG_MUSICBRAINZ_ALBUMID,
TAG_MUSICBRAINZ_ALBUMARTISTID,
TAG_MUSICBRAINZ_TRACKID,
TAG_NUM_OF_ITEM_TYPES
};
......
......@@ -24,6 +24,7 @@
#include <id3tag.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
......@@ -49,6 +50,23 @@
#define ID3_FRAME_ALBUM_ARTIST "TPE2"
#endif
static id3_utf8_t *
tag_id3_getstring(const struct id3_frame *frame, unsigned i)
{
union id3_field *field;
const id3_ucs4_t *ucs4;
field = id3_frame_field(frame, i);
if (field == NULL)
return NULL;
ucs4 = id3_field_getstring(field);
if (ucs4 == NULL)
return NULL;
return id3_ucs4_utf8duplicate(ucs4);
}
/* This will try to convert a string to utf-8,
*/
static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4, int type)
......@@ -91,8 +109,8 @@ static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4,
return utf8_stripped;
}
static struct tag *getID3Info(
struct id3_tag *tag, const char *id, int type, struct tag *mpdTag)
static void
getID3Info(struct id3_tag *tag, const char *id, int type, struct tag *mpdTag)
{
struct id3_frame const *frame;
id3_ucs4_t const *ucs4;
......@@ -104,13 +122,13 @@ static struct tag *getID3Info(
/* Check frame */
if (!frame)
{
return mpdTag;
return;
}
/* Check fields in frame */
if(frame->nfields == 0)
{
g_debug("Frame has no fields");
return mpdTag;
return;
}
/* Starting with T is a stringlist */
......@@ -126,7 +144,7 @@ static struct tag *getID3Info(
{
g_debug("Invalid number '%i' of fields for TXX frame",
frame->nfields);
return mpdTag;
return;
}
field = &frame->fields[0];
/**
......@@ -152,8 +170,6 @@ static struct tag *getID3Info(
if(!utf8)
continue;
if (mpdTag == NULL)
mpdTag = tag_new();
tag_add_item(mpdTag, type, (char *)utf8);
g_free(utf8);
}
......@@ -185,8 +201,6 @@ static struct tag *getID3Info(
utf8 = processID3FieldString(isId3v1(tag),ucs4, type);
if(utf8)
{
if (mpdTag == NULL)
mpdTag = tag_new();
tag_add_item(mpdTag, type, (char *)utf8);
g_free(utf8);
}
......@@ -205,32 +219,94 @@ static struct tag *getID3Info(
}
}
/* Unsupported */
else {
else
g_debug("Unsupported tag type requrested");
return mpdTag;
}
}
return mpdTag;
/**
* Parse a TXXX name, and convert it to a tag_type enum value.
* Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
*/
static enum tag_type
tag_id3_parse_txxx_name(const char *name)
{
static const struct {
enum tag_type type;
const char *name;
} musicbrainz_txxx[] = {
{ TAG_MUSICBRAINZ_ARTISTID, "MusicBrainz Artist Id" },
{ TAG_MUSICBRAINZ_ALBUMID, "MusicBrainz Album Id" },
{ TAG_MUSICBRAINZ_ALBUMARTISTID,
"MusicBrainz Album Artist Id" },
{ TAG_MUSICBRAINZ_TRACKID, "MusicBrainz Track Id" },
};
for (unsigned i = 0; i < G_N_ELEMENTS(musicbrainz_txxx); ++i)
if (strcmp(name, musicbrainz_txxx[i].name) == 0)
return musicbrainz_txxx[i].type;
return TAG_NUM_OF_ITEM_TYPES;
}
/**
* Import all known MusicBrainz tags from TXXX frames.
*/
static void
tag_id3_import_musicbrainz(struct tag *mpd_tag, struct id3_tag *id3_tag)
{
for (unsigned i = 0;; ++i) {
const struct id3_frame *frame;
id3_utf8_t *name, *value;
enum tag_type type;
frame = id3_tag_findframe(id3_tag, "TXXX", i);
if (frame == NULL)
break;
name = tag_id3_getstring(frame, 1);
if (name == NULL)
continue;
type = tag_id3_parse_txxx_name((const char*)name);
free(name);
if (type == TAG_NUM_OF_ITEM_TYPES)
continue;
value = tag_id3_getstring(frame, 2);
if (value == NULL)
continue;
tag_add_item(mpd_tag, type, (const char*)value);
free(value);
}
}
struct tag *tag_id3_import(struct id3_tag * tag)
{
struct tag *ret = NULL;
ret = getID3Info(tag, ID3_FRAME_ARTIST, TAG_ITEM_ARTIST, ret);
ret = getID3Info(tag, ID3_FRAME_ALBUM_ARTIST,
TAG_ITEM_ALBUM_ARTIST, ret);
ret = getID3Info(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
TAG_ITEM_ALBUM_ARTIST, ret);
ret = getID3Info(tag, ID3_FRAME_TITLE, TAG_ITEM_TITLE, ret);
ret = getID3Info(tag, ID3_FRAME_ALBUM, TAG_ITEM_ALBUM, ret);
ret = getID3Info(tag, ID3_FRAME_TRACK, TAG_ITEM_TRACK, ret);
ret = getID3Info(tag, ID3_FRAME_YEAR, TAG_ITEM_DATE, ret);
ret = getID3Info(tag, ID3_FRAME_GENRE, TAG_ITEM_GENRE, ret);
ret = getID3Info(tag, ID3_FRAME_COMPOSER, TAG_ITEM_COMPOSER, ret);
ret = getID3Info(tag, ID3_FRAME_PERFORMER, TAG_ITEM_PERFORMER, ret);
ret = getID3Info(tag, ID3_FRAME_COMMENT, TAG_ITEM_COMMENT, ret);
ret = getID3Info(tag, ID3_FRAME_DISC, TAG_ITEM_DISC, ret);
struct tag *ret = tag_new();
getID3Info(tag, ID3_FRAME_ARTIST, TAG_ITEM_ARTIST, ret);
getID3Info(tag, ID3_FRAME_ALBUM_ARTIST,
TAG_ITEM_ALBUM_ARTIST, ret);
getID3Info(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
TAG_ITEM_ALBUM_ARTIST, ret);
getID3Info(tag, ID3_FRAME_TITLE, TAG_ITEM_TITLE, ret);
getID3Info(tag, ID3_FRAME_ALBUM, TAG_ITEM_ALBUM, ret);
getID3Info(tag, ID3_FRAME_TRACK, TAG_ITEM_TRACK, ret);
getID3Info(tag, ID3_FRAME_YEAR, TAG_ITEM_DATE, ret);
getID3Info(tag, ID3_FRAME_GENRE, TAG_ITEM_GENRE, ret);
getID3Info(tag, ID3_FRAME_COMPOSER, TAG_ITEM_COMPOSER, ret);
getID3Info(tag, ID3_FRAME_PERFORMER, TAG_ITEM_PERFORMER, ret);
getID3Info(tag, ID3_FRAME_COMMENT, TAG_ITEM_COMMENT, ret);
getID3Info(tag, ID3_FRAME_DISC, TAG_ITEM_DISC, ret);
tag_id3_import_musicbrainz(ret, tag);
if (tag_is_empty(ret)) {
tag_free(ret);
ret = NULL;
}
return ret;
}
......
......@@ -36,6 +36,11 @@
#include "main.h"
#include "config.h"
#ifdef ENABLE_SQLITE
#include "sticker.h"
#include "song_sticker.h"
#endif
#include <glib.h>
#include <assert.h>
......@@ -127,6 +132,9 @@ delete_each_song(struct song *song, G_GNUC_UNUSED void *data)
return 0;
}
static void
delete_directory(struct directory *directory);
/**
* Recursively remove all sub directories and songs from a directory,
* leaving an empty directory.
......@@ -137,8 +145,9 @@ clear_directory(struct directory *directory)
int i;
for (i = directory->children.nr; --i >= 0;)
clear_directory(directory->children.base[i]);
dirvec_clear(&directory->children);
delete_directory(directory->children.base[i]);
assert(directory->children.nr == 0);
songvec_for_each(&directory->songs, delete_each_song, directory);
}
......@@ -220,7 +229,7 @@ removeDeletedFromDirectory(struct directory *directory)
continue;
g_debug("removing directory: %s", dv->base[i]->path);
dirvec_delete(dv, dv->base[i]);
delete_directory(dv->base[i]);
modified = true;
}
......@@ -614,7 +623,7 @@ addParentPathToDB(const char *utf8path)
*slash++ = '/';
}
free(duplicated);
g_free(duplicated);
return directory;
}
......@@ -643,7 +652,7 @@ static void * update_task(void *_path)
{
if (_path != NULL && !isRootDirectory(_path)) {
updatePath((char *)_path);
free(_path);
g_free(_path);
} else {
struct directory *directory = db_get_root();
struct stat st;
......@@ -680,12 +689,14 @@ directory_update_init(char *path)
{
assert(g_thread_self() == main_task);
if (!mapper_has_music_directory())
return 0;
if (progress != UPDATE_PROGRESS_IDLE) {
unsigned next_task_id;
if (update_paths_nr == G_N_ELEMENTS(update_paths)) {
if (path)
free(path);
g_free(path);
return 0;
}
......@@ -714,7 +725,13 @@ static void song_delete_event(void)
g_debug("removing: %s", uri);
g_free(uri);
deleteASongFromPlaylist(delete);
#ifdef ENABLE_SQLITE
/* if the song has a sticker, delete it */
if (sticker_enabled())
sticker_song_delete(delete);
#endif
deleteASongFromPlaylist(&g_playlist, delete);
delete = NULL;
notify_signal(&update_notify);
......@@ -731,7 +748,7 @@ static void update_finished_event(void)
if (modified) {
/* send "idle" events */
playlistVersionChange();
playlistVersionChange(&g_playlist);
idle_add(IDLE_DATABASE);
}
......
......@@ -65,12 +65,11 @@ char *parsePath(char *path)
const char *home;
if (path[1] == '/' || path[1] == '\0') {
struct config_param *param = config_get_param(CONF_USER);
if (param && param->value) {
struct passwd *passwd = getpwnam(param->value);
const char *user = config_get_string(CONF_USER, NULL);
if (user != NULL) {
struct passwd *passwd = getpwnam(user);
if (!passwd) {
g_warning("no such user %s",
param->value);
g_warning("no such user %s", user);
return NULL;
}
......
......@@ -23,6 +23,7 @@
#include "pcm_volume.h"
#include "config.h"
#include "audio.h"
#include "output_all.h"
#include <glib.h>
......@@ -47,37 +48,85 @@ void volume_finish(void)
{
}
/**
* Finds the first audio_output configuration section with the
* specified type.
*/
static struct config_param *
find_output_config(const char *type)
{
struct config_param *param = NULL;
while ((param = config_get_next_param(CONF_AUDIO_OUTPUT,
param)) != NULL) {
const char *param_type =
config_get_block_string(param, "type", NULL);
if (param_type != NULL && strcmp(param_type, type) == 0)
return param;
}
return NULL;
}
/**
* Copy a (top-level) legacy mixer configuration parameter to the
* audio_output section.
*/
static void
mixer_reconfigure(char *driver)
mixer_copy_legacy_param(const char *type, const char *name)
{
struct config_param *newparam, *param;
const struct config_param *param;
struct config_param *output;
const struct block_param *bp;
//create parameter list
newparam = newConfigParam(NULL, -1);
/* see if the deprecated configuration exists */
param = config_get_param(CONF_MIXER_DEVICE);
if (param) {
g_warning("deprecated option mixer_device found, translating to %s config section\n", driver);
addBlockParam(newparam, "mixer_device", param->value, -1);
}
param = config_get_param(CONF_MIXER_CONTROL);
if (param) {
g_warning("deprecated option mixer_control found, translating to %s config section\n", driver);
addBlockParam(newparam, "mixer_control", param->value, -1);
}
if (newparam->num_block_params > 0) {
//call configure method of corrensponding mixer
if (!mixer_configure_legacy(driver, newparam)) {
g_error("Using mixer_type '%s' with not enabled %s output", driver, driver);
}
param = config_get_param(name);
if (param == NULL)
return;
g_warning("deprecated option '%s' found, moving to '%s' audio output",
name, type);
/* determine the configuration section */
output = find_output_config(type);
if (output == NULL) {
/* if there is no output configuration at all, create
a new and empty configuration section for the
legacy mixer */
if (config_get_next_param(CONF_AUDIO_OUTPUT, NULL) != NULL)
/* there is an audio_output configuration, but
it does not match the mixer_type setting */
g_error("no '%s' audio output found", type);
output = newConfigParam(NULL, param->line);
addBlockParam(output, "type", type, param->line);
addBlockParam(output, "name", type, param->line);
config_add_param(CONF_AUDIO_OUTPUT, output);
}
//free parameter list
config_param_free(newparam, NULL);
bp = getBlockParam(output, name);
if (bp != NULL)
g_error("the '%s' audio output already has a '%s' setting",
type, name);
/* duplicate the parameter in the configuration section */
addBlockParam(output, name, param->value, param->line);
}
static void
mixer_reconfigure(const char *type)
{
mixer_copy_legacy_param(type, CONF_MIXER_DEVICE);
mixer_copy_legacy_param(type, CONF_MIXER_CONTROL);
}
void volume_init(void)
{
struct config_param *param = config_get_param(CONF_MIXER_TYPE);
const struct config_param *param = config_get_param(CONF_MIXER_TYPE);
//hw mixing is by default
if (param) {
if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) {
......
......@@ -34,18 +34,14 @@ static int zeroconfEnabled;
void initZeroconf(void)
{
const char *serviceName = SERVICE_NAME;
struct config_param *param;
const char *serviceName;
zeroconfEnabled = config_get_bool(CONF_ZEROCONF_ENABLED,
DEFAULT_ZEROCONF_ENABLED);
if (!zeroconfEnabled)
return;
param = config_get_param(CONF_ZEROCONF_NAME);
if (param && *param->value != 0)
serviceName = param->value;
serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME);
#ifdef HAVE_AVAHI
init_avahi(serviceName);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment