diff --git a/NEWS b/NEWS index 07af565e0..6c8309ee6 100644 --- a/NEWS +++ b/NEWS @@ -40,7 +40,7 @@ ver 0.22 (not yet released) * switch to C++17 - GCC 7 or clang 4 (or newer) recommended -ver 0.21.24 (not yet released) +ver 0.21.24 (2020/06/10) * protocol - "tagtypes" requires no permissions * database @@ -49,11 +49,14 @@ ver 0.21.24 (not yet released) - modplug: fix Windows build failure - wildmidi: attempt to detect WildMidi using pkg-config - wildmidi: fix Windows build failure +* player + - don't restart current song if seeking beyond end * Android - - enable the decoder plugins ModPlug and WildMidi + - enable the decoder plugins GME, ModPlug and WildMidi - fix build failure with Android NDK r21 * Windows - - enable the decoder plugins ModPlug and WildMidi + - fix stream playback + - enable the decoder plugins GME, ModPlug and WildMidi - work around Meson bug breaking the Windows build with GCC 10 * fix unit test failure diff --git a/android/build.py b/android/build.py index 8716fc1a9..5bee46304 100755 --- a/android/build.py +++ b/android/build.py @@ -170,6 +170,7 @@ thirdparty_libs = [ libid3tag, libmodplug, wildmidi, + gme, ffmpeg, curl, libexpat, diff --git a/doc/protocol.rst b/doc/protocol.rst index 9d0c170b9..30406e598 100644 --- a/doc/protocol.rst +++ b/doc/protocol.rst @@ -485,7 +485,8 @@ Querying :program:`MPD`'s status - ``songs``: number of songs - ``uptime``: daemon uptime in seconds - ``db_playtime``: sum of all song times in the database in seconds - - ``db_update``: last db update in UNIX time + - ``db_update``: last db update in UNIX time (seconds since + 1970-01-01 UTC) - ``playtime``: time length of music played Playback options diff --git a/python/build/libs.py b/python/build/libs.py index c7b2250cd..e211a0107 100644 --- a/python/build/libs.py +++ b/python/build/libs.py @@ -135,6 +135,18 @@ wildmidi = CmakeProject( version='0.4.3', ) +gme = CmakeProject( + 'https://bitbucket.org/mpyne/game-music-emu/downloads/game-music-emu-0.6.3.tar.xz', + 'aba34e53ef0ec6a34b58b84e28bf8cfbccee6585cebca25333604c35db3e051d', + 'lib/libgme.a', + [ + '-DBUILD_SHARED_LIBS=OFF', + '-DENABLE_UBSAN=OFF', + '-DZLIB_INCLUDE_DIR=OFF', + '-DSDL2_DIR=OFF', + ], +) + ffmpeg = FfmpegProject( 'http://ffmpeg.org/releases/ffmpeg-4.2.3.tar.xz', '9df6c90aed1337634c1fb026fb01c154c29c82a64ea71291ff2da9aacb9aad31', diff --git a/src/decoder/Bridge.cxx b/src/decoder/Bridge.cxx index 58e0641f2..a10ad634f 100644 --- a/src/decoder/Bridge.cxx +++ b/src/decoder/Bridge.cxx @@ -38,15 +38,19 @@ #include #include +#include #include DecoderBridge::DecoderBridge(DecoderControl &_dc, bool _initial_seek_pending, + bool _initial_seek_essential, std::unique_ptr _tag) noexcept :dc(_dc), initial_seek_pending(_initial_seek_pending), + initial_seek_essential(_initial_seek_essential), song_tag(std::move(_tag)) {} + DecoderBridge::~DecoderBridge() noexcept { /* caller must flush the chunk */ @@ -364,6 +368,10 @@ DecoderBridge::SeekError() noexcept /* d'oh, we can't seek to the sub-song start position, what now? - no idea, ignoring the problem for now. */ initial_seek_running = false; + + if (initial_seek_essential) + error = std::make_exception_ptr(std::runtime_error("Decoder failed to seek")); + return; } diff --git a/src/decoder/Bridge.hxx b/src/decoder/Bridge.hxx index cd3600f97..012801def 100644 --- a/src/decoder/Bridge.hxx +++ b/src/decoder/Bridge.hxx @@ -64,6 +64,11 @@ private: */ bool initial_seek_pending; + /** + * Are initial seek failures fatal? + */ + const bool initial_seek_essential; + /** * Is the initial seek currently running? During this time, * the decoder command is SEEK. This flag is set by @@ -112,6 +117,7 @@ private: public: DecoderBridge(DecoderControl &_dc, bool _initial_seek_pending, + bool _initial_seek_essential, std::unique_ptr _tag) noexcept; ~DecoderBridge() noexcept; diff --git a/src/decoder/Control.cxx b/src/decoder/Control.cxx index e6a0df714..50ba4f0d3 100644 --- a/src/decoder/Control.cxx +++ b/src/decoder/Control.cxx @@ -80,6 +80,7 @@ void DecoderControl::Start(std::unique_lock &lock, std::unique_ptr _song, SongTime _start_time, SongTime _end_time, + bool _initial_seek_essential, MusicBuffer &_buffer, std::shared_ptr _pipe) noexcept { @@ -89,6 +90,7 @@ DecoderControl::Start(std::unique_lock &lock, song = std::move(_song); start_time = _start_time; end_time = _end_time; + initial_seek_essential = _initial_seek_essential; buffer = &_buffer; pipe = std::move(_pipe); diff --git a/src/decoder/Control.hxx b/src/decoder/Control.hxx index 432ee4a4a..7bdf1809f 100644 --- a/src/decoder/Control.hxx +++ b/src/decoder/Control.hxx @@ -112,6 +112,12 @@ private: public: bool seek_error; bool seekable; + + /** + * @see #DecoderBridge::initial_seek_essential + */ + bool initial_seek_essential; + SongTime seek_time; private: @@ -383,12 +389,15 @@ public: * owned and freed by the decoder * @param start_time see #DecoderControl * @param end_time see #DecoderControl + * @param initial_seek_essential see + * #DecoderBridge::initial_seek_essential * @param pipe the pipe which receives the decoded chunks (owned by * the caller) */ void Start(std::unique_lock &lock, std::unique_ptr song, SongTime start_time, SongTime end_time, + bool initial_seek_essential, MusicBuffer &buffer, std::shared_ptr pipe) noexcept; diff --git a/src/decoder/DecoderPlugin.hxx b/src/decoder/DecoderPlugin.hxx index 610a4e97f..61f959c79 100644 --- a/src/decoder/DecoderPlugin.hxx +++ b/src/decoder/DecoderPlugin.hxx @@ -22,7 +22,7 @@ #include "util/Compiler.h" -#include +#include // IWYU pragma: export struct ConfigBlock; class InputStream; diff --git a/src/decoder/Thread.cxx b/src/decoder/Thread.cxx index 4fd208edb..9167cd421 100644 --- a/src/decoder/Thread.cxx +++ b/src/decoder/Thread.cxx @@ -422,6 +422,7 @@ decoder_run_song(DecoderControl &dc, dc.start_time = dc.seek_time; DecoderBridge bridge(dc, dc.start_time.IsPositive(), + dc.initial_seek_essential, /* pass the song tag only if it's authoritative, i.e. if it's a local file - tags on "stream" songs are just diff --git a/src/decoder/plugins/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx index 1d253937d..fed55a7a9 100644 --- a/src/decoder/plugins/GmeDecoderPlugin.cxx +++ b/src/decoder/plugins/GmeDecoderPlugin.cxx @@ -27,11 +27,11 @@ #include "fs/Path.hxx" #include "fs/AllocatedPath.hxx" #include "fs/FileSystem.hxx" +#include "fs/NarrowPath.hxx" #include "util/ScopeExit.hxx" #include "util/StringCompare.hxx" #include "util/StringFormat.hxx" #include "util/StringView.hxx" -#include "util/UriExtract.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -40,7 +40,6 @@ #include #include -#include #define SUBTUNE_PREFIX "tune_" @@ -83,11 +82,10 @@ gcc_pure static unsigned ParseSubtuneName(const char *base) noexcept { - if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0) + base = StringAfterPrefix(base, SUBTUNE_PREFIX); + if (base == nullptr) return 0; - base += sizeof(SUBTUNE_PREFIX) - 1; - char *endptr; auto track = strtoul(base, &endptr, 10); if (endptr == base || *endptr != '.') @@ -106,41 +104,46 @@ ParseContainerPath(Path path_fs) const Path base = path_fs.GetBase(); unsigned track; if (base.IsNull() || - (track = ParseSubtuneName(base.c_str())) < 1) + (track = ParseSubtuneName(NarrowPath(base))) < 1) return { AllocatedPath(path_fs), 0 }; return { path_fs.GetDirectoryName(), track - 1 }; } +static AllocatedPath +ReplaceSuffix(Path src, + const PathTraitsFS::const_pointer new_suffix) noexcept +{ + const auto *old_suffix = src.GetSuffix(); + if (old_suffix == nullptr) + return nullptr; + + PathTraitsFS::string s(src.c_str(), old_suffix); + s += new_suffix; + return AllocatedPath::FromFS(std::move(s)); +} + static Music_Emu* LoadGmeAndM3u(const GmeContainerPath& container) { - const char *path = container.path.c_str(); - const char *suffix = uri_get_suffix(path); - Music_Emu *emu; const char *gme_err = - gme_open_file(path, &emu, GME_SAMPLE_RATE); + gme_open_file(NarrowPath(container.path), &emu, GME_SAMPLE_RATE); if (gme_err != nullptr) { LogWarning(gme_domain, gme_err); return nullptr; } - if(suffix == nullptr) { - return emu; - } - - std::string m3u_path(path,suffix); - m3u_path += "m3u"; - + const auto m3u_path = ReplaceSuffix(container.path, + PATH_LITERAL("m3u")); /* * Some GME formats lose metadata if you attempt to * load a non-existant M3U file, so check that one * exists before loading. */ - if(FileExists(Path::FromFS(m3u_path.c_str()))) { - gme_load_m3u(emu,m3u_path.c_str()); - } + if (!m3u_path.IsNull() && FileExists(m3u_path)) + gme_load_m3u(emu, NarrowPath(m3u_path)); + return emu; } @@ -320,7 +323,7 @@ gme_container_scan(Path path_fs) if (num_songs < 2) return list; - const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); + const auto *subtune_suffix = path_fs.GetSuffix(); TagBuilder tag_builder; diff --git a/src/lib/curl/Request.cxx b/src/lib/curl/Request.cxx index 7189864c7..2253c5a4f 100644 --- a/src/lib/curl/Request.cxx +++ b/src/lib/curl/Request.cxx @@ -57,7 +57,7 @@ CurlRequest::CurlRequest(CurlGlobal &_global, easy.SetUserAgent("Music Player Daemon " VERSION); easy.SetHeaderFunction(_HeaderFunction, this); easy.SetWriteFunction(WriteFunction, this); -#ifndef ANDROID +#if !defined(ANDROID) && !defined(_WIN32) easy.SetOption(CURLOPT_NETRC, 1L); #endif easy.SetErrorBuffer(error_buffer); diff --git a/src/pcm/AudioFormat.hxx b/src/pcm/AudioFormat.hxx index 7d931a002..237853df1 100644 --- a/src/pcm/AudioFormat.hxx +++ b/src/pcm/AudioFormat.hxx @@ -20,8 +20,8 @@ #ifndef MPD_AUDIO_FORMAT_HXX #define MPD_AUDIO_FORMAT_HXX -#include "pcm/SampleFormat.hxx" -#include "pcm/ChannelDefs.hxx" +#include "pcm/SampleFormat.hxx" // IWYU pragma: export +#include "pcm/ChannelDefs.hxx" // IWYU pragma: export #include "util/Compiler.h" #include diff --git a/src/player/Thread.cxx b/src/player/Thread.cxx index 704eee05e..e53c5832a 100644 --- a/src/player/Thread.cxx +++ b/src/player/Thread.cxx @@ -224,7 +224,8 @@ private: * Caller must lock the mutex. */ void StartDecoder(std::unique_lock &lock, - std::shared_ptr pipe) noexcept; + std::shared_ptr pipe, + bool initial_seek_essential) noexcept; /** * The decoder has acknowledged the "START" command (see @@ -367,7 +368,8 @@ public: void Player::StartDecoder(std::unique_lock &lock, - std::shared_ptr _pipe) noexcept + std::shared_ptr _pipe, + bool initial_seek_essential) noexcept { assert(queued || pc.command == PlayerCommand::SEEK); assert(pc.next_song != nullptr); @@ -379,6 +381,7 @@ Player::StartDecoder(std::unique_lock &lock, dc.Start(lock, std::make_unique(*pc.next_song), start_time, pc.next_song->GetEndTime(), + initial_seek_essential, buffer, std::move(_pipe)); } @@ -636,7 +639,7 @@ Player::SeekDecoder(std::unique_lock &lock) noexcept pipe->Clear(); /* re-start the decoder */ - StartDecoder(lock, pipe); + StartDecoder(lock, pipe, true); ActivateDecoder(); pc.seeking = true; @@ -714,7 +717,8 @@ Player::ProcessCommand(std::unique_lock &lock) noexcept pc.CommandFinished(); if (dc.IsIdle()) - StartDecoder(lock, std::make_shared()); + StartDecoder(lock, std::make_shared(), + false); break; @@ -985,7 +989,7 @@ Player::Run() noexcept std::unique_lock lock(pc.mutex); - StartDecoder(lock, pipe); + StartDecoder(lock, pipe, true); ActivateDecoder(); pc.state = PlayerState::PLAY; @@ -1025,7 +1029,8 @@ Player::Run() noexcept assert(dc.pipe == nullptr || dc.pipe == pipe); - StartDecoder(lock, std::make_shared()); + StartDecoder(lock, std::make_shared(), + false); } if (/* no cross-fading if MPD is going to pause at the diff --git a/src/util/StringFormat.hxx b/src/util/StringFormat.hxx index 723591c69..c761e8806 100644 --- a/src/util/StringFormat.hxx +++ b/src/util/StringFormat.hxx @@ -30,7 +30,7 @@ #ifndef STRING_FORMAT_HXX #define STRING_FORMAT_HXX -#include "StringBuffer.hxx" +#include "StringBuffer.hxx" // IWYU pragma: export #include diff --git a/win32/build.py b/win32/build.py index 9e1b6e9e4..fb636ded5 100755 --- a/win32/build.py +++ b/win32/build.py @@ -98,6 +98,7 @@ thirdparty_libs = [ liblame, libmodplug, wildmidi, + gme, ffmpeg, curl, libexpat,