diff --git a/src/libs/av/CMakeLists.txt b/src/libs/av/CMakeLists.txt
index c2d03eb6..adb37766 100644
--- a/src/libs/av/CMakeLists.txt
+++ b/src/libs/av/CMakeLists.txt
@@ -2,6 +2,7 @@
add_library(lmsav SHARED
impl/AvInfo.cpp
impl/AvTranscoder.cpp
+ impl/AvTranscodeResourceHandler.cpp
impl/AvTypes.cpp
)
@@ -18,6 +19,7 @@ target_link_libraries(lmsav PUBLIC
lmsutils
avformat
avutil
+ wt
)
install(TARGETS lmsav DESTINATION lib)
diff --git a/src/libs/av/impl/AvTranscodeResourceHandler.cpp b/src/libs/av/impl/AvTranscodeResourceHandler.cpp
new file mode 100644
index 00000000..fbea1f2f
--- /dev/null
+++ b/src/libs/av/impl/AvTranscodeResourceHandler.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS 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 LMS. If not, see .
+ */
+
+#include "AvTranscodeResourceHandler.hpp"
+
+namespace Av
+{
+
+ std::unique_ptr
+ createTranscodeResourceHandler(const std::filesystem::path& trackPath, const TranscodeParameters& parameters)
+ {
+ return std::make_unique(trackPath, parameters);
+ }
+
+ // TODO set some nice HTTP return code
+
+ TranscodeResourceHandler::TranscodeResourceHandler(const std::filesystem::path& trackPath, const TranscodeParameters& parameters)
+ : _transcoder {trackPath, parameters}
+ {
+ _transcoder.start();
+ }
+
+ void
+ TranscodeResourceHandler::processRequest(const Wt::Http::Request& /*request*/, Wt::Http::Response& response)
+ {
+ response.setMimeType(_transcoder.getOutputMimeType());
+
+ if (!_transcoder.isComplete())
+ {
+ std::vector buffer;
+
+ _transcoder.process(buffer, _chunkSize);
+ response.out().write(reinterpret_cast(&buffer[0]), buffer.size());
+ }
+ }
+
+ bool
+ TranscodeResourceHandler::isFinished() const
+ {
+ return _transcoder.isComplete();
+ }
+}
+
diff --git a/src/libs/av/impl/AvTranscodeResourceHandler.hpp b/src/libs/av/impl/AvTranscodeResourceHandler.hpp
new file mode 100644
index 00000000..351e6e45
--- /dev/null
+++ b/src/libs/av/impl/AvTranscodeResourceHandler.hpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS 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 LMS. If not, see .
+ */
+
+#pragma once
+
+#include
+#include "av/AvTranscoder.hpp"
+#include "utils/IResourceHandler.hpp"
+
+namespace Av
+{
+
+ class TranscodeResourceHandler final : public IResourceHandler
+ {
+ public:
+ TranscodeResourceHandler(const std::filesystem::path& trackPath, const TranscodeParameters& parameters);
+
+ private:
+
+ void processRequest(const Wt::Http::Request& request, Wt::Http::Response& reponse) override;
+ bool isFinished() const override;
+
+ static constexpr std::size_t _chunkSize {262144};
+ const std::filesystem::path _trackPath;
+ Transcoder _transcoder;
+
+ };
+}
+
diff --git a/src/libs/av/include/av/AvTranscodeResourceHandlerCreator.hpp b/src/libs/av/include/av/AvTranscodeResourceHandlerCreator.hpp
new file mode 100644
index 00000000..041ebc91
--- /dev/null
+++ b/src/libs/av/include/av/AvTranscodeResourceHandlerCreator.hpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS 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 LMS. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "utils/IResourceHandler.hpp"
+
+namespace Av
+{
+ struct TranscodeParameters;
+
+ std::unique_ptr createTranscodeResourceHandler(const std::filesystem::path& trackPath, const TranscodeParameters& parameters);
+
+}
+
diff --git a/src/libs/subsonic/CMakeLists.txt b/src/libs/subsonic/CMakeLists.txt
index 0531b7bf..e5743a37 100644
--- a/src/libs/subsonic/CMakeLists.txt
+++ b/src/libs/subsonic/CMakeLists.txt
@@ -1,5 +1,7 @@
add_library(lmssubsonic SHARED
+ impl/ParameterParsing.cpp
+ impl/Stream.cpp
impl/SubsonicId.cpp
impl/SubsonicResource.cpp
impl/SubsonicResponse.cpp
diff --git a/src/libs/subsonic/impl/ParameterParsing.cpp b/src/libs/subsonic/impl/ParameterParsing.cpp
new file mode 100644
index 00000000..dbfd0656
--- /dev/null
+++ b/src/libs/subsonic/impl/ParameterParsing.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS 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 LMS. If not, see .
+ */
+
+#include "ParameterParsing.hpp"
+
+namespace StringUtils
+{
+ template<>
+ std::optional
+ StringUtils::readAs(const std::string& str)
+ {
+ return API::Subsonic::IdFromString(str);
+ }
+
+ template<>
+ std::optional
+ StringUtils::readAs(const std::string& str)
+ {
+ if (str == "true")
+ return true;
+ else if (str == "false")
+ return false;
+
+ return {};
+ }
+}
+
diff --git a/src/libs/subsonic/impl/ParameterParsing.hpp b/src/libs/subsonic/impl/ParameterParsing.hpp
new file mode 100644
index 00000000..5ddad43a
--- /dev/null
+++ b/src/libs/subsonic/impl/ParameterParsing.hpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS 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 LMS. If not, see .
+ */
+#pragma once
+
+#include
+
+#include "utils/String.hpp"
+#include "SubsonicId.hpp"
+#include "SubsonicResponse.hpp"
+
+namespace API::Subsonic
+{
+
+ template
+ std::vector
+ getMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& paramName)
+ {
+ std::vector res;
+
+ auto it = parameterMap.find(paramName);
+ if (it == parameterMap.end())
+ return res;
+
+ for (const std::string& param : it->second)
+ {
+ auto value {StringUtils::readAs(param)};
+ if (!value)
+ throw BadParameterFormatGenericError {paramName};
+
+ res.emplace_back(std::move(*value));
+ }
+
+ return res;
+ }
+
+ template
+ std::vector
+ getMandatoryMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
+ {
+ std::vector res {getMultiParametersAs(parameterMap, param)};
+ if (res.empty())
+ throw RequiredParameterMissingError {};
+
+ return res;
+ }
+
+ template
+ std::optional
+ getParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
+ {
+ std::vector params {getMultiParametersAs(parameterMap, param)};
+
+ if (params.size() != 1)
+ return {};
+
+ return T { std::move(params.front()) };
+ }
+
+ template
+ T
+ getMandatoryParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
+ {
+ auto res {getParameterAs(parameterMap, param)};
+ if (!res)
+ throw RequiredParameterMissingError {};
+
+ return *res;
+ }
+
+
+}
+
+namespace StringUtils
+{
+ template<>
+ std::optional
+ StringUtils::readAs(const std::string& str);
+
+ template<>
+ std::optional
+ StringUtils::readAs(const std::string& str);
+}
+
diff --git a/src/libs/subsonic/impl/RequestContext.hpp b/src/libs/subsonic/impl/RequestContext.hpp
new file mode 100644
index 00000000..ec10a226
--- /dev/null
+++ b/src/libs/subsonic/impl/RequestContext.hpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS 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 LMS. If not, see .
+ */
+
+#pragma once
+
+#include
+
+#include
+#include
+
+
+namespace Database
+{
+ class Session;
+}
+
+namespace API::Subsonic
+{
+
+ struct RequestContext
+ {
+ const Wt::Http::ParameterMap& parameters;
+ Database::Session& dbSession;
+ std::string userName;
+ };
+
+}
+
diff --git a/src/libs/subsonic/impl/Stream.cpp b/src/libs/subsonic/impl/Stream.cpp
new file mode 100644
index 00000000..e58f33cd
--- /dev/null
+++ b/src/libs/subsonic/impl/Stream.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS 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 LMS. If not, see .
+ */
+
+#include "Stream.hpp"
+
+#include "av/AvTranscoder.hpp"
+#include "av/AvTranscodeResourceHandlerCreator.hpp"
+#include "av/AvTypes.hpp"
+#include "database/Session.hpp"
+#include "database/Track.hpp"
+#include "database/User.hpp"
+#include "utils/IResourceHandler.hpp"
+#include "utils/FileResourceHandlerCreator.hpp"
+#include "utils/Utils.hpp"
+#include "ParameterParsing.hpp"
+#include "SubsonicId.hpp"
+
+using namespace Database;
+
+namespace API::Subsonic::Stream
+{
+
+static
+Av::Encoding
+userTranscodeFormatToAvEncoding(AudioFormat format)
+{
+ switch (format)
+ {
+ case AudioFormat::MP3: return Av::Encoding::MP3;
+ case AudioFormat::OGG_OPUS: return Av::Encoding::OGG_OPUS;
+ case AudioFormat::MATROSKA_OPUS: return Av::Encoding::MATROSKA_OPUS;
+ case AudioFormat::OGG_VORBIS: return Av::Encoding::OGG_VORBIS;
+ case AudioFormat::WEBM_VORBIS: return Av::Encoding::WEBM_VORBIS;
+ default: return Av::Encoding::OGG_OPUS;
+ }
+}
+
+struct StreamParameters
+{
+ std::filesystem::path trackPath;
+ std::optional transcodeParameters;
+};
+
+
+static
+StreamParameters
+getStreamParameters(RequestContext& context)
+{
+ // Mandatory params
+ Id id {getMandatoryParameterAs(context.parameters, "id")};
+
+ // Optional params
+ std::optional maxBitRate {getParameterAs(context.parameters, "maxBitRate")};
+ std::optional format {getParameterAs(context.parameters, "format")};
+
+ StreamParameters parameters;
+
+ auto transaction {context.dbSession.createSharedTransaction()};
+
+ {
+ auto track {Track::getById(context.dbSession, id.value)};
+ if (!track)
+ throw RequestedDataNotFoundError {};
+
+ parameters.trackPath = track->getPath();
+ }
+
+ {
+ const User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
+ if (!user)
+ throw UserNotAuthorizedError {};
+
+ // format = "raw" => no transcode. Other format values will be ignored
+ const bool transcode {(!format || (*format != "raw")) && user->getAudioTranscodeEnable()};
+ if (transcode)
+ {
+ // "If set to zero, no limit is imposed"
+ if (!maxBitRate || *maxBitRate == 0)
+ maxBitRate = user->getAudioTranscodeBitrate() / 1000;
+
+ *maxBitRate = clamp(*maxBitRate, std::size_t {48}, user->getMaxAudioTranscodeBitrate() / 1000);
+
+ Av::TranscodeParameters transcodeParameters;
+
+ transcodeParameters.bitrate = *maxBitRate * 1000;
+ transcodeParameters.encoding = userTranscodeFormatToAvEncoding(user->getAudioTranscodeFormat());
+
+ parameters.transcodeParameters = std::move(transcodeParameters);
+ }
+ }
+
+ return parameters;
+}
+
+void
+handle(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response)
+{
+ std::shared_ptr resourceHandler;
+
+ Wt::Http::ResponseContinuation *continuation = request.continuation();
+ if (!continuation)
+ {
+ StreamParameters streamParameters {getStreamParameters(context)};
+ if (streamParameters.transcodeParameters)
+ resourceHandler = Av::createTranscodeResourceHandler(streamParameters.trackPath, *streamParameters.transcodeParameters);
+ else
+ resourceHandler = createFileResourceHandler(streamParameters.trackPath);
+ }
+ else
+ {
+ resourceHandler = Wt::cpp17::any_cast>(continuation->data());
+ }
+
+ resourceHandler->processRequest(request, response);
+ if (!resourceHandler->isFinished())
+ {
+ Wt::Http::ResponseContinuation *continuation = response.createContinuation();
+ continuation->setData(resourceHandler);
+ }
+}
+
+}
+
diff --git a/src/libs/utils/include/utils/FileResourceHandler.hpp b/src/libs/subsonic/impl/Stream.hpp
similarity index 58%
rename from src/libs/utils/include/utils/FileResourceHandler.hpp
rename to src/libs/subsonic/impl/Stream.hpp
index fb663032..bf7fade3 100644
--- a/src/libs/utils/include/utils/FileResourceHandler.hpp
+++ b/src/libs/subsonic/impl/Stream.hpp
@@ -19,22 +19,13 @@
#pragma once
-#include
#include
#include
+#include "RequestContext.hpp"
-// Helper used to deliver file contents from a WResource
-namespace FileResourceHandler
+namespace API::Subsonic::Stream
{
- struct ContinuationData
- {
- std::filesystem::path path;
- ::uint64_t beyondLastByte;
- ::uint64_t offset;
- };
-
- std::optional handleInitialRequest(const Wt::Http::Request& request, Wt::Http::Response& response, const std::filesystem::path& path);
- std::optional handleContinuationRequest(const Wt::Http::Request& request, Wt::Http::Response& response, const ContinuationData& continuationData);
+ void handle(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response);
}
diff --git a/src/libs/subsonic/impl/SubsonicId.hpp b/src/libs/subsonic/impl/SubsonicId.hpp
index 02915a4c..91cc61bc 100644
--- a/src/libs/subsonic/impl/SubsonicId.hpp
+++ b/src/libs/subsonic/impl/SubsonicId.hpp
@@ -17,6 +17,8 @@
* along with LMS. If not, see .
*/
+#pragma once
+
#include
#include "database/Types.hpp"
diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp
index c538d1eb..c09a4c98 100644
--- a/src/libs/subsonic/impl/SubsonicResource.cpp
+++ b/src/libs/subsonic/impl/SubsonicResource.cpp
@@ -19,15 +19,10 @@
#include "subsonic/SubsonicResource.hpp"
#include
-#include
-#include
-#include
-#include
#include
#include "auth/IPasswordService.hpp"
-#include "av/AvTranscoder.hpp"
#include "cover/ICoverArtGrabber.hpp"
#include "database/Artist.hpp"
#include "database/Cluster.hpp"
@@ -40,10 +35,13 @@
#include "database/User.hpp"
#include "recommendation/IEngine.hpp"
#include "utils/Logger.hpp"
+#include "utils/Random.hpp"
#include "utils/Service.hpp"
#include "utils/String.hpp"
#include "utils/Utils.hpp"
-#include "SubsonicId.hpp"
+#include "ParameterParsing.hpp"
+#include "RequestContext.hpp"
+#include "Stream.hpp"
#include "SubsonicResponse.hpp"
using namespace Database;
@@ -65,25 +63,6 @@ namespace API::Subsonic
namespace StringUtils
{
- template<>
- std::optional
- StringUtils::readAs(const std::string& str)
- {
- return API::Subsonic::IdFromString(str);
- }
-
- template<>
- std::optional
- StringUtils::readAs(const std::string& str)
- {
- if (str == "true")
- return true;
- else if (str == "false")
- return false;
-
- return {};
- }
-
template<>
std::optional
StringUtils::readAs(const std::string& str)
@@ -122,21 +101,6 @@ namespace StringUtils
namespace API::Subsonic
{
-struct ClientInfo
-{
- std::string name;
- std::string user;
- std::string password;
- ClientVersion version;
-};
-
-struct RequestContext
-{
- const Wt::Http::ParameterMap& parameters;
- Session& dbSession;
- std::string userName;
-};
-
static
std::string
makeNameFilesystemCompatible(const std::string& name)
@@ -144,62 +108,6 @@ makeNameFilesystemCompatible(const std::string& name)
return StringUtils::replaceInString(name, "/", "_");
}
-template
-std::vector
-getMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& paramName)
-{
- std::vector res;
-
- auto it = parameterMap.find(paramName);
- if (it == parameterMap.end())
- return res;
-
- for (const std::string& param : it->second)
- {
- auto value {StringUtils::readAs(param)};
- if (!value)
- throw BadParameterFormatGenericError {paramName};
-
- res.emplace_back(std::move(*value));
- }
-
- return res;
-}
-
-template
-std::vector
-getMandatoryMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
-{
- std::vector res {getMultiParametersAs(parameterMap, param)};
- if (res.empty())
- throw RequiredParameterMissingError {};
-
- return res;
-}
-
-template
-std::optional
-getParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
-{
- std::vector params {getMultiParametersAs(parameterMap, param)};
-
- if (params.size() != 1)
- return {};
-
- return T { std::move(params.front()) };
-}
-
-template
-T
-getMandatoryParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
-{
- auto res {getParameterAs(parameterMap, param)};
- if (!res)
- throw RequiredParameterMissingError {};
-
- return *res;
-}
-
static
std::string
decodePasswordIfNeeded(const std::string& password)
@@ -216,6 +124,14 @@ decodePasswordIfNeeded(const std::string& password)
return password;
}
+struct ClientInfo
+{
+ std::string name;
+ std::string user;
+ std::string password;
+ ClientVersion version;
+};
+
static
ClientInfo
getClientInfo(const Wt::Http::ParameterMap& parameters)
@@ -238,14 +154,6 @@ getClientInfo(const Wt::Http::ParameterMap& parameters)
return res;
}
-// MediaRetrievals
-struct MediaRetrievalResult
-{
- std::string mimeType;
- std::vector data;
- Wt::cpp17::any continuationData;
-};
-
SubsonicResource::SubsonicResource(Db& db)
: _sessionPool {db}
{
@@ -1165,9 +1073,7 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
std::make_move_iterator(std::end(similarArtistTracks)));
}
- auto now {std::chrono::system_clock::now()};
- std::mt19937 randGenerator {static_cast(std::chrono::duration_cast(now.time_since_epoch()).count())};
- std::shuffle(std::begin(tracks), std::end(tracks), randGenerator);
+ Random::shuffleContainer(tracks);
Response response {Response::createOkResponse()};
Response::Node& similarSongsNode {response.createNode(id3 ? "similarSongs2" : "similarSongs")};
@@ -1775,111 +1681,8 @@ handleNotImplemented(RequestContext&)
}
static
-Av::Encoding
-userTranscodeFormatToAvEncoding(AudioFormat format)
-{
- switch (format)
- {
- case AudioFormat::MP3: return Av::Encoding::MP3;
- case AudioFormat::OGG_OPUS: return Av::Encoding::OGG_OPUS;
- case AudioFormat::MATROSKA_OPUS: return Av::Encoding::MATROSKA_OPUS;
- case AudioFormat::OGG_VORBIS: return Av::Encoding::OGG_VORBIS;
- case AudioFormat::WEBM_VORBIS: return Av::Encoding::WEBM_VORBIS;
- default: return Av::Encoding::OGG_OPUS;
- }
-}
-
-static
-std::shared_ptr
-createTranscoder(RequestContext& context)
-{
- // Mandatory params
- Id id {getMandatoryParameterAs(context.parameters, "id")};
-
- // Optional params
- std::optional maxBitRate {getParameterAs(context.parameters, "maxBitRate")};
- std::optional format {getParameterAs(context.parameters, "format")};
-
- Av::TranscodeParameters parameters {};
- parameters.stripMetadata = false; // Since it can be cached and some players read the metadata from the downloaded file
-
- std::filesystem::path trackPath;
- {
- auto transaction {context.dbSession.createSharedTransaction()};
-
- {
- auto track {Track::getById(context.dbSession, id.value)};
- if (!track)
- throw RequestedDataNotFoundError {};
-
- trackPath = track->getPath();
- }
-
- {
- const User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
- if (!user)
- throw UserNotAuthorizedError {};
-
- // format = "raw" => no transcode. Other format values will be ignored
- const bool transcode {(!format || (format && *format != "raw")) && user->getAudioTranscodeEnable()};
- if (transcode)
- {
- // "If set to zero, no limit is imposed"
- if (!maxBitRate || *maxBitRate == 0)
- maxBitRate = user->getAudioTranscodeBitrate() / 1000;
-
- *maxBitRate = clamp(*maxBitRate, std::size_t {48}, user->getMaxAudioTranscodeBitrate() / 1000);
-
- parameters.bitrate = *maxBitRate * 1000;
- parameters.encoding = userTranscodeFormatToAvEncoding(user->getAudioTranscodeFormat());
- }
- }
- }
-
- return std::make_shared(trackPath, parameters);
-}
-
-static
-MediaRetrievalResult
-handleStream(RequestContext& context, Wt::Http::ResponseContinuation* continuation)
-{
- MediaRetrievalResult res;
-
- std::shared_ptr transcoder;
-
- if (!continuation)
- {
- transcoder = createTranscoder(context);
- transcoder->start();
-
- res.mimeType = transcoder->getOutputMimeType();
- LMS_LOG(API_SUBSONIC, DEBUG) << "Mime type set to '" << transcoder->getOutputMimeType() << "'";
- }
- else
- {
- transcoder = Wt::cpp17::any_cast>(continuation->data());
- }
-
- if (!transcoder)
- throw InternalErrorGenericError {"Cannot create transcoder"};
-
- if (!transcoder->isComplete())
- {
- static constexpr std::size_t chunkSize {65536*4};
- res.data.reserve(chunkSize);
-
- transcoder->process(res.data, chunkSize);
- }
-
- if (!transcoder->isComplete())
- res.continuationData = std::move(transcoder);
-
- return res;
-}
-
-static
-MediaRetrievalResult
-handleGetCoverArt(RequestContext& context, Wt::Http::ResponseContinuation*)
+void
+handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/, Wt::Http::Response& response)
{
// Mandatory params
Id id {getMandatoryParameterAs(context.parameters, "id")};
@@ -1887,23 +1690,21 @@ handleGetCoverArt(RequestContext& context, Wt::Http::ResponseContinuation*)
std::size_t size {getParameterAs(context.parameters, "size").value_or(256)};
size = clamp(size, std::size_t {32}, std::size_t {1024});
- MediaRetrievalResult res;
-
+ std::vector data;
switch (id.type)
{
case Id::Type::Track:
- res.data = ServiceProvider::get()->getFromTrack(context.dbSession, id.value, CoverArt::Format::JPEG, size);
+ data = ServiceProvider::get()->getFromTrack(context.dbSession, id.value, CoverArt::Format::JPEG, size);
break;
case Id::Type::Release:
- res.data = ServiceProvider::get()->getFromRelease(context.dbSession, id.value, CoverArt::Format::JPEG, size);
+ data = ServiceProvider::get()->getFromRelease(context.dbSession, id.value, CoverArt::Format::JPEG, size);
break;
default:
throw BadParameterGenericError {"id"};
}
- res.mimeType = CoverArt::formatToMimeType(CoverArt::Format::JPEG);
-
- return res;
+ response.out().write(reinterpret_cast(&data[0]), data.size());
+ response.setMimeType(CoverArt::formatToMimeType(CoverArt::Format::JPEG));
}
using RequestHandlerFunc = std::function;
@@ -2019,12 +1820,12 @@ static std::unordered_map requestEntryPoints
{"startScan", {handleNotImplemented, true}},
};
-using MediaRetrievalHandlerFunc = std::function;
+using MediaRetrievalHandlerFunc = std::function;
static std::unordered_map mediaRetrievalHandlers
{
// Media retrieval
{"getCoverArt", handleGetCoverArt},
- {"stream", handleStream},
+ {"stream", Stream::handle},
};
void
@@ -2091,26 +1892,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
auto itStreamHandler {mediaRetrievalHandlers.find(requestPath)};
if (itStreamHandler != mediaRetrievalHandlers.end())
{
- MediaRetrievalResult res {itStreamHandler->second(requestContext, request.continuation())};
-
- if (!res.mimeType.empty())
- response.setMimeType(res.mimeType);
- if (!res.data.empty())
- {
- response.out().write(reinterpret_cast(&res.data[0]), res.data.size());
- if (!response.out())
- {
- LMS_LOG(API_SUBSONIC, ERROR) << "Write failed!";
- return;
- }
- }
-
- if (res.continuationData.has_value())
- {
- auto continuation {response.createContinuation()};
- continuation->setData(std::move(res.continuationData));
- }
-
+ itStreamHandler->second(requestContext, request, response);
LMS_LOG(API_SUBSONIC, DEBUG) << "Request " << requestId << " '" << requestPath << "' handled!";
return;
}
diff --git a/src/libs/subsonic/impl/SubsonicResponse.hpp b/src/libs/subsonic/impl/SubsonicResponse.hpp
index f153e6f7..a88217ad 100644
--- a/src/libs/subsonic/impl/SubsonicResponse.hpp
+++ b/src/libs/subsonic/impl/SubsonicResponse.hpp
@@ -16,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see .
*/
+#pragma once
#include