From 48bdcd181c21521d44121c78a6e7102e9019d178 Mon Sep 17 00:00:00 2001 From: datasone Date: Tue, 11 Apr 2023 23:44:32 +0800 Subject: [PATCH 1/5] SongPrint: include `real_uri` in `song_print_info` CUE virtual playlists is currently implemented by accessing ranges in file referenced by `real_uri` locally. But the `real_uri` is not included in the song info protocol, thus `ProxyDatabasePlugin` is not able to retreive this information and access the corresponding file, leads to not working cue tracks in satellite setup. This commit adds the `real_uri` field into server-printed song info, with the key name `RealUri`. --- src/SongPrint.cxx | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx index 98d544cc38..79ff54cd56 100644 --- a/src/SongPrint.cxx +++ b/src/SongPrint.cxx @@ -48,6 +48,42 @@ song_print_uri(Response &r, const DetachedSong &song, bool base) noexcept song_print_uri(r, song.GetURI(), base); } +static void +song_print_real_uri(Response &r, const char *real_uri, bool base) noexcept +{ + std::string allocated; + + if (base) { + real_uri = PathTraitsUTF8::GetBase(real_uri); + } else { + allocated = uri_remove_auth(real_uri); + if (!allocated.empty()) + real_uri = allocated.c_str(); + } + + r.Fmt(FMT_STRING("RealUri: {}\n"), real_uri); +} + +static void +song_print_real_uri(Response &r, const LightSong &song, bool base) noexcept +{ + if (song.real_uri == nullptr) + return; + + if (!base && song.directory != nullptr) + r.Fmt(FMT_STRING("RealUri: {}\n"), + song.real_uri); + else + song_print_real_uri(r, song.real_uri, base); +} + +static void +song_print_real_uri(Response &r, const DetachedSong &song, bool base) noexcept +{ + if (song.HasRealURI()) + song_print_real_uri(r, song.GetRealURI(), base); +} + static void PrintRange(Response &r, SongTime start_time, SongTime end_time) noexcept { @@ -71,6 +107,8 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept { song_print_uri(r, song, base); + song_print_real_uri(r, song, base); + PrintRange(r, song.start_time, song.end_time); if (!IsNegative(song.mtime)) @@ -94,6 +132,8 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept { song_print_uri(r, song, base); + song_print_real_uri(r, song, base); + PrintRange(r, song.GetStartTime(), song.GetEndTime()); if (!IsNegative(song.GetLastModified())) From 32fbdb786b130a44b05493e0748078d7a7bc8767 Mon Sep 17 00:00:00 2001 From: datasone Date: Tue, 11 Apr 2023 23:50:15 +0800 Subject: [PATCH 2/5] db/ProxyDatabasePlugin: read `real_uri` from server's song info This commit reads `real_uri` property from `mpd_song` in libmpdclient, and set ProxySong's `real_uri` base on it. The commit solves the issue that cue virtual playlists not working in satellite setup. --- src/db/plugins/ProxyDatabasePlugin.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/db/plugins/ProxyDatabasePlugin.cxx b/src/db/plugins/ProxyDatabasePlugin.cxx index 4efd97ec9c..509498b855 100644 --- a/src/db/plugins/ProxyDatabasePlugin.cxx +++ b/src/db/plugins/ProxyDatabasePlugin.cxx @@ -203,6 +203,10 @@ Copy(TagBuilder &tag, TagType d_tag, ProxySong::ProxySong(const mpd_song *song) :LightSong(mpd_song_get_uri(song), tag2) { + const auto _real_uri = mpd_song_get_real_uri(song); + if (_real_uri != nullptr) + real_uri = _real_uri; + const auto _mtime = mpd_song_get_last_modified(song); if (_mtime > 0) mtime = std::chrono::system_clock::from_time_t(_mtime); From 495f453735996ee1691a878eedf7ea3f2d41d081 Mon Sep 17 00:00:00 2001 From: datasone Date: Tue, 11 Apr 2023 23:54:55 +0800 Subject: [PATCH 3/5] input/CurlInputPlugin: fix malformed value for HTTP Range header CURLOPT_RANGE value requires the format "X-Y", where X or Y can be omitted. The current value provided to libcurl in `CurlInputPlugin` is only offset without the hyphen character and will cause HTTP error while accessing song files with range. This commit fixes it by adding back the hyphen and feed value formatted by `{offset}-` to libcurl. --- src/input/plugins/CurlInputPlugin.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx index 7045bddc12..2f479acd2b 100644 --- a/src/input/plugins/CurlInputPlugin.cxx +++ b/src/input/plugins/CurlInputPlugin.cxx @@ -534,7 +534,7 @@ CurlInputStream::SeekInternal(offset_type new_offset) if (offset > 0) request->SetOption(CURLOPT_RANGE, - fmt::format_int{offset}.c_str()); + fmt::format("{}-", offset).c_str()); StartRequest(); } From 0f7ee068a1ecf9e35d247c44890dd954eec1a322 Mon Sep 17 00:00:00 2001 From: datasone Date: Tue, 11 Apr 2023 23:56:01 +0800 Subject: [PATCH 4/5] db/ProxyDatabasePlugin: read millisecond values for range start and end Range `start` and `end` values in `mpd_song` (libmpdclient) is stored in seconds only, which makes range times in `ProxySong` truncated to seconds and leads to precision loss on satellite cue track range times. This commit reads the added `start_ms` and `end_ms` value from libmpdclient, which represent range start and end time in milliseconds. And uses them to set `start_time` and `end_time` of `ProxySong`. --- src/db/plugins/ProxyDatabasePlugin.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/db/plugins/ProxyDatabasePlugin.cxx b/src/db/plugins/ProxyDatabasePlugin.cxx index 509498b855..29ff450543 100644 --- a/src/db/plugins/ProxyDatabasePlugin.cxx +++ b/src/db/plugins/ProxyDatabasePlugin.cxx @@ -211,8 +211,8 @@ ProxySong::ProxySong(const mpd_song *song) if (_mtime > 0) mtime = std::chrono::system_clock::from_time_t(_mtime); - start_time = SongTime::FromS(mpd_song_get_start(song)); - end_time = SongTime::FromS(mpd_song_get_end(song)); + start_time = SongTime::FromMS(mpd_song_get_start_ms(song)); + end_time = SongTime::FromMS(mpd_song_get_end_ms(song)); const auto *af = mpd_song_get_audio_format(song); if (af != nullptr) { From 630d68d6a7687d3da2e9f5e2f4639050452b7e75 Mon Sep 17 00:00:00 2001 From: datasone Date: Wed, 12 Apr 2023 00:00:27 +0800 Subject: [PATCH 5/5] player/Thread: merge tag for songs have range instead of replacing with `chunk->tag` While playing first track of cue file on satellite setup, the tag of currentsong will loss. This is due to `chunk->tag` overwriting song tag in `PlayerControl::PlayChunk`. In local cue files, `song_tag` is used to form `stream_tag` and merged into `chunk->tag`, but `song_tag` only works for local files. So the `chunk->tag` is decided by decoded tag from audio files, which in cue's case is the whole album file and doesn't contain track metadata. This commit fixes the issue by merging song's tag with `chunk->tag` (with `chunk->tag` prioritized) in `PlayerControl::LockUpdateSongTag`, only when the song's `end_time` is not zero. As the `end_time` will only be set on cue virtual songs or with `rangeid` command, i.e. songs represented by portion of a whole file, where metadata of the portion and whole file may differ. The impact to current behaviour is minimized: changes only happen when users use `rangeid` command on a remote song, and they expect that song tags in database should be completely overwritten with tags provided by remote stream instead of merged. --- src/player/Thread.cxx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/player/Thread.cxx b/src/player/Thread.cxx index 6e57ecb4ad..af5e85b8aa 100644 --- a/src/player/Thread.cxx +++ b/src/player/Thread.cxx @@ -903,7 +903,10 @@ PlayerControl::LockUpdateSongTag(DetachedSong &song, streams may change tags dynamically */ return; - song.SetTag(new_tag); + if (!song.GetEndTime().IsZero()) + song.SetTag(Tag::Merge(song.GetTag(), new_tag)); + else + song.SetTag(new_tag); LockSetTaggedSong(song);