Implemented Subsonic command getNowPlaying, fixes #792
This commit is contained in:
@@ -77,6 +77,8 @@ namespace lms::scrobbling
|
||||
|
||||
void ScrobblingService::listenStarted(const Listen& listen)
|
||||
{
|
||||
insertNowPlayingEntry(listen);
|
||||
|
||||
if (std::optional<ScrobblingBackend> backend{ getUserBackend(listen.userId) })
|
||||
_scrobblingBackends[*backend]->listenStarted(listen);
|
||||
}
|
||||
@@ -93,6 +95,24 @@ namespace lms::scrobbling
|
||||
_scrobblingBackends[*backend]->addTimedListen(listen);
|
||||
}
|
||||
|
||||
void ScrobblingService::visitNowPlayingListens(const std::function<void(Clock::time_point startedAt, const Listen&)>& visitor, db::UserId userId)
|
||||
{
|
||||
const Clock::time_point now{ Clock::now() };
|
||||
|
||||
std::shared_lock lock{ _nowPlayingEntriesMutex };
|
||||
|
||||
for (const auto& [entryUserId, entry] : _nowPlayingEntries)
|
||||
{
|
||||
if (userId.isValid() && entryUserId != userId)
|
||||
continue;
|
||||
|
||||
if (entry.expiryAt <= now)
|
||||
continue;
|
||||
|
||||
visitor(entry.startedAt, Listen{ .userId = entryUserId, .trackId = entry.trackId });
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ScrobblingBackend> ScrobblingService::getUserBackend(UserId userId)
|
||||
{
|
||||
std::optional<ScrobblingBackend> backend;
|
||||
@@ -253,4 +273,20 @@ namespace lms::scrobbling
|
||||
res = db::Listen::getTopTracks(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
|
||||
void ScrobblingService::insertNowPlayingEntry(const Listen& listen)
|
||||
{
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
if (const db::Track::pointer track{ db::Track::find(session, listen.trackId) })
|
||||
{
|
||||
const Clock::time_point now{ Clock::now() };
|
||||
|
||||
std::unique_lock lock{ _nowPlayingEntriesMutex };
|
||||
|
||||
// Add an extra delay to ensure the listen is not purged too early
|
||||
_nowPlayingEntries.insert_or_assign(listen.userId, NowPlayingEntry{ .startedAt = now, .expiryAt = now + track->getDuration() + std::chrono::seconds{ 5 }, .trackId = listen.trackId });
|
||||
}
|
||||
}
|
||||
} // namespace lms::scrobbling
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "services/scrobbling/IScrobblingService.hpp"
|
||||
@@ -33,12 +34,16 @@ namespace lms::scrobbling
|
||||
{
|
||||
public:
|
||||
ScrobblingService(boost::asio::io_context& ioContext, db::IDb& db);
|
||||
~ScrobblingService();
|
||||
~ScrobblingService() override;
|
||||
|
||||
ScrobblingService(const ScrobblingService&) = delete;
|
||||
ScrobblingService& operator=(const ScrobblingService&) = delete;
|
||||
|
||||
private:
|
||||
void listenStarted(const Listen& listen) override;
|
||||
void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) override;
|
||||
void addTimedListen(const TimedListen& listen) override;
|
||||
void visitNowPlayingListens(const std::function<void(Clock::time_point startedAt, const Listen&)>& visitor, db::UserId userId) override;
|
||||
|
||||
ArtistContainer getRecentArtists(const ArtistFindParameters& params) override;
|
||||
ReleaseContainer getRecentReleases(const FindParameters& params) override;
|
||||
@@ -56,8 +61,18 @@ namespace lms::scrobbling
|
||||
|
||||
std::optional<db::ScrobblingBackend> getUserBackend(db::UserId userId);
|
||||
|
||||
void insertNowPlayingEntry(const Listen& listen);
|
||||
|
||||
db::IDb& _db;
|
||||
std::unordered_map<db::ScrobblingBackend, std::unique_ptr<IScrobblingBackend>> _scrobblingBackends;
|
||||
};
|
||||
|
||||
std::shared_mutex _nowPlayingEntriesMutex;
|
||||
struct NowPlayingEntry
|
||||
{
|
||||
Clock::time_point startedAt;
|
||||
Clock::time_point expiryAt;
|
||||
db::TrackId trackId;
|
||||
};
|
||||
std::unordered_map<db::UserId, NowPlayingEntry> _nowPlayingEntries;
|
||||
};
|
||||
} // namespace lms::scrobbling
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "database/objects/ReleaseId.hpp"
|
||||
#include "database/objects/TrackId.hpp"
|
||||
#include "database/objects/Types.hpp"
|
||||
#include "database/objects/UserId.hpp"
|
||||
#include "services/scrobbling/Listen.hpp"
|
||||
|
||||
namespace lms::db
|
||||
@@ -46,12 +47,22 @@ namespace lms::scrobbling
|
||||
public:
|
||||
virtual ~IScrobblingService() = default;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
// Scrobbling
|
||||
|
||||
// Notify that a listen has started (for now-playing purposes)
|
||||
virtual void listenStarted(const Listen& listen) = 0;
|
||||
|
||||
// Notify that a listen has finished (for scrobbling purposes)
|
||||
virtual void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> playedDuration = std::nullopt) = 0;
|
||||
|
||||
// Used to add listens afterwards (after some offline listening for example)
|
||||
virtual void addTimedListen(const TimedListen& listen) = 0;
|
||||
|
||||
// Visit all now-playing listens
|
||||
virtual void visitNowPlayingListens(const std::function<void(Clock::time_point startedAt, const Listen&)>& visitor, db::UserId userId = {}) = 0;
|
||||
|
||||
// Stats
|
||||
using ArtistContainer = db::RangeResults<db::ArtistId>;
|
||||
using ReleaseContainer = db::RangeResults<db::ReleaseId>;
|
||||
|
||||
Reference in New Issue
Block a user