Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76e3dec723 | ||
|
|
ba6ef53ef9 | ||
|
|
c93a28c641 | ||
|
|
7088a679a2 | ||
|
|
04c02a1eb8 | ||
|
|
41487426f5 | ||
|
|
0d24250aa7 | ||
|
|
2050e2f886 | ||
|
|
013e8479af | ||
|
|
27535a7f78 | ||
|
|
acaa725478 | ||
|
|
f351550534 | ||
|
|
66ecf39efe | ||
|
|
5ad21d7e98 | ||
|
|
ef5125f8f4 | ||
|
|
bf2e07074b | ||
|
|
20695ef369 | ||
|
|
9374e0f445 | ||
|
|
19ed233118 | ||
|
|
faa4fff4dd | ||
|
|
2276e7677b | ||
|
|
93f9c2ab6b | ||
|
|
4a993cd79e | ||
|
|
02325d2ede | ||
|
|
9c83464b95 | ||
|
|
b1bbd70f0f | ||
|
|
c31d11bfe0 | ||
|
|
c8ec85d649 | ||
|
|
e291f3d257 | ||
|
|
dc22846d58 | ||
|
|
c9aaabb5d4 | ||
|
|
335d5d5d72 | ||
|
|
51d793bec1 | ||
|
|
249dcd967e | ||
|
|
302972e9fc | ||
|
|
31b380b266 | ||
|
|
a869dfea85 | ||
|
|
12838c6294 | ||
|
|
49c7102547 | ||
|
|
1ae8972859 | ||
|
|
adcd2c8eac | ||
|
|
45ff355835 | ||
|
|
f8bf3afeae | ||
|
|
f703da1516 | ||
|
|
a582deee2c | ||
|
|
12be9e818f | ||
|
|
281cd7c057 |
2
INSTALL
2
INSTALL
@@ -103,7 +103,7 @@ libsidplay2 - http://sidplay2.sourceforge.net/
|
|||||||
For C64 SID support.
|
For C64 SID support.
|
||||||
|
|
||||||
libfluidsynth - http://fluidsynth.resonance.org/
|
libfluidsynth - http://fluidsynth.resonance.org/
|
||||||
For MIDI support (DO NOT USE - use libwildmidi instead)
|
For MIDI support.
|
||||||
|
|
||||||
libwildmidi - http://wildmidi.sourceforge.net/
|
libwildmidi - http://wildmidi.sourceforge.net/
|
||||||
For MIDI support.
|
For MIDI support.
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ src_mpd_SOURCES = \
|
|||||||
$(OUTPUT_API_SRC) \
|
$(OUTPUT_API_SRC) \
|
||||||
$(MIXER_API_SRC) \
|
$(MIXER_API_SRC) \
|
||||||
src/glib_socket.h \
|
src/glib_socket.h \
|
||||||
|
src/clock.c src/clock.h \
|
||||||
src/notify.c \
|
src/notify.c \
|
||||||
src/audio_config.c src/audio_config.h \
|
src/audio_config.c src/audio_config.h \
|
||||||
src/audio_check.c \
|
src/audio_check.c \
|
||||||
@@ -1069,7 +1070,6 @@ test_dump_playlist_SOURCES = test/dump_playlist.c \
|
|||||||
src/audio_check.c src/pcm_buffer.c \
|
src/audio_check.c src/pcm_buffer.c \
|
||||||
src/text_input_stream.c src/fifo_buffer.c \
|
src/text_input_stream.c src/fifo_buffer.c \
|
||||||
src/cue/cue_parser.c src/cue/cue_parser.h \
|
src/cue/cue_parser.c src/cue/cue_parser.h \
|
||||||
src/timer.c \
|
|
||||||
src/fd_util.c
|
src/fd_util.c
|
||||||
|
|
||||||
if HAVE_FLAC
|
if HAVE_FLAC
|
||||||
@@ -1096,7 +1096,6 @@ test_run_decoder_SOURCES = test/run_decoder.c \
|
|||||||
src/fd_util.c \
|
src/fd_util.c \
|
||||||
src/audio_check.c \
|
src/audio_check.c \
|
||||||
src/audio_format.c \
|
src/audio_format.c \
|
||||||
src/timer.c \
|
|
||||||
$(ARCHIVE_SRC) \
|
$(ARCHIVE_SRC) \
|
||||||
$(INPUT_SRC) \
|
$(INPUT_SRC) \
|
||||||
$(TAG_SRC) \
|
$(TAG_SRC) \
|
||||||
@@ -1118,7 +1117,6 @@ test_read_tags_SOURCES = test/read_tags.c \
|
|||||||
src/uri.c \
|
src/uri.c \
|
||||||
src/fd_util.c \
|
src/fd_util.c \
|
||||||
src/audio_check.c \
|
src/audio_check.c \
|
||||||
src/timer.c \
|
|
||||||
$(DECODER_SRC)
|
$(DECODER_SRC)
|
||||||
|
|
||||||
if HAVE_ID3TAG
|
if HAVE_ID3TAG
|
||||||
@@ -1240,7 +1238,7 @@ test_run_output_SOURCES = test/run_output.c \
|
|||||||
src/audio_check.c \
|
src/audio_check.c \
|
||||||
src/audio_format.c \
|
src/audio_format.c \
|
||||||
src/audio_parser.c \
|
src/audio_parser.c \
|
||||||
src/timer.c \
|
src/timer.c src/clock.c \
|
||||||
src/tag.c src/tag_pool.c \
|
src/tag.c src/tag_pool.c \
|
||||||
src/fifo_buffer.c src/growing_fifo.c \
|
src/fifo_buffer.c src/growing_fifo.c \
|
||||||
src/page.c \
|
src/page.c \
|
||||||
|
|||||||
24
NEWS
24
NEWS
@@ -1,3 +1,27 @@
|
|||||||
|
ver 0.17.2 (2012/09/30)
|
||||||
|
* protocol:
|
||||||
|
- fix crash in local file check
|
||||||
|
* decoder:
|
||||||
|
- fluidsynth: remove throttle (requires libfluidsynth 1.1)
|
||||||
|
- fluidsynth: stop playback at end of file
|
||||||
|
- fluidsynth: check MIDI file format while scanning
|
||||||
|
- fluidsynth: add sample rate setting
|
||||||
|
- wavpack: support all APEv2 tags
|
||||||
|
* output:
|
||||||
|
- httpd: use monotonic clock, avoid hiccups after system clock adjustment
|
||||||
|
- httpd: fix throttling bug after resuming playback
|
||||||
|
* playlist:
|
||||||
|
- cue: map "PERFORMER" to "artist" or "album artist"
|
||||||
|
* mapper: fix non-UTF8 music directory name
|
||||||
|
* mapper: fix potential crash in file permission check
|
||||||
|
* playlist: fix use-after-free bug
|
||||||
|
* playlist: fix memory leak
|
||||||
|
* state_file: save song priorities
|
||||||
|
* player: disable cross-fading in "single" mode
|
||||||
|
* update: fix unsafe readlink() usage
|
||||||
|
* configure.ac:
|
||||||
|
- don't auto-detect the vorbis encoder when Tremor is enabled
|
||||||
|
|
||||||
ver 0.17.1 (2012/07/31)
|
ver 0.17.1 (2012/07/31)
|
||||||
* protocol:
|
* protocol:
|
||||||
- require appropriate permissions for searchadd{,pl}
|
- require appropriate permissions for searchadd{,pl}
|
||||||
|
|||||||
13
configure.ac
13
configure.ac
@@ -1,6 +1,6 @@
|
|||||||
AC_PREREQ(2.60)
|
AC_PREREQ(2.60)
|
||||||
|
|
||||||
AC_INIT(mpd, 0.17.1, musicpd-dev-team@lists.sourceforge.net)
|
AC_INIT(mpd, 0.17.2, musicpd-dev-team@lists.sourceforge.net)
|
||||||
|
|
||||||
VERSION_MAJOR=0
|
VERSION_MAJOR=0
|
||||||
VERSION_MINOR=17
|
VERSION_MINOR=17
|
||||||
@@ -217,8 +217,8 @@ AC_ARG_ENABLE(flac,
|
|||||||
|
|
||||||
AC_ARG_ENABLE(fluidsynth,
|
AC_ARG_ENABLE(fluidsynth,
|
||||||
AS_HELP_STRING([--enable-fluidsynth],
|
AS_HELP_STRING([--enable-fluidsynth],
|
||||||
[enable MIDI support via fluidsynth (default: disable)]),,
|
[enable MIDI support via fluidsynth (default: auto)]),,
|
||||||
enable_fluidsynth=no)
|
enable_fluidsynth=auto)
|
||||||
|
|
||||||
AC_ARG_ENABLE(gme,
|
AC_ARG_ENABLE(gme,
|
||||||
AS_HELP_STRING([--enable-gme],
|
AS_HELP_STRING([--enable-gme],
|
||||||
@@ -845,7 +845,7 @@ enable_flac_encoder=$enable_flac
|
|||||||
|
|
||||||
dnl -------------------------------- FluidSynth -------------------------------
|
dnl -------------------------------- FluidSynth -------------------------------
|
||||||
if test x$enable_fluidsynth = xyes; then
|
if test x$enable_fluidsynth = xyes; then
|
||||||
PKG_CHECK_MODULES(FLUIDSYNTH, [fluidsynth],
|
PKG_CHECK_MODULES(FLUIDSYNTH, [fluidsynth >= 1.1],
|
||||||
AC_DEFINE(ENABLE_FLUIDSYNTH, 1, [Define for fluidsynth support]),
|
AC_DEFINE(ENABLE_FLUIDSYNTH, 1, [Define for fluidsynth support]),
|
||||||
enable_fluidsynth=no)
|
enable_fluidsynth=no)
|
||||||
fi
|
fi
|
||||||
@@ -999,6 +999,11 @@ if test x$enable_tremor = xyes; then
|
|||||||
AC_MSG_WARN(["OggTremor detected, could not enable Vorbis."])
|
AC_MSG_WARN(["OggTremor detected, could not enable Vorbis."])
|
||||||
fi
|
fi
|
||||||
enable_vorbis=no
|
enable_vorbis=no
|
||||||
|
|
||||||
|
if test x$enable_vorbis_encoder = xauto; then
|
||||||
|
AC_MSG_WARN([OggTremor detected, disabling the Vorbis encoder plugin.])
|
||||||
|
enable_vorbis_encoder=no
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
MPD_AUTO_PKG(vorbis, VORBIS, [vorbis vorbisfile ogg],
|
MPD_AUTO_PKG(vorbis, VORBIS, [vorbis vorbisfile ogg],
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ default is 5.
|
|||||||
.TP
|
.TP
|
||||||
.B max_playlist_length <number>
|
.B max_playlist_length <number>
|
||||||
This specifies the maximum number of songs that can be in the playlist. The
|
This specifies the maximum number of songs that can be in the playlist. The
|
||||||
default is 4096.
|
default is 16384.
|
||||||
.TP
|
.TP
|
||||||
.B max_command_list_size <size in KiB>
|
.B max_command_list_size <size in KiB>
|
||||||
This specifies the maximum size a command list can be. The default is 2048.
|
This specifies the maximum size a command list can be. The default is 2048.
|
||||||
@@ -252,11 +252,12 @@ when saving playlists. The default is "no".
|
|||||||
This specifies the tag types that will be scanned for and made available to
|
This specifies the tag types that will be scanned for and made available to
|
||||||
clients. Note that you must recreate (not update) your database for changes to
|
clients. Note that you must recreate (not update) your database for changes to
|
||||||
this parameter to take effect. Possible values are artist, album, title,
|
this parameter to take effect. Possible values are artist, album, title,
|
||||||
track, name, genre, date, composer, performer, comment, and disc. Multiple
|
track, name, genre, date, composer, performer, comment, disc,
|
||||||
tags may be specified as a comma separated list. An example value is
|
musicbrainz_artistid, musicbrainz_albumid, musicbrainz_albumartistid,
|
||||||
"artist,album,title,track". The special value "none" may be used alone to
|
musicbrainz_trackid. Multiple tags may be specified as a comma separated list.
|
||||||
disable all metadata. The default is to use all known tag types except for
|
An example value is "artist,album,title,track". The special value "none" may
|
||||||
comments.
|
be used alone to disable all metadata. The default is to use all known tag
|
||||||
|
types except for comments and those starting with "musicbrainz".
|
||||||
.TP
|
.TP
|
||||||
.B auto_update <yes or no>
|
.B auto_update <yes or no>
|
||||||
This specifies the wheter to support automatic update of music database when
|
This specifies the wheter to support automatic update of music database when
|
||||||
|
|||||||
@@ -114,9 +114,8 @@
|
|||||||
#save_absolute_paths_in_playlists "no"
|
#save_absolute_paths_in_playlists "no"
|
||||||
#
|
#
|
||||||
# This setting defines a list of tag types that will be extracted during the
|
# This setting defines a list of tag types that will be extracted during the
|
||||||
# audio file discovery process. Optionally, 'comment' can be added to this
|
# audio file discovery process. The complete list of possible values can be
|
||||||
# list.
|
# found in the mpd.conf man page.
|
||||||
#
|
|
||||||
#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
|
#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
|
||||||
#
|
#
|
||||||
# This setting enables automatic update of MPD's database when files in
|
# This setting enables automatic update of MPD's database when files in
|
||||||
|
|||||||
71
doc/user.xml
71
doc/user.xml
@@ -818,6 +818,46 @@ systemctl start mpd.socket</programlisting>
|
|||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title><varname>fluidsynth</varname></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
MIDI decoder based on libfluidsynth.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<informaltable>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Setting</entry>
|
||||||
|
<entry>Description</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>sample_rate</varname>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
The sample rate that shall be synthesized by the
|
||||||
|
plugin. Defaults to 48000.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>soundfont</varname>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
The absolute path of the soundfont file. Defaults
|
||||||
|
to
|
||||||
|
<filename>/usr/share/sounds/sf2/FluidR3_GM.sf2</filename>.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</informaltable>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<title><varname>mikmod</varname></title>
|
<title><varname>mikmod</varname></title>
|
||||||
|
|
||||||
@@ -847,6 +887,37 @@ systemctl start mpd.socket</programlisting>
|
|||||||
</tgroup>
|
</tgroup>
|
||||||
</informaltable>
|
</informaltable>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title><varname>wildmidi</varname></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
MIDI decoder based on libwildmidi.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<informaltable>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Setting</entry>
|
||||||
|
<entry>Description</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>config_file</varname>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
The absolute path of the timidity config file. Defaults
|
||||||
|
to
|
||||||
|
<filename>/etc/timidity/timidity.cfg</filename>.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</informaltable>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ struct Compressor {
|
|||||||
struct Compressor *Compressor_new(unsigned int history)
|
struct Compressor *Compressor_new(unsigned int history)
|
||||||
{
|
{
|
||||||
struct Compressor *obj = malloc(sizeof(struct Compressor));
|
struct Compressor *obj = malloc(sizeof(struct Compressor));
|
||||||
|
if (obj == NULL)
|
||||||
|
/* out of memory, not much we can do */
|
||||||
|
abort();
|
||||||
|
|
||||||
obj->prefs.target = TARGET;
|
obj->prefs.target = TARGET;
|
||||||
obj->prefs.maxgain = GAINMAX;
|
obj->prefs.maxgain = GAINMAX;
|
||||||
@@ -61,6 +64,10 @@ void Compressor_delete(struct Compressor *obj)
|
|||||||
static int *resizeArray(int *data, int newsz, int oldsz)
|
static int *resizeArray(int *data, int newsz, int oldsz)
|
||||||
{
|
{
|
||||||
data = realloc(data, newsz*sizeof(int));
|
data = realloc(data, newsz*sizeof(int));
|
||||||
|
if (data == NULL)
|
||||||
|
/* out of memory, not much we can do */
|
||||||
|
abort();
|
||||||
|
|
||||||
if (newsz > oldsz)
|
if (newsz > oldsz)
|
||||||
memset(data + oldsz, 0, sizeof(int)*(newsz - oldsz));
|
memset(data + oldsz, 0, sizeof(int)*(newsz - oldsz));
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ struct client;
|
|||||||
* @param path_fs the absolute path name in filesystem encoding
|
* @param path_fs the absolute path name in filesystem encoding
|
||||||
* @return true if access is allowed
|
* @return true if access is allowed
|
||||||
*/
|
*/
|
||||||
G_GNUC_PURE
|
|
||||||
bool
|
bool
|
||||||
client_allow_file(const struct client *client, const char *path_fs,
|
client_allow_file(const struct client *client, const char *path_fs,
|
||||||
GError **error_r);
|
GError **error_r);
|
||||||
|
|||||||
95
src/clock.c
Normal file
95
src/clock.c
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "clock.h"
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#include <mach/mach_time.h>
|
||||||
|
#else
|
||||||
|
#include <time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
unsigned
|
||||||
|
monotonic_clock_ms(void)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
return GetTickCount();
|
||||||
|
#elif defined(__APPLE__) /* OS X does not define CLOCK_MONOTONIC */
|
||||||
|
static mach_timebase_info_data_t base;
|
||||||
|
if (base.denom == 0)
|
||||||
|
(void)mach_timebase_info(&base);
|
||||||
|
|
||||||
|
return (unsigned)((mach_absolute_time() * base.numer)
|
||||||
|
/ (1000000 * base.denom));
|
||||||
|
#elif defined(CLOCK_MONOTONIC)
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
#else
|
||||||
|
/* we have no monotonic clock, fall back to gettimeofday() */
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, 0);
|
||||||
|
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t
|
||||||
|
monotonic_clock_us(void)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
LARGE_INTEGER l_value, l_frequency;
|
||||||
|
|
||||||
|
if (!QueryPerformanceCounter(&l_value) ||
|
||||||
|
!QueryPerformanceFrequency(&l_frequency))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint64_t value = l_value.QuadPart;
|
||||||
|
uint64_t frequency = l_frequency.QuadPart;
|
||||||
|
|
||||||
|
if (frequency > 1000000) {
|
||||||
|
value *= 10000;
|
||||||
|
value /= frequency / 100;
|
||||||
|
} else if (frequency < 1000000) {
|
||||||
|
value *= 10000;
|
||||||
|
value /= frequency;
|
||||||
|
value *= 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
#elif defined(__APPLE__) /* OS X does not define CLOCK_MONOTONIC */
|
||||||
|
static mach_timebase_info_data_t base;
|
||||||
|
if (base.denom == 0)
|
||||||
|
(void)mach_timebase_info(&base);
|
||||||
|
|
||||||
|
return ((uint64_t)mach_absolute_time() * (uint64_t)base.numer)
|
||||||
|
/ (1000 * (uint64_t)base.denom);
|
||||||
|
#elif defined(CLOCK_MONOTONIC)
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (uint64_t)ts.tv_sec * 1000000 + (uint64_t)(ts.tv_nsec / 1000);
|
||||||
|
#else
|
||||||
|
/* we have no monotonic clock, fall back to gettimeofday() */
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, 0);
|
||||||
|
return (uint64_t)tv.tv_sec * 1000 + (uint64_t)(tv.tv_usec) / 1000(;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
41
src/clock.h
Normal file
41
src/clock.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_CLOCK_H
|
||||||
|
#define MPD_CLOCK_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of a monotonic clock in milliseconds.
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
|
unsigned
|
||||||
|
monotonic_clock_ms(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of a monotonic clock in microseconds.
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
|
uint64_t
|
||||||
|
monotonic_clock_us(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1069,7 +1069,7 @@ handle_next(G_GNUC_UNUSED struct client *client,
|
|||||||
{
|
{
|
||||||
/* single mode is not considered when this is user who
|
/* single mode is not considered when this is user who
|
||||||
* wants to change song. */
|
* wants to change song. */
|
||||||
int single = g_playlist.queue.single;
|
const bool single = g_playlist.queue.single;
|
||||||
g_playlist.queue.single = false;
|
g_playlist.queue.single = false;
|
||||||
|
|
||||||
playlist_next(&g_playlist, client->player_control);
|
playlist_next(&g_playlist, client->player_control);
|
||||||
@@ -1544,7 +1544,7 @@ handle_config(struct client *client,
|
|||||||
return COMMAND_RETURN_ERROR;
|
return COMMAND_RETURN_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *path = mapper_get_music_directory();
|
const char *path = mapper_get_music_directory_utf8();
|
||||||
if (path != NULL)
|
if (path != NULL)
|
||||||
client_printf(client, "music_directory: %s\n", path);
|
client_printf(client, "music_directory: %s\n", path);
|
||||||
|
|
||||||
|
|||||||
@@ -216,9 +216,19 @@ cue_parser_feed2(struct cue_parser *parser, char *p)
|
|||||||
if (tag != NULL)
|
if (tag != NULL)
|
||||||
cue_parse_rem(p, tag);
|
cue_parse_rem(p, tag);
|
||||||
} else if (strcmp(command, "PERFORMER") == 0) {
|
} else if (strcmp(command, "PERFORMER") == 0) {
|
||||||
|
/* MPD knows a "performer" tag, but it is not a good
|
||||||
|
match for this CUE tag; from the Hydrogenaudio
|
||||||
|
Knowledgebase: "At top-level this will specify the
|
||||||
|
CD artist, while at track-level it specifies the
|
||||||
|
track artist." */
|
||||||
|
|
||||||
|
enum tag_type type = parser->state == TRACK
|
||||||
|
? TAG_ARTIST
|
||||||
|
: TAG_ALBUM_ARTIST;
|
||||||
|
|
||||||
struct tag *tag = cue_current_tag(parser);
|
struct tag *tag = cue_current_tag(parser);
|
||||||
if (tag != NULL)
|
if (tag != NULL)
|
||||||
cue_add_tag(tag, TAG_PERFORMER, p);
|
cue_add_tag(tag, type, p);
|
||||||
} else if (strcmp(command, "TITLE") == 0) {
|
} else if (strcmp(command, "TITLE") == 0) {
|
||||||
if (parser->state == HEADER)
|
if (parser->state == HEADER)
|
||||||
cue_add_tag(parser->tag, TAG_ALBUM, p);
|
cue_add_tag(parser->tag, TAG_ALBUM, p);
|
||||||
|
|||||||
@@ -124,11 +124,10 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
|
|||||||
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
|
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
|
||||||
if (flac_parse_replay_gain(&rgi, block))
|
if (flac_parse_replay_gain(&rgi, block))
|
||||||
replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
|
replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
|
||||||
if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) {
|
|
||||||
g_debug("setting mixramp_tags");
|
if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block))
|
||||||
decoder_mixramp(data->decoder, replay_gain_db,
|
decoder_mixramp(data->decoder, replay_gain_db,
|
||||||
mixramp_start, mixramp_end);
|
mixramp_start, mixramp_end);
|
||||||
}
|
|
||||||
|
|
||||||
if (data->tag != NULL)
|
if (data->tag != NULL)
|
||||||
flac_vorbis_comments_to_tag(data->tag, NULL,
|
flac_vorbis_comments_to_tag(data->tag, NULL,
|
||||||
|
|||||||
@@ -33,12 +33,14 @@ ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream)
|
|||||||
size_t r;
|
size_t r;
|
||||||
|
|
||||||
r = decoder_read(NULL, inStream, buf, sizeof(buf));
|
r = decoder_read(NULL, inStream, buf, sizeof(buf));
|
||||||
if (r >= 32 && memcmp(buf, "OggS", 4) == 0 && (
|
if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0)
|
||||||
(memcmp(buf+29, "FLAC", 4) == 0
|
return VORBIS;
|
||||||
&& memcmp(buf+37, "fLaC", 4) == 0)
|
|
||||||
|| (memcmp(buf+28, "FLAC", 4) == 0)
|
if ((memcmp(buf + 29, "FLAC", 4) == 0 &&
|
||||||
|| (memcmp(buf+28, "fLaC", 4) == 0))) {
|
memcmp(buf + 37, "fLaC", 4) == 0) ||
|
||||||
|
memcmp(buf + 28, "FLAC", 4) == 0 ||
|
||||||
|
memcmp(buf + 28, "fLaC", 4) == 0)
|
||||||
return FLAC;
|
return FLAC;
|
||||||
}
|
|
||||||
return VORBIS;
|
return VORBIS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,18 +17,9 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
|
||||||
* WARNING! This plugin suffers from major shortcomings in the
|
|
||||||
* libfluidsynth API, which render it practically unusable. For a
|
|
||||||
* discussion, see the post on the fluidsynth mailing list:
|
|
||||||
*
|
|
||||||
* http://www.mail-archive.com/fluid-dev@nongnu.org/msg01099.html
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "decoder_api.h"
|
#include "decoder_api.h"
|
||||||
#include "timer.h"
|
#include "audio_check.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
@@ -38,6 +29,9 @@
|
|||||||
#undef G_LOG_DOMAIN
|
#undef G_LOG_DOMAIN
|
||||||
#define G_LOG_DOMAIN "fluidsynth"
|
#define G_LOG_DOMAIN "fluidsynth"
|
||||||
|
|
||||||
|
static unsigned sample_rate;
|
||||||
|
static const char *soundfont_path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a fluidsynth log level to a GLib log level.
|
* Convert a fluidsynth log level to a GLib log level.
|
||||||
*/
|
*/
|
||||||
@@ -75,8 +69,21 @@ fluidsynth_mpd_log_function(int level, char *message, G_GNUC_UNUSED void *data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
fluidsynth_init(G_GNUC_UNUSED const struct config_param *param)
|
fluidsynth_init(const struct config_param *param)
|
||||||
{
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
sample_rate = config_get_block_unsigned(param, "sample_rate", 48000);
|
||||||
|
if (!audio_check_sample_rate(sample_rate, &error)) {
|
||||||
|
g_warning("%s\n", error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
soundfont_path =
|
||||||
|
config_get_block_string(param, "soundfont",
|
||||||
|
"/usr/share/sounds/sf2/FluidR3_GM.sf2");
|
||||||
|
|
||||||
fluid_set_log_function(LAST_LOG_LEVEL,
|
fluid_set_log_function(LAST_LOG_LEVEL,
|
||||||
fluidsynth_mpd_log_function, NULL);
|
fluidsynth_mpd_log_function, NULL);
|
||||||
|
|
||||||
@@ -86,36 +93,24 @@ fluidsynth_init(G_GNUC_UNUSED const struct config_param *param)
|
|||||||
static void
|
static void
|
||||||
fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
|
fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
|
||||||
{
|
{
|
||||||
static const struct audio_format audio_format = {
|
|
||||||
.sample_rate = 48000,
|
|
||||||
.format = SAMPLE_FORMAT_S16,
|
|
||||||
.channels = 2,
|
|
||||||
};
|
|
||||||
char setting_sample_rate[] = "synth.sample-rate";
|
char setting_sample_rate[] = "synth.sample-rate";
|
||||||
/*
|
/*
|
||||||
char setting_verbose[] = "synth.verbose";
|
char setting_verbose[] = "synth.verbose";
|
||||||
char setting_yes[] = "yes";
|
char setting_yes[] = "yes";
|
||||||
*/
|
*/
|
||||||
const char *soundfont_path;
|
|
||||||
fluid_settings_t *settings;
|
fluid_settings_t *settings;
|
||||||
fluid_synth_t *synth;
|
fluid_synth_t *synth;
|
||||||
fluid_player_t *player;
|
fluid_player_t *player;
|
||||||
char *path_dup;
|
|
||||||
int ret;
|
int ret;
|
||||||
struct timer *timer;
|
|
||||||
enum decoder_command cmd;
|
enum decoder_command cmd;
|
||||||
|
|
||||||
soundfont_path =
|
|
||||||
config_get_string("soundfont",
|
|
||||||
"/usr/share/sounds/sf2/FluidR3_GM.sf2");
|
|
||||||
|
|
||||||
/* set up fluid settings */
|
/* set up fluid settings */
|
||||||
|
|
||||||
settings = new_fluid_settings();
|
settings = new_fluid_settings();
|
||||||
if (settings == NULL)
|
if (settings == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fluid_settings_setnum(settings, setting_sample_rate, 48000);
|
fluid_settings_setnum(settings, setting_sample_rate, sample_rate);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
fluid_settings_setstr(settings, setting_verbose, setting_yes);
|
fluid_settings_setstr(settings, setting_verbose, setting_yes);
|
||||||
@@ -146,11 +141,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* temporarily duplicate the path_fs string, because
|
ret = fluid_player_add(player, path_fs);
|
||||||
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) {
|
if (ret != 0) {
|
||||||
g_warning("fluid_player_add() failed");
|
g_warning("fluid_player_add() failed");
|
||||||
delete_fluid_player(player);
|
delete_fluid_player(player);
|
||||||
@@ -170,47 +161,34 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
|
|||||||
return;
|
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
|
/* initialization complete - announce the audio format to the
|
||||||
MPD core */
|
MPD core */
|
||||||
|
|
||||||
|
struct audio_format audio_format;
|
||||||
|
audio_format_init(&audio_format, sample_rate, SAMPLE_FORMAT_S16, 2);
|
||||||
decoder_initialized(decoder, &audio_format, false, -1);
|
decoder_initialized(decoder, &audio_format, false, -1);
|
||||||
|
|
||||||
do {
|
while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) {
|
||||||
int16_t buffer[2048];
|
int16_t buffer[2048];
|
||||||
const unsigned max_frames = G_N_ELEMENTS(buffer) / 2;
|
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
|
/* read samples from fluidsynth and send them to the
|
||||||
MPD core */
|
MPD core */
|
||||||
|
|
||||||
ret = fluid_synth_write_s16(synth, max_frames,
|
ret = fluid_synth_write_s16(synth, max_frames,
|
||||||
buffer, 0, 2,
|
buffer, 0, 2,
|
||||||
buffer, 1, 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)
|
if (ret != 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer),
|
cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer),
|
||||||
0);
|
0);
|
||||||
} while (cmd == DECODE_COMMAND_NONE);
|
if (cmd != DECODE_COMMAND_NONE)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* clean up */
|
/* clean up */
|
||||||
|
|
||||||
timer_free(timer);
|
|
||||||
|
|
||||||
fluid_player_stop(player);
|
fluid_player_stop(player);
|
||||||
fluid_player_join(player);
|
fluid_player_join(player);
|
||||||
|
|
||||||
@@ -224,10 +202,7 @@ fluidsynth_scan_file(const char *file,
|
|||||||
G_GNUC_UNUSED const struct tag_handler *handler,
|
G_GNUC_UNUSED const struct tag_handler *handler,
|
||||||
G_GNUC_UNUSED void *handler_ctx)
|
G_GNUC_UNUSED void *handler_ctx)
|
||||||
{
|
{
|
||||||
/* to be implemented */
|
return fluid_is_midifile(file);
|
||||||
(void)file;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *const fluidsynth_suffixes[] = {
|
static const char *const fluidsynth_suffixes[] = {
|
||||||
|
|||||||
@@ -365,11 +365,10 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
|
|||||||
replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
|
replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
|
||||||
data->found_replay_gain = true;
|
data->found_replay_gain = true;
|
||||||
}
|
}
|
||||||
if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag)) {
|
|
||||||
g_debug("setting mixramp_tags");
|
if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag))
|
||||||
decoder_mixramp(data->decoder, replay_gain_db,
|
decoder_mixramp(data->decoder, replay_gain_db,
|
||||||
mixramp_start, mixramp_end);
|
mixramp_start, mixramp_end);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id3_tag_delete(id3_tag);
|
id3_tag_delete(id3_tag);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "tag_table.h"
|
#include "tag_table.h"
|
||||||
#include "tag_handler.h"
|
#include "tag_handler.h"
|
||||||
|
#include "tag_ape.h"
|
||||||
|
|
||||||
#include <wavpack/wavpack.h>
|
#include <wavpack/wavpack.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
@@ -38,21 +39,6 @@
|
|||||||
|
|
||||||
#define ERRORLEN 80
|
#define ERRORLEN 80
|
||||||
|
|
||||||
static const struct tag_table wavpack_tags[] = {
|
|
||||||
{ "artist", TAG_ARTIST },
|
|
||||||
{ "album", TAG_ALBUM },
|
|
||||||
{ "title", TAG_TITLE },
|
|
||||||
{ "track", TAG_TRACK },
|
|
||||||
{ "name", TAG_NAME },
|
|
||||||
{ "genre", TAG_GENRE },
|
|
||||||
{ "date", TAG_DATE },
|
|
||||||
{ "composer", TAG_COMPOSER },
|
|
||||||
{ "performer", TAG_PERFORMER },
|
|
||||||
{ "comment", TAG_COMMENT },
|
|
||||||
{ "disc", TAG_DISC },
|
|
||||||
{ NULL, TAG_NUM_OF_ITEM_TYPES }
|
|
||||||
};
|
|
||||||
|
|
||||||
/** A pointer type for format converter function. */
|
/** A pointer type for format converter function. */
|
||||||
typedef void (*format_samples_t)(
|
typedef void (*format_samples_t)(
|
||||||
int bytes_per_sample,
|
int bytes_per_sample,
|
||||||
@@ -321,7 +307,17 @@ wavpack_scan_file(const char *fname,
|
|||||||
WavpackGetNumSamples(wpc) /
|
WavpackGetNumSamples(wpc) /
|
||||||
WavpackGetSampleRate(wpc));
|
WavpackGetSampleRate(wpc));
|
||||||
|
|
||||||
for (const struct tag_table *i = wavpack_tags; i->name != NULL; ++i)
|
/* the WavPack format implies APEv2 tags, which means we can
|
||||||
|
reuse the mapping from tag_ape.c */
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
|
||||||
|
const char *name = tag_item_names[i];
|
||||||
|
if (name != NULL)
|
||||||
|
wavpack_scan_tag_item(wpc, name, (enum tag_type)i,
|
||||||
|
handler, handler_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const struct tag_table *i = ape_tags; i->name != NULL; ++i)
|
||||||
wavpack_scan_tag_item(wpc, i->name, i->type,
|
wavpack_scan_tag_item(wpc, i->name, i->type,
|
||||||
handler, handler_ctx);
|
handler, handler_ctx);
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,6 @@ dc_mixramp_start(struct decoder_control *dc, char *mixramp_start)
|
|||||||
|
|
||||||
g_free(dc->mixramp_start);
|
g_free(dc->mixramp_start);
|
||||||
dc->mixramp_start = mixramp_start;
|
dc->mixramp_start = mixramp_start;
|
||||||
g_debug("mixramp_start = %s", mixramp_start ? mixramp_start : "NULL");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -179,7 +178,6 @@ dc_mixramp_end(struct decoder_control *dc, char *mixramp_end)
|
|||||||
|
|
||||||
g_free(dc->mixramp_end);
|
g_free(dc->mixramp_end);
|
||||||
dc->mixramp_end = mixramp_end;
|
dc->mixramp_end = mixramp_end;
|
||||||
g_debug("mixramp_end = %s", mixramp_end ? mixramp_end : "NULL");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -189,5 +187,4 @@ dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end)
|
|||||||
|
|
||||||
g_free(dc->mixramp_prev_end);
|
g_free(dc->mixramp_prev_end);
|
||||||
dc->mixramp_prev_end = mixramp_prev_end;
|
dc->mixramp_prev_end = mixramp_prev_end;
|
||||||
g_debug("mixramp_prev_end = %s", mixramp_prev_end ? mixramp_prev_end : "NULL");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -468,7 +468,6 @@ decoder_task(gpointer arg)
|
|||||||
|
|
||||||
switch (dc->command) {
|
switch (dc->command) {
|
||||||
case DECODE_COMMAND_START:
|
case DECODE_COMMAND_START:
|
||||||
g_debug("clearing mixramp tags");
|
|
||||||
dc_mixramp_start(dc, NULL);
|
dc_mixramp_start(dc, NULL);
|
||||||
dc_mixramp_prev_end(dc, dc->mixramp_end);
|
dc_mixramp_prev_end(dc, dc->mixramp_end);
|
||||||
dc->mixramp_end = NULL; /* Don't free, it's copied above. */
|
dc->mixramp_end = NULL; /* Don't free, it's copied above. */
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
#include "pcm_buffer.h"
|
#include "pcm_buffer.h"
|
||||||
#include "pcm_volume.h"
|
#include "pcm_volume.h"
|
||||||
#include "audio_format.h"
|
#include "audio_format.h"
|
||||||
#include "player_control.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ mpd_inotify_callback(int wd, unsigned mask,
|
|||||||
(mask & IN_ISDIR) != 0) {
|
(mask & IN_ISDIR) != 0) {
|
||||||
/* a sub directory was changed: register those in
|
/* a sub directory was changed: register those in
|
||||||
inotify */
|
inotify */
|
||||||
const char *root = mapper_get_music_directory();
|
const char *root = mapper_get_music_directory_fs();
|
||||||
const char *path_fs;
|
const char *path_fs;
|
||||||
char *allocated = NULL;
|
char *allocated = NULL;
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ mpd_inotify_init(unsigned max_depth)
|
|||||||
|
|
||||||
g_debug("initializing inotify");
|
g_debug("initializing inotify");
|
||||||
|
|
||||||
const char *path = mapper_get_music_directory();
|
const char *path = mapper_get_music_directory_fs();
|
||||||
if (path == NULL) {
|
if (path == NULL) {
|
||||||
g_debug("no music directory configured");
|
g_debug("no music directory configured");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -22,16 +22,13 @@
|
|||||||
#include "input_internal.h"
|
#include "input_internal.h"
|
||||||
#include "input_plugin.h"
|
#include "input_plugin.h"
|
||||||
|
|
||||||
|
#include <libavutil/avutil.h>
|
||||||
#include <libavformat/avio.h>
|
#include <libavformat/avio.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#undef G_LOG_DOMAIN
|
#undef G_LOG_DOMAIN
|
||||||
#define G_LOG_DOMAIN "input_ffmpeg"
|
#define G_LOG_DOMAIN "input_ffmpeg"
|
||||||
|
|
||||||
#ifndef AV_VERSION_INT
|
|
||||||
#define AV_VERSION_INT(a, b, c) (a<<16 | b<<8 | c)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct input_ffmpeg {
|
struct input_ffmpeg {
|
||||||
struct input_stream base;
|
struct input_stream base;
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,6 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#if !GLIB_CHECK_VERSION(2,14,0)
|
|
||||||
typedef gint64 goffset;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct input_stream {
|
struct input_stream {
|
||||||
/**
|
/**
|
||||||
* the plugin which implements this input stream
|
* the plugin which implements this input stream
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ locate_tag_search(const struct song *song, enum tag_type type, const char *str)
|
|||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) {
|
if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) {
|
||||||
char *uri = song_get_uri(song);
|
char *uri = song_get_uri(song);
|
||||||
char *p = g_utf8_casefold(uri, -1);
|
char *p = g_utf8_casefold(uri, -1);
|
||||||
g_free(uri);
|
g_free(uri);
|
||||||
@@ -147,7 +147,7 @@ locate_tag_search(const struct song *song, enum tag_type type, const char *str)
|
|||||||
|
|
||||||
for (unsigned i = 0; i < song->tag->num_items && !ret; i++) {
|
for (unsigned i = 0; i < song->tag->num_items && !ret; i++) {
|
||||||
visited_types[song->tag->items[i]->type] = true;
|
visited_types[song->tag->items[i]->type] = true;
|
||||||
if (type != LOCATE_TAG_ANY_TYPE &&
|
if ((int)type != LOCATE_TAG_ANY_TYPE &&
|
||||||
song->tag->items[i]->type != type) {
|
song->tag->items[i]->type != type) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ locate_song_search(const struct song *song,
|
|||||||
static bool
|
static bool
|
||||||
locate_tag_match(const struct song *song, enum tag_type type, const char *str)
|
locate_tag_match(const struct song *song, enum tag_type type, const char *str)
|
||||||
{
|
{
|
||||||
if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) {
|
if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) {
|
||||||
char *uri = song_get_uri(song);
|
char *uri = song_get_uri(song);
|
||||||
bool matches = strcmp(str, uri) == 0;
|
bool matches = strcmp(str, uri) == 0;
|
||||||
g_free(uri);
|
g_free(uri);
|
||||||
@@ -205,7 +205,7 @@ locate_tag_match(const struct song *song, enum tag_type type, const char *str)
|
|||||||
|
|
||||||
for (unsigned i = 0; i < song->tag->num_items; i++) {
|
for (unsigned i = 0; i < song->tag->num_items; i++) {
|
||||||
visited_types[song->tag->items[i]->type] = true;
|
visited_types[song->tag->items[i]->type] = true;
|
||||||
if (type != LOCATE_TAG_ANY_TYPE &&
|
if ((int)type != LOCATE_TAG_ANY_TYPE &&
|
||||||
song->tag->items[i]->type != type) {
|
song->tag->items[i]->type != type) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
56
src/log.c
56
src/log.c
@@ -55,7 +55,7 @@ static const char *log_charset;
|
|||||||
|
|
||||||
static bool stdout_mode = true;
|
static bool stdout_mode = true;
|
||||||
static int out_fd;
|
static int out_fd;
|
||||||
static const char *out_filename;
|
static char *out_filename;
|
||||||
|
|
||||||
static void redirect_logs(int fd)
|
static void redirect_logs(int fd)
|
||||||
{
|
{
|
||||||
@@ -134,14 +134,15 @@ open_log_file(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
log_init_file(const char *path, unsigned line, GError **error_r)
|
log_init_file(unsigned line, GError **error_r)
|
||||||
{
|
{
|
||||||
out_filename = path;
|
assert(out_filename != NULL);
|
||||||
|
|
||||||
out_fd = open_log_file();
|
out_fd = open_log_file();
|
||||||
if (out_fd < 0) {
|
if (out_fd < 0) {
|
||||||
g_set_error(error_r, log_quark(), errno,
|
g_set_error(error_r, log_quark(), errno,
|
||||||
"failed to open log file \"%s\" (config line %u): %s",
|
"failed to open log file \"%s\" (config line %u): %s",
|
||||||
path, line, g_strerror(errno));
|
out_filename, line, g_strerror(errno));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,22 +272,33 @@ log_init(bool verbose, bool use_stdout, GError **error_r)
|
|||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
GError *error = NULL;
|
out_filename = config_dup_path(CONF_LOG_FILE, error_r);
|
||||||
char *path = config_dup_path(CONF_LOG_FILE, &error);
|
return out_filename != NULL &&
|
||||||
if (path == NULL) {
|
log_init_file(param->line, error_r);
|
||||||
assert(error != NULL);
|
|
||||||
g_propagate_error(error_r, error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool success = log_init_file(path, param->line,
|
|
||||||
error_r);
|
|
||||||
g_free(path);
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
close_log_files(void)
|
||||||
|
{
|
||||||
|
if (stdout_mode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#ifdef HAVE_SYSLOG
|
||||||
|
if (out_filename == NULL)
|
||||||
|
closelog();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
log_deinit(void)
|
||||||
|
{
|
||||||
|
close_log_files();
|
||||||
|
g_free(out_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void setup_log_output(bool use_stdout)
|
void setup_log_output(bool use_stdout)
|
||||||
{
|
{
|
||||||
fflush(NULL);
|
fflush(NULL);
|
||||||
@@ -327,15 +339,3 @@ int cycle_log_files(void)
|
|||||||
g_debug("Done cycling log files\n");
|
g_debug("Done cycling log files\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_log_files(void)
|
|
||||||
{
|
|
||||||
if (stdout_mode)
|
|
||||||
return;
|
|
||||||
|
|
||||||
#ifdef HAVE_SYSLOG
|
|
||||||
if (out_filename == NULL)
|
|
||||||
closelog();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -44,10 +44,11 @@ log_early_init(bool verbose);
|
|||||||
bool
|
bool
|
||||||
log_init(bool verbose, bool use_stdout, GError **error_r);
|
log_init(bool verbose, bool use_stdout, GError **error_r);
|
||||||
|
|
||||||
|
void
|
||||||
|
log_deinit(void);
|
||||||
|
|
||||||
void setup_log_output(bool use_stdout);
|
void setup_log_output(bool use_stdout);
|
||||||
|
|
||||||
int cycle_log_files(void);
|
int cycle_log_files(void);
|
||||||
|
|
||||||
void close_log_files(void);
|
|
||||||
|
|
||||||
#endif /* LOG_H */
|
#endif /* LOG_H */
|
||||||
|
|||||||
@@ -135,10 +135,8 @@ glue_mapper_init(GError **error_r)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if GLIB_CHECK_VERSION(2,14,0)
|
|
||||||
if (music_dir == NULL)
|
if (music_dir == NULL)
|
||||||
music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC));
|
music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC));
|
||||||
#endif
|
|
||||||
|
|
||||||
mapper_init(music_dir, playlist_dir);
|
mapper_init(music_dir, playlist_dir);
|
||||||
|
|
||||||
@@ -538,6 +536,6 @@ int mpd_main(int argc, char *argv[])
|
|||||||
WSACleanup();
|
WSACleanup();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
close_log_files();
|
log_deinit();
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
95
src/mapper.c
95
src/mapper.c
@@ -36,10 +36,24 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
|
||||||
static char *music_dir;
|
/**
|
||||||
static size_t music_dir_length;
|
* The absolute path of the music directory encoded in UTF-8.
|
||||||
|
*/
|
||||||
|
static char *music_dir_utf8;
|
||||||
|
static size_t music_dir_utf8_length;
|
||||||
|
|
||||||
static char *playlist_dir;
|
/**
|
||||||
|
* The absolute path of the music directory encoded in the filesystem
|
||||||
|
* character set.
|
||||||
|
*/
|
||||||
|
static char *music_dir_fs;
|
||||||
|
static size_t music_dir_fs_length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The absolute path of the playlist directory encoded in the
|
||||||
|
* filesystem character set.
|
||||||
|
*/
|
||||||
|
static char *playlist_dir_fs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duplicate a string, chop all trailing slashes.
|
* Duplicate a string, chop all trailing slashes.
|
||||||
@@ -79,27 +93,28 @@ check_directory(const char *path)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
DIR *dir = opendir(path);
|
DIR *dir = opendir(path);
|
||||||
if (dir == NULL && errno == EACCES)
|
if (dir != NULL)
|
||||||
g_warning("No permission to read directory: %s", path);
|
|
||||||
else
|
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
|
else if (errno == EACCES)
|
||||||
|
g_warning("No permission to read directory: %s", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mapper_set_music_dir(const char *path)
|
mapper_set_music_dir(const char *path_utf8)
|
||||||
{
|
{
|
||||||
check_directory(path);
|
music_dir_utf8 = strdup_chop_slash(path_utf8);
|
||||||
|
music_dir_utf8_length = strlen(music_dir_utf8);
|
||||||
|
|
||||||
music_dir = strdup_chop_slash(path);
|
music_dir_fs = utf8_to_fs_charset(music_dir_utf8);
|
||||||
music_dir_length = strlen(music_dir);
|
check_directory(music_dir_fs);
|
||||||
|
music_dir_fs_length = strlen(music_dir_fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mapper_set_playlist_dir(const char *path)
|
mapper_set_playlist_dir(const char *path_utf8)
|
||||||
{
|
{
|
||||||
check_directory(path);
|
playlist_dir_fs = utf8_to_fs_charset(path_utf8);
|
||||||
|
check_directory(playlist_dir_fs);
|
||||||
playlist_dir = g_strdup(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void mapper_init(const char *_music_dir, const char *_playlist_dir)
|
void mapper_init(const char *_music_dir, const char *_playlist_dir)
|
||||||
@@ -113,23 +128,31 @@ void mapper_init(const char *_music_dir, const char *_playlist_dir)
|
|||||||
|
|
||||||
void mapper_finish(void)
|
void mapper_finish(void)
|
||||||
{
|
{
|
||||||
g_free(music_dir);
|
g_free(music_dir_utf8);
|
||||||
g_free(playlist_dir);
|
g_free(music_dir_fs);
|
||||||
|
g_free(playlist_dir_fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
mapper_get_music_directory(void)
|
mapper_get_music_directory_utf8(void)
|
||||||
{
|
{
|
||||||
return music_dir;
|
return music_dir_utf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
mapper_get_music_directory_fs(void)
|
||||||
|
{
|
||||||
|
return music_dir_fs;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
map_to_relative_path(const char *path_utf8)
|
map_to_relative_path(const char *path_utf8)
|
||||||
{
|
{
|
||||||
return music_dir != NULL &&
|
return music_dir_utf8 != NULL &&
|
||||||
memcmp(path_utf8, music_dir, music_dir_length) == 0 &&
|
memcmp(path_utf8, music_dir_utf8,
|
||||||
G_IS_DIR_SEPARATOR(path_utf8[music_dir_length])
|
music_dir_utf8_length) == 0 &&
|
||||||
? path_utf8 + music_dir_length + 1
|
G_IS_DIR_SEPARATOR(path_utf8[music_dir_utf8_length])
|
||||||
|
? path_utf8 + music_dir_utf8_length + 1
|
||||||
: path_utf8;
|
: path_utf8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,14 +164,14 @@ map_uri_fs(const char *uri)
|
|||||||
assert(uri != NULL);
|
assert(uri != NULL);
|
||||||
assert(*uri != '/');
|
assert(*uri != '/');
|
||||||
|
|
||||||
if (music_dir == NULL)
|
if (music_dir_fs == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
uri_fs = utf8_to_fs_charset(uri);
|
uri_fs = utf8_to_fs_charset(uri);
|
||||||
if (uri_fs == NULL)
|
if (uri_fs == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
path_fs = g_build_filename(music_dir, uri_fs, NULL);
|
path_fs = g_build_filename(music_dir_fs, uri_fs, NULL);
|
||||||
g_free(uri_fs);
|
g_free(uri_fs);
|
||||||
|
|
||||||
return path_fs;
|
return path_fs;
|
||||||
@@ -157,10 +180,11 @@ map_uri_fs(const char *uri)
|
|||||||
char *
|
char *
|
||||||
map_directory_fs(const struct directory *directory)
|
map_directory_fs(const struct directory *directory)
|
||||||
{
|
{
|
||||||
assert(music_dir != NULL);
|
assert(music_dir_utf8 != NULL);
|
||||||
|
assert(music_dir_fs != NULL);
|
||||||
|
|
||||||
if (directory_is_root(directory))
|
if (directory_is_root(directory))
|
||||||
return g_strdup(music_dir);
|
return g_strdup(music_dir_fs);
|
||||||
|
|
||||||
return map_uri_fs(directory_get_path(directory));
|
return map_uri_fs(directory_get_path(directory));
|
||||||
}
|
}
|
||||||
@@ -168,9 +192,10 @@ map_directory_fs(const struct directory *directory)
|
|||||||
char *
|
char *
|
||||||
map_directory_child_fs(const struct directory *directory, const char *name)
|
map_directory_child_fs(const struct directory *directory, const char *name)
|
||||||
{
|
{
|
||||||
char *name_fs, *parent_fs, *path;
|
assert(music_dir_utf8 != NULL);
|
||||||
|
assert(music_dir_fs != NULL);
|
||||||
|
|
||||||
assert(music_dir != NULL);
|
char *name_fs, *parent_fs, *path;
|
||||||
|
|
||||||
/* check for invalid or unauthorized base names */
|
/* check for invalid or unauthorized base names */
|
||||||
if (*name == 0 || strchr(name, '/') != NULL ||
|
if (*name == 0 || strchr(name, '/') != NULL ||
|
||||||
@@ -208,11 +233,11 @@ map_song_fs(const struct song *song)
|
|||||||
char *
|
char *
|
||||||
map_fs_to_utf8(const char *path_fs)
|
map_fs_to_utf8(const char *path_fs)
|
||||||
{
|
{
|
||||||
if (music_dir != NULL &&
|
if (music_dir_fs != NULL &&
|
||||||
strncmp(path_fs, music_dir, music_dir_length) == 0 &&
|
strncmp(path_fs, music_dir_fs, music_dir_fs_length) == 0 &&
|
||||||
G_IS_DIR_SEPARATOR(path_fs[music_dir_length]))
|
G_IS_DIR_SEPARATOR(path_fs[music_dir_fs_length]))
|
||||||
/* remove musicDir prefix */
|
/* remove musicDir prefix */
|
||||||
path_fs += music_dir_length + 1;
|
path_fs += music_dir_fs_length + 1;
|
||||||
else if (G_IS_DIR_SEPARATOR(path_fs[0]))
|
else if (G_IS_DIR_SEPARATOR(path_fs[0]))
|
||||||
/* not within musicDir */
|
/* not within musicDir */
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -226,7 +251,7 @@ map_fs_to_utf8(const char *path_fs)
|
|||||||
const char *
|
const char *
|
||||||
map_spl_path(void)
|
map_spl_path(void)
|
||||||
{
|
{
|
||||||
return playlist_dir;
|
return playlist_dir_fs;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
@@ -234,7 +259,7 @@ map_spl_utf8_to_fs(const char *name)
|
|||||||
{
|
{
|
||||||
char *filename_utf8, *filename_fs, *path;
|
char *filename_utf8, *filename_fs, *path;
|
||||||
|
|
||||||
if (playlist_dir == NULL)
|
if (playlist_dir_fs == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL);
|
filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL);
|
||||||
@@ -243,7 +268,7 @@ map_spl_utf8_to_fs(const char *name)
|
|||||||
if (filename_fs == NULL)
|
if (filename_fs == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
path = g_build_filename(playlist_dir, filename_fs, NULL);
|
path = g_build_filename(playlist_dir_fs, filename_fs, NULL);
|
||||||
g_free(filename_fs);
|
g_free(filename_fs);
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
|
|||||||
15
src/mapper.h
15
src/mapper.h
@@ -36,9 +36,20 @@ void mapper_init(const char *_music_dir, const char *_playlist_dir);
|
|||||||
|
|
||||||
void mapper_finish(void);
|
void mapper_finish(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the absolute path of the music directory encoded in UTF-8.
|
||||||
|
*/
|
||||||
G_GNUC_CONST
|
G_GNUC_CONST
|
||||||
const char *
|
const char *
|
||||||
mapper_get_music_directory(void);
|
mapper_get_music_directory_utf8(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the absolute path of the music directory encoded in the
|
||||||
|
* filesystem character set.
|
||||||
|
*/
|
||||||
|
G_GNUC_CONST
|
||||||
|
const char *
|
||||||
|
mapper_get_music_directory_fs(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if a music directory was configured.
|
* Returns true if a music directory was configured.
|
||||||
@@ -47,7 +58,7 @@ G_GNUC_CONST
|
|||||||
static inline bool
|
static inline bool
|
||||||
mapper_has_music_directory(void)
|
mapper_has_music_directory(void)
|
||||||
{
|
{
|
||||||
return mapper_get_music_directory() != NULL;
|
return mapper_get_music_directory_utf8() != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -53,6 +53,31 @@ httpd_output_quark(void)
|
|||||||
return g_quark_from_static_string("httpd_output");
|
return g_quark_from_static_string("httpd_output");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether there is at least one client.
|
||||||
|
*
|
||||||
|
* Caller must lock the mutex.
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
|
static bool
|
||||||
|
httpd_output_has_clients(const struct httpd_output *httpd)
|
||||||
|
{
|
||||||
|
return httpd->clients != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether there is at least one client.
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
|
static bool
|
||||||
|
httpd_output_lock_has_clients(const struct httpd_output *httpd)
|
||||||
|
{
|
||||||
|
g_mutex_lock(httpd->mutex);
|
||||||
|
bool result = httpd_output_has_clients(httpd);
|
||||||
|
g_mutex_unlock(httpd->mutex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
httpd_listen_in_event(int fd, const struct sockaddr *address,
|
httpd_listen_in_event(int fd, const struct sockaddr *address,
|
||||||
size_t address_length, int uid, void *ctx);
|
size_t address_length, int uid, void *ctx);
|
||||||
@@ -397,6 +422,19 @@ httpd_output_delay(struct audio_output *ao)
|
|||||||
{
|
{
|
||||||
struct httpd_output *httpd = (struct httpd_output *)ao;
|
struct httpd_output *httpd = (struct httpd_output *)ao;
|
||||||
|
|
||||||
|
if (!httpd_output_lock_has_clients(httpd) && httpd->base.pause) {
|
||||||
|
/* if there's no client and this output is paused,
|
||||||
|
then httpd_output_pause() will not do anything, it
|
||||||
|
will not fill the buffer and it will not update the
|
||||||
|
timer; therefore, we reset the timer here */
|
||||||
|
timer_reset(httpd->timer);
|
||||||
|
|
||||||
|
/* some arbitrary delay that is long enough to avoid
|
||||||
|
consuming too much CPU, and short enough to notice
|
||||||
|
new clients quickly enough */
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
return httpd->timer->started
|
return httpd->timer->started
|
||||||
? timer_delay(httpd->timer)
|
? timer_delay(httpd->timer)
|
||||||
: 0;
|
: 0;
|
||||||
@@ -475,13 +513,8 @@ httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
|
|||||||
GError **error)
|
GError **error)
|
||||||
{
|
{
|
||||||
struct httpd_output *httpd = (struct httpd_output *)ao;
|
struct httpd_output *httpd = (struct httpd_output *)ao;
|
||||||
bool has_clients;
|
|
||||||
|
|
||||||
g_mutex_lock(httpd->mutex);
|
if (httpd_output_lock_has_clients(httpd)) {
|
||||||
has_clients = httpd->clients != NULL;
|
|
||||||
g_mutex_unlock(httpd->mutex);
|
|
||||||
|
|
||||||
if (has_clients) {
|
|
||||||
bool success;
|
bool success;
|
||||||
|
|
||||||
success = httpd_output_encode_and_play(httpd, chunk, size,
|
success = httpd_output_encode_and_play(httpd, chunk, size,
|
||||||
@@ -502,16 +535,11 @@ httpd_output_pause(struct audio_output *ao)
|
|||||||
{
|
{
|
||||||
struct httpd_output *httpd = (struct httpd_output *)ao;
|
struct httpd_output *httpd = (struct httpd_output *)ao;
|
||||||
|
|
||||||
g_mutex_lock(httpd->mutex);
|
if (httpd_output_lock_has_clients(httpd)) {
|
||||||
bool has_clients = httpd->clients != NULL;
|
|
||||||
g_mutex_unlock(httpd->mutex);
|
|
||||||
|
|
||||||
if (has_clients) {
|
|
||||||
static const char silence[1020];
|
static const char silence[1020];
|
||||||
return httpd_output_play(ao, silence, sizeof(silence),
|
return httpd_output_play(ao, silence, sizeof(silence),
|
||||||
NULL) > 0;
|
NULL) > 0;
|
||||||
} else {
|
} else {
|
||||||
g_usleep(100000);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -608,6 +608,16 @@ mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao)
|
|||||||
mpd_jack_stop(jd);
|
mpd_jack_stop(jd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unsigned
|
||||||
|
mpd_jack_delay(struct audio_output *ao)
|
||||||
|
{
|
||||||
|
struct jack_data *jd = (struct jack_data *)ao;
|
||||||
|
|
||||||
|
return jd->base.pause && jd->pause && !jd->shutdown
|
||||||
|
? 1000
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
static inline jack_default_audio_sample_t
|
static inline jack_default_audio_sample_t
|
||||||
sample_16_to_jack(int16_t sample)
|
sample_16_to_jack(int16_t sample)
|
||||||
{
|
{
|
||||||
@@ -727,10 +737,6 @@ mpd_jack_pause(struct audio_output *ao)
|
|||||||
|
|
||||||
jd->pause = true;
|
jd->pause = true;
|
||||||
|
|
||||||
/* due to a MPD API limitation, we have to sleep a little bit
|
|
||||||
here, to avoid hogging the CPU */
|
|
||||||
g_usleep(50000);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -742,6 +748,7 @@ const struct audio_output_plugin jack_output_plugin = {
|
|||||||
.enable = mpd_jack_enable,
|
.enable = mpd_jack_enable,
|
||||||
.disable = mpd_jack_disable,
|
.disable = mpd_jack_disable,
|
||||||
.open = mpd_jack_open,
|
.open = mpd_jack_open,
|
||||||
|
.delay = mpd_jack_delay,
|
||||||
.play = mpd_jack_play,
|
.play = mpd_jack_play,
|
||||||
.pause = mpd_jack_pause,
|
.pause = mpd_jack_pause,
|
||||||
.close = mpd_jack_close,
|
.close = mpd_jack_close,
|
||||||
|
|||||||
@@ -681,35 +681,6 @@ pulse_output_close(struct audio_output *ao)
|
|||||||
pa_threaded_mainloop_unlock(po->mainloop);
|
pa_threaded_mainloop_unlock(po->mainloop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the stream is (already) connected, and waits for a signal
|
|
||||||
* if not. The mainloop must be locked before calling this function.
|
|
||||||
*
|
|
||||||
* @return the current stream state
|
|
||||||
*/
|
|
||||||
static pa_stream_state_t
|
|
||||||
pulse_output_check_stream(struct pulse_output *po)
|
|
||||||
{
|
|
||||||
pa_stream_state_t state = pa_stream_get_state(po->stream);
|
|
||||||
|
|
||||||
assert(po->mainloop != NULL);
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case PA_STREAM_READY:
|
|
||||||
case PA_STREAM_FAILED:
|
|
||||||
case PA_STREAM_TERMINATED:
|
|
||||||
case PA_STREAM_UNCONNECTED:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PA_STREAM_CREATING:
|
|
||||||
pa_threaded_mainloop_wait(po->mainloop);
|
|
||||||
state = pa_stream_get_state(po->stream);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the stream is (already) connected, and waits if not. The
|
* Check if the stream is (already) connected, and waits if not. The
|
||||||
* mainloop must be locked before calling this function.
|
* mainloop must be locked before calling this function.
|
||||||
@@ -719,35 +690,25 @@ pulse_output_check_stream(struct pulse_output *po)
|
|||||||
static bool
|
static bool
|
||||||
pulse_output_wait_stream(struct pulse_output *po, GError **error_r)
|
pulse_output_wait_stream(struct pulse_output *po, GError **error_r)
|
||||||
{
|
{
|
||||||
pa_stream_state_t state = pa_stream_get_state(po->stream);
|
while (true) {
|
||||||
|
switch (pa_stream_get_state(po->stream)) {
|
||||||
|
case PA_STREAM_READY:
|
||||||
|
return true;
|
||||||
|
|
||||||
switch (state) {
|
case PA_STREAM_FAILED:
|
||||||
case PA_STREAM_READY:
|
case PA_STREAM_TERMINATED:
|
||||||
return true;
|
case PA_STREAM_UNCONNECTED:
|
||||||
|
g_set_error(error_r, pulse_output_quark(),
|
||||||
|
pa_context_errno(po->context),
|
||||||
|
"failed to connect the stream: %s",
|
||||||
|
pa_strerror(pa_context_errno(po->context)));
|
||||||
|
return false;
|
||||||
|
|
||||||
case PA_STREAM_FAILED:
|
case PA_STREAM_CREATING:
|
||||||
case PA_STREAM_TERMINATED:
|
pa_threaded_mainloop_wait(po->mainloop);
|
||||||
case PA_STREAM_UNCONNECTED:
|
break;
|
||||||
g_set_error(error_r, pulse_output_quark(), 0,
|
}
|
||||||
"disconnected");
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case PA_STREAM_CREATING:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
|
||||||
state = pulse_output_check_stream(po);
|
|
||||||
} while (state == PA_STREAM_CREATING);
|
|
||||||
|
|
||||||
if (state != PA_STREAM_READY) {
|
|
||||||
g_set_error(error_r, pulse_output_quark(), 0,
|
|
||||||
"failed to connect the stream: %s",
|
|
||||||
pa_strerror(pa_context_errno(po->context)));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -801,6 +762,24 @@ pulse_output_stream_pause(struct pulse_output *po, bool pause,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unsigned
|
||||||
|
pulse_output_delay(struct audio_output *ao)
|
||||||
|
{
|
||||||
|
struct pulse_output *po = (struct pulse_output *)ao;
|
||||||
|
unsigned result = 0;
|
||||||
|
|
||||||
|
pa_threaded_mainloop_lock(po->mainloop);
|
||||||
|
|
||||||
|
if (po->base.pause && pulse_output_stream_is_paused(po) &&
|
||||||
|
pa_stream_get_state(po->stream) == PA_STREAM_READY)
|
||||||
|
/* idle while paused */
|
||||||
|
result = 1000;
|
||||||
|
|
||||||
|
pa_threaded_mainloop_unlock(po->mainloop);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
|
pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
|
||||||
GError **error_r)
|
GError **error_r)
|
||||||
@@ -928,13 +907,8 @@ pulse_output_pause(struct audio_output *ao)
|
|||||||
|
|
||||||
/* cork the stream */
|
/* cork the stream */
|
||||||
|
|
||||||
if (pulse_output_stream_is_paused(po)) {
|
if (!pulse_output_stream_is_paused(po) &&
|
||||||
/* already paused; due to a MPD API limitation, we
|
!pulse_output_stream_pause(po, true, &error)) {
|
||||||
have to sleep a little bit here, to avoid hogging
|
|
||||||
the CPU */
|
|
||||||
|
|
||||||
g_usleep(50000);
|
|
||||||
} else if (!pulse_output_stream_pause(po, true, &error)) {
|
|
||||||
pa_threaded_mainloop_unlock(po->mainloop);
|
pa_threaded_mainloop_unlock(po->mainloop);
|
||||||
g_warning("%s", error->message);
|
g_warning("%s", error->message);
|
||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
@@ -971,6 +945,7 @@ const struct audio_output_plugin pulse_output_plugin = {
|
|||||||
.enable = pulse_output_enable,
|
.enable = pulse_output_enable,
|
||||||
.disable = pulse_output_disable,
|
.disable = pulse_output_disable,
|
||||||
.open = pulse_output_open,
|
.open = pulse_output_open,
|
||||||
|
.delay = pulse_output_delay,
|
||||||
.play = pulse_output_play,
|
.play = pulse_output_play,
|
||||||
.cancel = pulse_output_cancel,
|
.cancel = pulse_output_cancel,
|
||||||
.pause = pulse_output_pause,
|
.pause = pulse_output_pause,
|
||||||
|
|||||||
@@ -210,6 +210,14 @@ pc_set_pause(struct player_control *pc, bool pause_flag)
|
|||||||
player_unlock(pc);
|
player_unlock(pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
pc_set_border_pause(struct player_control *pc, bool border_pause)
|
||||||
|
{
|
||||||
|
player_lock(pc);
|
||||||
|
pc->border_pause = border_pause;
|
||||||
|
player_unlock(pc);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
pc_get_status(struct player_control *pc, struct player_status *status)
|
pc_get_status(struct player_control *pc, struct player_status *status)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -115,6 +115,15 @@ struct player_control {
|
|||||||
float mixramp_db;
|
float mixramp_db;
|
||||||
float mixramp_delay_seconds;
|
float mixramp_delay_seconds;
|
||||||
double total_play_time;
|
double total_play_time;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this flag is set, then the player will be auto-paused at
|
||||||
|
* the end of the song, before the next song starts to play.
|
||||||
|
*
|
||||||
|
* This is a copy of the queue's "single" flag most of the
|
||||||
|
* time.
|
||||||
|
*/
|
||||||
|
bool border_pause;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct player_control *
|
struct player_control *
|
||||||
@@ -207,6 +216,12 @@ pc_set_pause(struct player_control *pc, bool pause_flag);
|
|||||||
void
|
void
|
||||||
pc_pause(struct player_control *pc);
|
pc_pause(struct player_control *pc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the player's #border_pause flag.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
pc_set_border_pause(struct player_control *pc, bool border_pause);
|
||||||
|
|
||||||
void
|
void
|
||||||
pc_kill(struct player_control *pc);
|
pc_kill(struct player_control *pc);
|
||||||
|
|
||||||
|
|||||||
@@ -450,6 +450,8 @@ static bool player_seek_decoder(struct player *player)
|
|||||||
|
|
||||||
assert(pc->next_song != NULL);
|
assert(pc->next_song != NULL);
|
||||||
|
|
||||||
|
const unsigned start_ms = song->start_ms;
|
||||||
|
|
||||||
if (decoder_current_song(dc) != song) {
|
if (decoder_current_song(dc) != song) {
|
||||||
/* the decoder is already decoding the "next" song -
|
/* the decoder is already decoding the "next" song -
|
||||||
stop it and start the previous song again */
|
stop it and start the previous song again */
|
||||||
@@ -498,7 +500,7 @@ static bool player_seek_decoder(struct player *player)
|
|||||||
if (where < 0.0)
|
if (where < 0.0)
|
||||||
where = 0.0;
|
where = 0.0;
|
||||||
|
|
||||||
if (!dc_seek(dc, where + song->start_ms / 1000.0)) {
|
if (!dc_seek(dc, where + start_ms / 1000.0)) {
|
||||||
/* decoder failure */
|
/* decoder failure */
|
||||||
player_command_finished(pc);
|
player_command_finished(pc);
|
||||||
return false;
|
return false;
|
||||||
@@ -840,6 +842,16 @@ player_song_border(struct player *player)
|
|||||||
if (!player_wait_for_decoder(player))
|
if (!player_wait_for_decoder(player))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
struct player_control *const pc = player->pc;
|
||||||
|
player_lock(pc);
|
||||||
|
|
||||||
|
if (pc->border_pause) {
|
||||||
|
player->paused = true;
|
||||||
|
pc->state = PLAYER_STATE_PAUSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
player_unlock(pc);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,7 +967,10 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
|
|||||||
player_dc_start(&player, music_pipe_new());
|
player_dc_start(&player, music_pipe_new());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player_dc_at_next_song(&player) &&
|
if (/* no cross-fading if MPD is going to pause at the
|
||||||
|
end of the current song */
|
||||||
|
!pc->border_pause &&
|
||||||
|
player_dc_at_next_song(&player) &&
|
||||||
player.xfade == XFADE_UNKNOWN &&
|
player.xfade == XFADE_UNKNOWN &&
|
||||||
!decoder_lock_is_starting(dc)) {
|
!decoder_lock_is_starting(dc)) {
|
||||||
/* enable cross fading in this song? if yes,
|
/* enable cross fading in this song? if yes,
|
||||||
|
|||||||
@@ -109,11 +109,6 @@ playlist_song_started(struct playlist *playlist, struct player_control *pc)
|
|||||||
playlist->current = playlist->queued;
|
playlist->current = playlist->queued;
|
||||||
playlist->queued = -1;
|
playlist->queued = -1;
|
||||||
|
|
||||||
/* Pause if we are in single mode. */
|
|
||||||
if(playlist->queue.single && !playlist->queue.repeat) {
|
|
||||||
pc_set_pause(pc, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(playlist->queue.consume)
|
if(playlist->queue.consume)
|
||||||
playlist_delete(playlist, pc,
|
playlist_delete(playlist, pc,
|
||||||
queue_order_to_position(&playlist->queue,
|
queue_order_to_position(&playlist->queue,
|
||||||
@@ -239,9 +234,13 @@ playlist_sync(struct playlist *playlist, struct player_control *pc)
|
|||||||
if (pc_next_song == NULL && playlist->queued != -1)
|
if (pc_next_song == NULL && playlist->queued != -1)
|
||||||
playlist_song_started(playlist, pc);
|
playlist_song_started(playlist, pc);
|
||||||
|
|
||||||
|
player_lock(pc);
|
||||||
|
pc_next_song = pc->next_song;
|
||||||
|
player_unlock(pc);
|
||||||
|
|
||||||
/* make sure the queued song is always set (if
|
/* make sure the queued song is always set (if
|
||||||
possible) */
|
possible) */
|
||||||
if (pc->next_song == NULL && playlist->queued < 0)
|
if (pc_next_song == NULL && playlist->queued < 0)
|
||||||
playlist_update_queued_song(playlist, pc, NULL);
|
playlist_update_queued_song(playlist, pc, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,7 +305,11 @@ playlist_set_repeat(struct playlist *playlist, struct player_control *pc,
|
|||||||
if (status == playlist->queue.repeat)
|
if (status == playlist->queue.repeat)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
playlist->queue.repeat = status;
|
struct queue *queue = &playlist->queue;
|
||||||
|
|
||||||
|
queue->repeat = status;
|
||||||
|
|
||||||
|
pc_set_border_pause(pc, queue->single && !queue->repeat);
|
||||||
|
|
||||||
/* if the last song is currently being played, the "next song"
|
/* if the last song is currently being played, the "next song"
|
||||||
might change when repeat mode is toggled */
|
might change when repeat mode is toggled */
|
||||||
@@ -334,7 +337,11 @@ playlist_set_single(struct playlist *playlist, struct player_control *pc,
|
|||||||
if (status == playlist->queue.single)
|
if (status == playlist->queue.single)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
playlist->queue.single = status;
|
struct queue *queue = &playlist->queue;
|
||||||
|
|
||||||
|
queue->single = status;
|
||||||
|
|
||||||
|
pc_set_border_pause(pc, queue->single && !queue->repeat);
|
||||||
|
|
||||||
/* if the last song is currently being played, the "next song"
|
/* if the last song is currently being played, the "next song"
|
||||||
might change when single mode is toggled */
|
might change when single mode is toggled */
|
||||||
|
|||||||
@@ -58,12 +58,10 @@ lastfm_init(const struct config_param *param)
|
|||||||
|
|
||||||
lastfm_config.user = g_uri_escape_string(user, NULL, false);
|
lastfm_config.user = g_uri_escape_string(user, NULL, false);
|
||||||
|
|
||||||
#if GLIB_CHECK_VERSION(2,16,0)
|
|
||||||
if (strlen(passwd) != 32)
|
if (strlen(passwd) != 32)
|
||||||
lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
|
lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
|
||||||
passwd, strlen(passwd));
|
passwd, strlen(passwd));
|
||||||
else
|
else
|
||||||
#endif
|
|
||||||
lastfm_config.md5 = g_strdup(passwd);
|
lastfm_config.md5 = g_strdup(passwd);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ playlist_append_song(struct playlist *playlist, struct player_control *pc,
|
|||||||
|
|
||||||
queued = playlist_get_queued_song(playlist);
|
queued = playlist_get_queued_song(playlist);
|
||||||
|
|
||||||
id = queue_append(&playlist->queue, song);
|
id = queue_append(&playlist->queue, song, 0);
|
||||||
|
|
||||||
if (playlist->queue.random) {
|
if (playlist->queue.random) {
|
||||||
/* shuffle the new song into the list of remaining
|
/* shuffle the new song into the list of remaining
|
||||||
|
|||||||
@@ -163,6 +163,9 @@ playlist_provider_print(struct client *client, const char *uri,
|
|||||||
song_print_info(client, song);
|
song_print_info(client, song);
|
||||||
else
|
else
|
||||||
song_print_uri(client, song);
|
song_print_uri(client, song);
|
||||||
|
|
||||||
|
if (!song_in_database(song))
|
||||||
|
song_free(song);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(base_uri);
|
g_free(base_uri);
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ apply_song_metadata(struct song *dest, const struct song *src)
|
|||||||
} else {
|
} else {
|
||||||
tmp = song_file_new(dest->uri, NULL);
|
tmp = song_file_new(dest->uri, NULL);
|
||||||
merge_song_metadata(tmp, dest, src);
|
merge_song_metadata(tmp, dest, src);
|
||||||
song_free(dest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dest->tag != NULL && dest->tag->time > 0 &&
|
if (dest->tag != NULL && dest->tag->time > 0 &&
|
||||||
@@ -80,20 +79,42 @@ apply_song_metadata(struct song *dest, const struct song *src)
|
|||||||
(e.g. last track on a CUE file); fix it up here */
|
(e.g. last track on a CUE file); fix it up here */
|
||||||
tmp->tag->time = dest->tag->time - src->start_ms / 1000;
|
tmp->tag->time = dest->tag->time - src->start_ms / 1000;
|
||||||
|
|
||||||
|
if (!song_in_database(dest))
|
||||||
|
song_free(dest);
|
||||||
|
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct song *
|
||||||
|
playlist_check_load_song(const struct song *song, const char *uri, bool secure)
|
||||||
|
{
|
||||||
|
struct song *dest;
|
||||||
|
|
||||||
|
if (uri_has_scheme(uri)) {
|
||||||
|
dest = song_remote_new(uri);
|
||||||
|
} else if (g_path_is_absolute(uri) && secure) {
|
||||||
|
dest = song_file_load(uri, NULL);
|
||||||
|
if (dest == NULL)
|
||||||
|
return NULL;
|
||||||
|
} else {
|
||||||
|
dest = db_get_song(uri);
|
||||||
|
if (dest == NULL)
|
||||||
|
/* not found in database */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return apply_song_metadata(dest, song);
|
||||||
|
}
|
||||||
|
|
||||||
struct song *
|
struct song *
|
||||||
playlist_check_translate_song(struct song *song, const char *base_uri,
|
playlist_check_translate_song(struct song *song, const char *base_uri,
|
||||||
bool secure)
|
bool secure)
|
||||||
{
|
{
|
||||||
struct song *dest;
|
|
||||||
|
|
||||||
if (song_in_database(song))
|
if (song_in_database(song))
|
||||||
/* already ok */
|
/* already ok */
|
||||||
return song;
|
return song;
|
||||||
|
|
||||||
char *uri = song->uri;
|
const char *uri = song->uri;
|
||||||
|
|
||||||
if (uri_has_scheme(uri)) {
|
if (uri_has_scheme(uri)) {
|
||||||
if (uri_supported_scheme(uri))
|
if (uri_supported_scheme(uri))
|
||||||
@@ -115,11 +136,11 @@ playlist_check_translate_song(struct song *song, const char *base_uri,
|
|||||||
|
|
||||||
if (g_path_is_absolute(uri)) {
|
if (g_path_is_absolute(uri)) {
|
||||||
/* XXX fs_charset vs utf8? */
|
/* XXX fs_charset vs utf8? */
|
||||||
const char *prefix = mapper_get_music_directory();
|
const char *suffix = map_to_relative_path(uri);
|
||||||
|
assert(suffix != NULL);
|
||||||
|
|
||||||
if (prefix != NULL && g_str_has_prefix(uri, prefix) &&
|
if (suffix != uri)
|
||||||
uri[strlen(prefix)] == '/')
|
uri = suffix;
|
||||||
uri += strlen(prefix) + 1;
|
|
||||||
else if (!secure) {
|
else if (!secure) {
|
||||||
/* local files must be relative to the music
|
/* local files must be relative to the music
|
||||||
directory when "secure" is enabled */
|
directory when "secure" is enabled */
|
||||||
@@ -130,32 +151,12 @@ playlist_check_translate_song(struct song *song, const char *base_uri,
|
|||||||
base_uri = NULL;
|
base_uri = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *allocated = NULL;
|
||||||
if (base_uri != NULL)
|
if (base_uri != NULL)
|
||||||
uri = g_build_filename(base_uri, uri, NULL);
|
uri = allocated = g_build_filename(base_uri, uri, NULL);
|
||||||
else
|
|
||||||
uri = g_strdup(uri);
|
|
||||||
|
|
||||||
if (uri_has_scheme(uri)) {
|
struct song *dest = playlist_check_load_song(song, uri, secure);
|
||||||
dest = song_remote_new(uri);
|
|
||||||
g_free(uri);
|
|
||||||
} else if (g_path_is_absolute(uri) && secure) {
|
|
||||||
dest = song_file_load(uri, NULL);
|
|
||||||
if (dest == NULL) {
|
|
||||||
song_free(song);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dest = db_get_song(uri);
|
|
||||||
g_free(uri);
|
|
||||||
if (dest == NULL) {
|
|
||||||
/* not found in database */
|
|
||||||
song_free(song);
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dest = apply_song_metadata(dest, song);
|
|
||||||
song_free(song);
|
song_free(song);
|
||||||
|
g_free(allocated);
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ queue_modify_all(struct queue *queue)
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsigned
|
unsigned
|
||||||
queue_append(struct queue *queue, struct song *song)
|
queue_append(struct queue *queue, struct song *song, uint8_t priority)
|
||||||
{
|
{
|
||||||
unsigned id = queue_generate_id(queue);
|
unsigned id = queue_generate_id(queue);
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ queue_append(struct queue *queue, struct song *song)
|
|||||||
.song = song,
|
.song = song,
|
||||||
.id = id,
|
.id = id,
|
||||||
.version = queue->version,
|
.version = queue->version,
|
||||||
.priority = 0,
|
.priority = priority,
|
||||||
};
|
};
|
||||||
|
|
||||||
queue->order[queue->length] = queue->length;
|
queue->order[queue->length] = queue->length;
|
||||||
|
|||||||
@@ -280,9 +280,11 @@ queue_modify_all(struct queue *queue);
|
|||||||
*
|
*
|
||||||
* If a song is not in the database (determined by
|
* If a song is not in the database (determined by
|
||||||
* song_in_database()), it is freed when removed from the queue.
|
* song_in_database()), it is freed when removed from the queue.
|
||||||
|
*
|
||||||
|
* @param priority the priority of this new queue item
|
||||||
*/
|
*/
|
||||||
unsigned
|
unsigned
|
||||||
queue_append(struct queue *queue, struct song *song);
|
queue_append(struct queue *queue, struct song *song, uint8_t priority);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Swaps two songs, addressed by their position.
|
* Swaps two songs, addressed by their position.
|
||||||
|
|||||||
@@ -24,9 +24,12 @@
|
|||||||
#include "uri.h"
|
#include "uri.h"
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "song_save.h"
|
#include "song_save.h"
|
||||||
|
#include "text_file.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define PRIO_LABEL "Prio: "
|
||||||
|
|
||||||
static void
|
static void
|
||||||
queue_save_database_song(FILE *fp, int idx, const struct song *song)
|
queue_save_database_song(FILE *fp, int idx, const struct song *song)
|
||||||
{
|
{
|
||||||
@@ -54,8 +57,13 @@ queue_save_song(FILE *fp, int idx, const struct song *song)
|
|||||||
void
|
void
|
||||||
queue_save(FILE *fp, const struct queue *queue)
|
queue_save(FILE *fp, const struct queue *queue)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < queue_length(queue); i++)
|
for (unsigned i = 0; i < queue_length(queue); i++) {
|
||||||
|
uint8_t prio = queue_get_priority_at_position(queue, i);
|
||||||
|
if (prio != 0)
|
||||||
|
fprintf(fp, PRIO_LABEL "%u\n", prio);
|
||||||
|
|
||||||
queue_save_song(fp, i, queue_get(queue, i));
|
queue_save_song(fp, i, queue_get(queue, i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct song *
|
static struct song *
|
||||||
@@ -75,6 +83,15 @@ queue_load_song(FILE *fp, GString *buffer, const char *line,
|
|||||||
if (queue_is_full(queue))
|
if (queue_is_full(queue))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
uint8_t priority = 0;
|
||||||
|
if (g_str_has_prefix(line, PRIO_LABEL)) {
|
||||||
|
priority = strtoul(line + sizeof(PRIO_LABEL) - 1, NULL, 10);
|
||||||
|
|
||||||
|
line = read_text_line(fp, buffer);
|
||||||
|
if (line == NULL)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (g_str_has_prefix(line, SONG_BEGIN)) {
|
if (g_str_has_prefix(line, SONG_BEGIN)) {
|
||||||
const char *uri = line + sizeof(SONG_BEGIN) - 1;
|
const char *uri = line + sizeof(SONG_BEGIN) - 1;
|
||||||
if (!uri_has_scheme(uri) && !g_path_is_absolute(uri))
|
if (!uri_has_scheme(uri) && !g_path_is_absolute(uri))
|
||||||
@@ -102,5 +119,5 @@ queue_load_song(FILE *fp, GString *buffer, const char *line,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
queue_append(queue, song);
|
queue_append(queue, song, priority);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
#include "tag_handler.h"
|
#include "tag_handler.h"
|
||||||
#include "ape.h"
|
#include "ape.h"
|
||||||
|
|
||||||
static const struct tag_table ape_tags[] = {
|
const struct tag_table ape_tags[] = {
|
||||||
{ "album artist", TAG_ALBUM_ARTIST },
|
{ "album artist", TAG_ALBUM_ARTIST },
|
||||||
{ "year", TAG_DATE },
|
{ "year", TAG_DATE },
|
||||||
{ NULL, TAG_NUM_OF_ITEM_TYPES }
|
{ NULL, TAG_NUM_OF_ITEM_TYPES }
|
||||||
|
|||||||
@@ -20,10 +20,14 @@
|
|||||||
#ifndef MPD_TAG_APE_H
|
#ifndef MPD_TAG_APE_H
|
||||||
#define MPD_TAG_APE_H
|
#define MPD_TAG_APE_H
|
||||||
|
|
||||||
|
#include "tag_table.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
struct tag_handler;
|
struct tag_handler;
|
||||||
|
|
||||||
|
extern const struct tag_table ape_tags[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan the APE tags of a file.
|
* Scan the APE tags of a file.
|
||||||
*
|
*
|
||||||
|
|||||||
17
src/timer.c
17
src/timer.c
@@ -20,23 +20,14 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
#include "audio_format.h"
|
#include "audio_format.h"
|
||||||
|
#include "clock.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <sys/time.h>
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
static uint64_t now(void)
|
|
||||||
{
|
|
||||||
struct timeval tv;
|
|
||||||
|
|
||||||
gettimeofday(&tv, NULL);
|
|
||||||
|
|
||||||
return ((uint64_t)tv.tv_sec * 1000000) + tv.tv_usec;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct timer *timer_new(const struct audio_format *af)
|
struct timer *timer_new(const struct audio_format *af)
|
||||||
{
|
{
|
||||||
struct timer *timer = g_new(struct timer, 1);
|
struct timer *timer = g_new(struct timer, 1);
|
||||||
@@ -54,7 +45,7 @@ void timer_free(struct timer *timer)
|
|||||||
|
|
||||||
void timer_start(struct timer *timer)
|
void timer_start(struct timer *timer)
|
||||||
{
|
{
|
||||||
timer->time = now();
|
timer->time = monotonic_clock_us();
|
||||||
timer->started = 1;
|
timer->started = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +65,7 @@ void timer_add(struct timer *timer, int size)
|
|||||||
unsigned
|
unsigned
|
||||||
timer_delay(const struct timer *timer)
|
timer_delay(const struct timer *timer)
|
||||||
{
|
{
|
||||||
int64_t delay = (int64_t)(timer->time - now()) / 1000;
|
int64_t delay = (int64_t)(timer->time - monotonic_clock_us()) / 1000;
|
||||||
if (delay < 0)
|
if (delay < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@@ -90,7 +81,7 @@ void timer_sync(struct timer *timer)
|
|||||||
|
|
||||||
assert(timer->started);
|
assert(timer->started);
|
||||||
|
|
||||||
sleep_duration = timer->time - now();
|
sleep_duration = timer->time - monotonic_clock_us();
|
||||||
if (sleep_duration > 0)
|
if (sleep_duration > 0)
|
||||||
g_usleep(sleep_duration);
|
g_usleep(sleep_duration);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -283,12 +283,20 @@ skip_symlink(const struct directory *directory, const char *utf8_name)
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
char buffer[MPD_PATH_MAX];
|
char buffer[MPD_PATH_MAX];
|
||||||
ssize_t ret = readlink(path_fs, buffer, sizeof(buffer));
|
ssize_t length = readlink(path_fs, buffer, sizeof(buffer));
|
||||||
g_free(path_fs);
|
g_free(path_fs);
|
||||||
if (ret < 0)
|
if (length < 0)
|
||||||
/* don't skip if this is not a symlink */
|
/* don't skip if this is not a symlink */
|
||||||
return errno != EINVAL;
|
return errno != EINVAL;
|
||||||
|
|
||||||
|
if ((size_t)length >= sizeof(buffer))
|
||||||
|
/* skip symlinks when the buffer is too small for the
|
||||||
|
link target */
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/* null-terminate the buffer, because readlink() will not */
|
||||||
|
buffer[length] = 0;
|
||||||
|
|
||||||
if (!follow_inside_symlinks && !follow_outside_symlinks) {
|
if (!follow_inside_symlinks && !follow_outside_symlinks) {
|
||||||
/* ignore all symlinks */
|
/* ignore all symlinks */
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "volume.h"
|
#include "volume.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
#include "player_control.h"
|
|
||||||
#include "idle.h"
|
#include "idle.h"
|
||||||
#include "pcm_volume.h"
|
#include "pcm_volume.h"
|
||||||
#include "output_all.h"
|
#include "output_all.h"
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
|
|||||||
queue_init(&queue, 32);
|
queue_init(&queue, 32);
|
||||||
|
|
||||||
for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i)
|
for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i)
|
||||||
queue_append(&queue, &songs[i]);
|
queue_append(&queue, &songs[i], 0);
|
||||||
|
|
||||||
assert(queue_length(&queue) == G_N_ELEMENTS(songs));
|
assert(queue_length(&queue) == G_N_ELEMENTS(songs));
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,14 @@
|
|||||||
fun:g_main_loop_run
|
fun:g_main_loop_run
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
<insert_a_suppression_name_here>
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:*alloc
|
||||||
|
...
|
||||||
|
fun:g_once_init_leave
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
g_log
|
g_log
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
@@ -57,6 +65,14 @@
|
|||||||
fun:g_slice_*
|
fun:g_slice_*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
<insert_a_suppression_name_here>
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:*alloc
|
||||||
|
...
|
||||||
|
fun:g_static_mutex_get_mutex_impl
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
g_private
|
g_private
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
@@ -483,3 +499,24 @@
|
|||||||
fun:?alloc
|
fun:?alloc
|
||||||
fun:snd1_dlobj_cache_get
|
fun:snd1_dlobj_cache_get
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
<insert_a_suppression_name_here>
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:_Znwm
|
||||||
|
...
|
||||||
|
obj:*/libjack.so*
|
||||||
|
fun:call_init
|
||||||
|
fun:_dl_init
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
<insert_a_suppression_name_here>
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:*alloc
|
||||||
|
fun:_dl_allocate_tls
|
||||||
|
...
|
||||||
|
obj:*/libffado.so*
|
||||||
|
fun:call_init
|
||||||
|
fun:_dl_init
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user