Now delevering the raw file when transcoding is not enabled (no more remux done via fmmpeg). Fixes #28, Ref #36
This commit is contained in:
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
var LMS = LMS || {};
|
var LMS = LMS || {};
|
||||||
|
|
||||||
|
const Mode = {
|
||||||
|
Transcode: 1,
|
||||||
|
File: 2,
|
||||||
|
}
|
||||||
|
Object.freeze(Mode);
|
||||||
|
|
||||||
LMS.mediaplayer = function () {
|
LMS.mediaplayer = function () {
|
||||||
|
|
||||||
var _root = {};
|
var _root = {};
|
||||||
@@ -9,6 +15,7 @@ LMS.mediaplayer = function () {
|
|||||||
var _offset = 0;
|
var _offset = 0;
|
||||||
var _duration = 0;
|
var _duration = 0;
|
||||||
var _audioSrc;
|
var _audioSrc;
|
||||||
|
var _mode = Mode.File;
|
||||||
|
|
||||||
var _updateControls = function() {
|
var _updateControls = function() {
|
||||||
if (_elems.audio.paused) {
|
if (_elems.audio.paused) {
|
||||||
@@ -125,11 +132,22 @@ LMS.mediaplayer = function () {
|
|||||||
if (!_elems.audio.hasAttribute("src"))
|
if (!_elems.audio.hasAttribute("src"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_offset = parseInt(_elems.seek.value, 10);
|
let selectedOffset = parseInt(_elems.seek.value, 10);
|
||||||
_elems.audio.src = _audioSrc + "&offset=" + _offset;
|
|
||||||
_elems.audio.load();
|
|
||||||
_playTrack();
|
|
||||||
|
|
||||||
|
switch (_mode) {
|
||||||
|
case Mode.Transcode:
|
||||||
|
_offset = selectedOffset;
|
||||||
|
_elems.audio.src = _audioSrc + "&offset=" + _offset;
|
||||||
|
_elems.audio.load();
|
||||||
|
_elems.audio.currentTime = 0;
|
||||||
|
_playTrack();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Mode.File:
|
||||||
|
_elems.audio.currentTime = selectedOffset;
|
||||||
|
_playTrack();
|
||||||
|
break;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_elems.audio.addEventListener("play", _updateControls);
|
_elems.audio.addEventListener("play", _updateControls);
|
||||||
@@ -175,6 +193,7 @@ LMS.mediaplayer = function () {
|
|||||||
_offset = 0;
|
_offset = 0;
|
||||||
_duration = params.duration;
|
_duration = params.duration;
|
||||||
_audioSrc = params.resource;
|
_audioSrc = params.resource;
|
||||||
|
_mode = params.mode;
|
||||||
|
|
||||||
_elems.seek.max = _duration;
|
_elems.seek.max = _duration;
|
||||||
_elems.audio.src = _audioSrc;
|
_elems.audio.src = _audioSrc;
|
||||||
|
|||||||
@@ -283,6 +283,16 @@ User::getStarredTracks() const
|
|||||||
return std::vector<Wt::Dbo::ptr<Track>>(_starredTracks.begin(), _starredTracks.end());
|
return std::vector<Wt::Dbo::ptr<Track>>(_starredTracks.begin(), _starredTracks.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
User::checkBitrate(Database::Bitrate bitrate) const
|
||||||
|
{
|
||||||
|
if (audioTranscodeAllowedBitrates.find(bitrate) == std::cend(audioTranscodeAllowedBitrates))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return static_cast<Bitrate>(_maxAudioTranscodeBitrate) >= bitrate;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Database
|
} // namespace Database
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
bool hasStarredTrack(Wt::Dbo::ptr<Track> track) const;
|
bool hasStarredTrack(Wt::Dbo::ptr<Track> track) const;
|
||||||
std::vector<Wt::Dbo::ptr<Track>> getStarredTracks() const;
|
std::vector<Wt::Dbo::ptr<Track>> getStarredTracks() const;
|
||||||
|
|
||||||
|
bool checkBitrate(Bitrate bitrate) const;
|
||||||
|
|
||||||
template<class Action>
|
template<class Action>
|
||||||
void persist(Action& a)
|
void persist(Action& a)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ add_executable(lms
|
|||||||
ui/explore/ReleaseView.cpp
|
ui/explore/ReleaseView.cpp
|
||||||
ui/explore/TracksInfoView.cpp
|
ui/explore/TracksInfoView.cpp
|
||||||
ui/explore/TracksView.cpp
|
ui/explore/TracksView.cpp
|
||||||
|
ui/resource/AudioFileResource.cpp
|
||||||
|
ui/resource/AudioTranscodeResource.cpp
|
||||||
ui/resource/ImageResource.cpp
|
ui/resource/ImageResource.cpp
|
||||||
ui/resource/AudioResource.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(lms PRIVATE
|
target_include_directories(lms PRIVATE
|
||||||
|
|||||||
@@ -44,8 +44,9 @@
|
|||||||
#include "admin/DatabaseSettingsView.hpp"
|
#include "admin/DatabaseSettingsView.hpp"
|
||||||
#include "admin/UserView.hpp"
|
#include "admin/UserView.hpp"
|
||||||
#include "admin/UsersView.hpp"
|
#include "admin/UsersView.hpp"
|
||||||
|
#include "resource/AudioFileResource.hpp"
|
||||||
|
#include "resource/AudioTranscodeResource.hpp"
|
||||||
#include "resource/ImageResource.hpp"
|
#include "resource/ImageResource.hpp"
|
||||||
#include "resource/AudioResource.hpp"
|
|
||||||
#include "Auth.hpp"
|
#include "Auth.hpp"
|
||||||
#include "LmsApplicationException.hpp"
|
#include "LmsApplicationException.hpp"
|
||||||
#include "MediaPlayer.hpp"
|
#include "MediaPlayer.hpp"
|
||||||
@@ -400,7 +401,8 @@ void
|
|||||||
LmsApplication::createHome()
|
LmsApplication::createHome()
|
||||||
{
|
{
|
||||||
_imageResource = std::make_shared<ImageResource>();
|
_imageResource = std::make_shared<ImageResource>();
|
||||||
_audioResource = std::make_shared<AudioResource>();
|
_audioTranscodeResource = std::make_shared<AudioTranscodeResource>();
|
||||||
|
_audioFileResource = std::make_shared<AudioFileResource>();
|
||||||
|
|
||||||
setConfirmCloseMessage(Wt::WString::tr("Lms.quit-confirm"));
|
setConfirmCloseMessage(Wt::WString::tr("Lms.quit-confirm"));
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ namespace Database {
|
|||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
class AudioResource;
|
class AudioTranscodeResource;
|
||||||
|
class AudioFileResource;
|
||||||
class Auth;
|
class Auth;
|
||||||
class ImageResource;
|
class ImageResource;
|
||||||
class LmsApplicationException;
|
class LmsApplicationException;
|
||||||
@@ -82,7 +83,8 @@ class LmsApplication : public Wt::WApplication
|
|||||||
|
|
||||||
// Session application data
|
// Session application data
|
||||||
std::shared_ptr<ImageResource> getImageResource() { return _imageResource; }
|
std::shared_ptr<ImageResource> getImageResource() { return _imageResource; }
|
||||||
std::shared_ptr<AudioResource> getAudioResource() { return _audioResource; }
|
std::shared_ptr<AudioTranscodeResource> getAudioTranscodeResource() { return _audioTranscodeResource; }
|
||||||
|
std::shared_ptr<AudioFileResource> getAudioFileResource() { return _audioFileResource; }
|
||||||
Database::Session& getDbSession() { return _dbSession;}
|
Database::Session& getDbSession() { return _dbSession;}
|
||||||
|
|
||||||
Wt::Dbo::ptr<Database::User> getUser();
|
Wt::Dbo::ptr<Database::User> getUser();
|
||||||
@@ -128,8 +130,9 @@ class LmsApplication : public Wt::WApplication
|
|||||||
Events _events;
|
Events _events;
|
||||||
std::optional<Database::IdType> _userId;
|
std::optional<Database::IdType> _userId;
|
||||||
std::optional<bool> _userAuthStrong;
|
std::optional<bool> _userAuthStrong;
|
||||||
|
std::shared_ptr<AudioTranscodeResource> _audioTranscodeResource;
|
||||||
|
std::shared_ptr<AudioFileResource> _audioFileResource;
|
||||||
std::shared_ptr<ImageResource> _imageResource;
|
std::shared_ptr<ImageResource> _imageResource;
|
||||||
std::shared_ptr<AudioResource> _audioResource;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+63
-58
@@ -19,15 +19,16 @@
|
|||||||
|
|
||||||
#include "MediaPlayer.hpp"
|
#include "MediaPlayer.hpp"
|
||||||
|
|
||||||
#include "av/AvInfo.hpp"
|
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
|
|
||||||
#include "database/Artist.hpp"
|
#include "database/Artist.hpp"
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
|
#include "database/User.hpp"
|
||||||
|
|
||||||
#include "resource/ImageResource.hpp"
|
#include "resource/ImageResource.hpp"
|
||||||
#include "resource/AudioResource.hpp"
|
#include "resource/AudioTranscodeResource.hpp"
|
||||||
|
#include "resource/AudioFileResource.hpp"
|
||||||
|
|
||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
@@ -57,68 +58,72 @@ MediaPlayer::loadTrack(Database::IdType trackId, bool play)
|
|||||||
LMS_LOG(UI, DEBUG) << "Playing track ID = " << trackId;
|
LMS_LOG(UI, DEBUG) << "Playing track ID = " << trackId;
|
||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||||
|
|
||||||
const auto track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
|
const auto track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
|
||||||
|
const std::string imgResourceMimeType {LmsApp->getImageResource()->getMimeType()};
|
||||||
|
|
||||||
try
|
std::string resource;
|
||||||
|
bool transcode;
|
||||||
|
if (LmsApp->getUser()->getAudioTranscodeEnable())
|
||||||
{
|
{
|
||||||
const Av::MediaFile mediaFile {track->getPath()};
|
resource = LmsApp->getAudioTranscodeResource()->getUrlForUser(trackId, LmsApp->getUser());
|
||||||
|
transcode = true;
|
||||||
const std::string resource {LmsApp->getAudioResource()->getUrl(trackId)};
|
|
||||||
const std::string imgResourceMimeType {LmsApp->getImageResource()->getMimeType()};
|
|
||||||
|
|
||||||
const auto artists {track->getArtists()};
|
|
||||||
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss
|
|
||||||
<< "var params = {"
|
|
||||||
<< " resource: \"" << resource << "\","
|
|
||||||
<< " duration: " << std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count() << ","
|
|
||||||
<< " title: \"" << StringUtils::jsEscape(track->getName()) << "\","
|
|
||||||
<< " artist: \"" << (!artists.empty() ? StringUtils::jsEscape(artists.front()->getName()) : "") << "\","
|
|
||||||
<< " release: \"" << (track->getRelease() ? StringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
|
|
||||||
<< " artwork: ["
|
|
||||||
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, 96) << "\", sizes: \"96x96\", type: \"" << imgResourceMimeType << "\" },"
|
|
||||||
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, 256) << "\", sizes: \"256x256\", type: \"" << imgResourceMimeType << "\" },"
|
|
||||||
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, 512) << "\", sizes: \"512x512\", type: \"" << imgResourceMimeType << "\" },"
|
|
||||||
<< " ]"
|
|
||||||
<< "};";
|
|
||||||
oss << "LMS.mediaplayer.loadTrack(params, " << (play ? "true" : "false") << ")"; // true to autoplay
|
|
||||||
|
|
||||||
LMS_LOG(UI, DEBUG) << "Running js = '" << oss.str() << "'";
|
|
||||||
|
|
||||||
_title->setTextFormat(Wt::TextFormat::Plain);
|
|
||||||
_title->setText(Wt::WString::fromUTF8(track->getName()));
|
|
||||||
|
|
||||||
if (!artists.empty())
|
|
||||||
{
|
|
||||||
_artist->setTextFormat(Wt::TextFormat::Plain);
|
|
||||||
_artist->setText(Wt::WString::fromUTF8(artists.front()->getName()));
|
|
||||||
_artist->setLink(LmsApp->createArtistLink(artists.front()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_artist->setText("");
|
|
||||||
_artist->setLink({});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (track->getRelease())
|
|
||||||
{
|
|
||||||
_release->setTextFormat(Wt::TextFormat::Plain);
|
|
||||||
_release->setText(Wt::WString::fromUTF8(track->getRelease()->getName()));
|
|
||||||
_release->setLink(LmsApp->createReleaseLink(track->getRelease()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_release->setText("");
|
|
||||||
_release->setLink({});
|
|
||||||
}
|
|
||||||
|
|
||||||
wApp->doJavaScript(oss.str());
|
|
||||||
}
|
}
|
||||||
catch (Av::MediaFileException& e)
|
else
|
||||||
{
|
{
|
||||||
LMS_LOG(UI, ERROR) << "MediaFileException: " << e.what();
|
resource = LmsApp->getAudioFileResource()->getUrl(trackId);
|
||||||
|
transcode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto artists {track->getArtists()};
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss
|
||||||
|
<< "var params = {"
|
||||||
|
<< " mode: " << (transcode ? "Mode.Transcode" : "Mode.File") << ","
|
||||||
|
<< " resource: \"" << resource << "\","
|
||||||
|
<< " duration: " << std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count() << ","
|
||||||
|
<< " title: \"" << StringUtils::jsEscape(track->getName()) << "\","
|
||||||
|
<< " artist: \"" << (!artists.empty() ? StringUtils::jsEscape(artists.front()->getName()) : "") << "\","
|
||||||
|
<< " release: \"" << (track->getRelease() ? StringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
|
||||||
|
<< " artwork: ["
|
||||||
|
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, 96) << "\", sizes: \"96x96\", type: \"" << imgResourceMimeType << "\" },"
|
||||||
|
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, 256) << "\", sizes: \"256x256\", type: \"" << imgResourceMimeType << "\" },"
|
||||||
|
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, 512) << "\", sizes: \"512x512\", type: \"" << imgResourceMimeType << "\" },"
|
||||||
|
<< " ]"
|
||||||
|
<< "};";
|
||||||
|
oss << "LMS.mediaplayer.loadTrack(params, " << (play ? "true" : "false") << ")"; // true to autoplay
|
||||||
|
|
||||||
|
LMS_LOG(UI, DEBUG) << "Running js = '" << oss.str() << "'";
|
||||||
|
|
||||||
|
_title->setTextFormat(Wt::TextFormat::Plain);
|
||||||
|
_title->setText(Wt::WString::fromUTF8(track->getName()));
|
||||||
|
|
||||||
|
if (!artists.empty())
|
||||||
|
{
|
||||||
|
_artist->setTextFormat(Wt::TextFormat::Plain);
|
||||||
|
_artist->setText(Wt::WString::fromUTF8(artists.front()->getName()));
|
||||||
|
_artist->setLink(LmsApp->createArtistLink(artists.front()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_artist->setText("");
|
||||||
|
_artist->setLink({});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track->getRelease())
|
||||||
|
{
|
||||||
|
_release->setTextFormat(Wt::TextFormat::Plain);
|
||||||
|
_release->setText(Wt::WString::fromUTF8(track->getRelease()->getName()));
|
||||||
|
_release->setLink(LmsApp->createReleaseLink(track->getRelease()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_release->setText("");
|
||||||
|
_release->setLink({});
|
||||||
|
}
|
||||||
|
|
||||||
|
wApp->doJavaScript(oss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AudioFileResource.hpp"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <Wt/Http/Response.h>
|
||||||
|
|
||||||
|
#include "av/AvInfo.hpp"
|
||||||
|
#include "database/Track.hpp"
|
||||||
|
#include "utils/Logger.hpp"
|
||||||
|
#include "utils/String.hpp"
|
||||||
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
|
namespace UserInterface {
|
||||||
|
|
||||||
|
#define LOG(level) LMS_LOG(UI, level) << "Audio file resource: "
|
||||||
|
|
||||||
|
AudioFileResource:: ~AudioFileResource()
|
||||||
|
{
|
||||||
|
beingDeleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
AudioFileResource::getUrl(Database::IdType trackId) const
|
||||||
|
{
|
||||||
|
return url()+ "&trackid=" + std::to_string(trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
std::optional<std::filesystem::path>
|
||||||
|
getTrackPathFromTrackId(Database::IdType trackId)
|
||||||
|
{
|
||||||
|
// DbSession are not thread safe
|
||||||
|
Wt::WApplication::UpdateLock lock {LmsApp};
|
||||||
|
|
||||||
|
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||||
|
|
||||||
|
const Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
|
||||||
|
if (!track)
|
||||||
|
{
|
||||||
|
LOG(ERROR) << "Missing track";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return track->getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
std::optional<std::filesystem::path>
|
||||||
|
getTrackPathFromURLArgs(const Wt::Http::Request& request)
|
||||||
|
{
|
||||||
|
const std::string* trackIdParameter {request.getParameter("trackid")};
|
||||||
|
if (!trackIdParameter)
|
||||||
|
{
|
||||||
|
LOG(ERROR) << "Missing trackid URL parameter!";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto trackId {StringUtils::readAs<Database::IdType>(*trackIdParameter)};
|
||||||
|
if (!trackId)
|
||||||
|
{
|
||||||
|
LOG(ERROR) << "Bad trackid URL parameter!";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getTrackPathFromTrackId(*trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
AudioFileResource::handleRequest(const Wt::Http::Request& request,
|
||||||
|
Wt::Http::Response& response)
|
||||||
|
{
|
||||||
|
if (!request.continuation())
|
||||||
|
{
|
||||||
|
LOG(DEBUG) << "Initial request";
|
||||||
|
|
||||||
|
auto trackPath {getTrackPathFromURLArgs(request)};
|
||||||
|
if (!trackPath)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ContinuationData continuationData;
|
||||||
|
continuationData.path = *trackPath;
|
||||||
|
continuationData.offset = 0;
|
||||||
|
continuationData.beyondLastByte = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
continuationData.fileSize = std::filesystem::file_size(*trackPath, ec);
|
||||||
|
|
||||||
|
if (ec)
|
||||||
|
{
|
||||||
|
LOG(ERROR) << "Cannot get file size for '" << *trackPath << "': " << ec.message();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(DEBUG) << "Initial request. File = '" << continuationData.path << "', size = " << continuationData.fileSize;
|
||||||
|
|
||||||
|
handleRequestPiecewise(request, response, continuationData);
|
||||||
|
|
||||||
|
const auto fileFormat {Av::guessMediaFileFormat(*trackPath)};
|
||||||
|
const std::string mimeType {fileFormat ? fileFormat->mimeType : "application/octet-stream"};
|
||||||
|
response.setMimeType(mimeType);
|
||||||
|
|
||||||
|
LOG(DEBUG) << "Mime type set to '" << mimeType << "'";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ContinuationData continuationData {Wt::cpp17::any_cast<ContinuationData>(request.continuation()->data())};
|
||||||
|
handleRequestPiecewise(request, response, continuationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
AudioFileResource::handleRequestPiecewise(const Wt::Http::Request& request,
|
||||||
|
Wt::Http::Response& response,
|
||||||
|
ContinuationData continuationData)
|
||||||
|
{
|
||||||
|
LOG(DEBUG) << "Handling request. File = '" << continuationData.path << "', size = " << continuationData.fileSize << ", offset = " << continuationData.offset << ", beyondLastByte = " << continuationData.beyondLastByte;
|
||||||
|
|
||||||
|
::uint64_t startByte {continuationData.offset};
|
||||||
|
std::ifstream ifs {continuationData.path.string().c_str(), std::ios::in | std::ios::binary};
|
||||||
|
|
||||||
|
if (startByte == 0)
|
||||||
|
{
|
||||||
|
if (!ifs)
|
||||||
|
{
|
||||||
|
response.setStatus(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.setStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wt::Http::Request::ByteRangeSpecifier ranges {request.getRanges(continuationData.fileSize)};
|
||||||
|
if (!ranges.isSatisfiable())
|
||||||
|
{
|
||||||
|
std::ostringstream contentRange;
|
||||||
|
contentRange << "bytes */" << continuationData.fileSize;
|
||||||
|
response.setStatus(416); // Requested range not satisfiable
|
||||||
|
response.addHeader("Content-Range", contentRange.str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ranges.size() == 1)
|
||||||
|
{
|
||||||
|
response.setStatus(206);
|
||||||
|
startByte = ranges[0].firstByte();
|
||||||
|
continuationData.beyondLastByte = ranges[0].lastByte() + 1;
|
||||||
|
|
||||||
|
std::ostringstream contentRange;
|
||||||
|
contentRange << "bytes " << startByte << "-"
|
||||||
|
<< continuationData.beyondLastByte - 1 << "/" << continuationData.fileSize;
|
||||||
|
|
||||||
|
response.addHeader("Content-Range", contentRange.str());
|
||||||
|
response.setContentLength(continuationData.beyondLastByte - startByte);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continuationData.beyondLastByte = continuationData.fileSize;
|
||||||
|
response.setContentLength(continuationData.beyondLastByte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ifs.seekg(static_cast<std::istream::pos_type>(startByte));
|
||||||
|
|
||||||
|
std::vector<char> buf;
|
||||||
|
buf.resize(_chunkSize);
|
||||||
|
|
||||||
|
::uint64_t restSize = continuationData.beyondLastByte - startByte;
|
||||||
|
::uint64_t pieceSize = buf.size() > restSize ? restSize : buf.size();
|
||||||
|
|
||||||
|
ifs.read(&buf[0], pieceSize);
|
||||||
|
const ::uint64_t actualPieceSize {static_cast<::uint64_t>(ifs.gcount())};
|
||||||
|
response.out().write(&buf[0], actualPieceSize);
|
||||||
|
|
||||||
|
LOG(DEBUG) << "Written " << actualPieceSize << " bytes!";
|
||||||
|
|
||||||
|
if (ifs.good() && actualPieceSize < restSize)
|
||||||
|
{
|
||||||
|
LOG(DEBUG) << "Still more to do";
|
||||||
|
|
||||||
|
auto* continuation {response.createContinuation()};
|
||||||
|
ContinuationData newContinuationData {continuationData};
|
||||||
|
newContinuationData.offset = startByte + actualPieceSize;
|
||||||
|
continuation->setData(newContinuationData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <Wt/WResource.h>
|
||||||
|
|
||||||
|
#include "database/Types.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace UserInterface {
|
||||||
|
|
||||||
|
class AudioFileResource : public Wt::WResource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~AudioFileResource();
|
||||||
|
|
||||||
|
std::string getUrl(Database::IdType trackId) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
static constexpr std::size_t _chunkSize {262144};
|
||||||
|
|
||||||
|
struct ContinuationData
|
||||||
|
{
|
||||||
|
std::filesystem::path path;
|
||||||
|
::uint64_t beyondLastByte;
|
||||||
|
::uint64_t fileSize;
|
||||||
|
::uint64_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
void handleRequest(const Wt::Http::Request& request,
|
||||||
|
Wt::Http::Response& response);
|
||||||
|
|
||||||
|
void handleRequestPiecewise(const Wt::Http::Request& request,
|
||||||
|
Wt::Http::Response& response,
|
||||||
|
ContinuationData continuationData);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "AudioResource.hpp"
|
|
||||||
|
|
||||||
#include <Wt/Http/Response.h>
|
|
||||||
|
|
||||||
#include "utils/Logger.hpp"
|
|
||||||
|
|
||||||
#include "database/Track.hpp"
|
|
||||||
#include "database/User.hpp"
|
|
||||||
|
|
||||||
#include "LmsApplication.hpp"
|
|
||||||
|
|
||||||
namespace UserInterface {
|
|
||||||
|
|
||||||
|
|
||||||
AudioResource:: ~AudioResource()
|
|
||||||
{
|
|
||||||
beingDeleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string
|
|
||||||
AudioResource::getUrl(Database::IdType trackId) const
|
|
||||||
{
|
|
||||||
return url()+ "&trackid=" + std::to_string(trackId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
AudioResource::handleRequest(const Wt::Http::Request& request,
|
|
||||||
Wt::Http::Response& response)
|
|
||||||
{
|
|
||||||
std::shared_ptr<Av::Transcoder> transcoder;
|
|
||||||
|
|
||||||
// First, see if this request is for a continuation
|
|
||||||
Wt::Http::ResponseContinuation *continuation = request.continuation();
|
|
||||||
if (continuation)
|
|
||||||
{
|
|
||||||
LMS_LOG(UI, DEBUG) << "Continuation! " << continuation ;
|
|
||||||
transcoder = Wt::cpp17::any_cast<std::shared_ptr<Av::Transcoder>>(continuation->data());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Database::IdType trackId;
|
|
||||||
Av::TranscodeParameters parameters {};
|
|
||||||
parameters.stripMetadata = true;
|
|
||||||
|
|
||||||
LMS_LOG(UI, DEBUG) << "First request: creating transcoder";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
auto trackIdStr = request.getParameter("trackid");
|
|
||||||
if (!trackIdStr)
|
|
||||||
{
|
|
||||||
LMS_LOG(UI, ERROR) << "Missing trackid transcode parameter!";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
trackId = std::stol(*request.getParameter("trackid"));
|
|
||||||
|
|
||||||
auto offsetStr = request.getParameter("offset");
|
|
||||||
parameters.offset = std::chrono::seconds(offsetStr ? std::stol(*offsetStr) : 0);
|
|
||||||
}
|
|
||||||
catch (std::exception &e)
|
|
||||||
{
|
|
||||||
LMS_LOG(UI, ERROR) << "Exception while handling URL parameters: " << e.what();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DbSession are not thread safe
|
|
||||||
{
|
|
||||||
Wt::WApplication::UpdateLock lock(LmsApp);
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
|
|
||||||
const Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
|
|
||||||
if (!track)
|
|
||||||
{
|
|
||||||
LMS_LOG(UI, ERROR) << "Missing track";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LmsApp->getUser()->getAudioTranscodeEnable())
|
|
||||||
{
|
|
||||||
parameters.bitrate = LmsApp->getUser()->getAudioTranscodeBitrate();
|
|
||||||
|
|
||||||
switch (LmsApp->getUser()->getAudioTranscodeFormat())
|
|
||||||
{
|
|
||||||
case Database::AudioFormat::MP3:
|
|
||||||
parameters.encoding = Av::Encoding::MP3;
|
|
||||||
break;
|
|
||||||
case Database::AudioFormat::OGG_OPUS:
|
|
||||||
parameters.encoding = Av::Encoding::OGG_OPUS;
|
|
||||||
break;
|
|
||||||
case Database::AudioFormat::MATROSKA_OPUS:
|
|
||||||
parameters.encoding = Av::Encoding::MATROSKA_OPUS;
|
|
||||||
break;
|
|
||||||
case Database::AudioFormat::OGG_VORBIS:
|
|
||||||
parameters.encoding = Av::Encoding::OGG_VORBIS;
|
|
||||||
break;
|
|
||||||
case Database::AudioFormat::WEBM_VORBIS:
|
|
||||||
parameters.encoding = Av::Encoding::WEBM_VORBIS;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
parameters.encoding = Av::Encoding::OGG_OPUS;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
parameters.bitrate = 0;
|
|
||||||
|
|
||||||
transcoder = std::make_shared<Av::Transcoder>(track->getPath(), parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!transcoder->start())
|
|
||||||
{
|
|
||||||
LMS_LOG(UI, ERROR) << "Cannot start transcoder";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LMS_LOG(UI, DEBUG) << "Transcoder started";
|
|
||||||
|
|
||||||
std::string mimeType {transcoder->getOutputMimeType()};
|
|
||||||
response.setMimeType(mimeType);
|
|
||||||
LMS_LOG(UI, DEBUG) << "Mime type set to '" << mimeType << "'";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!transcoder->isComplete())
|
|
||||||
{
|
|
||||||
std::vector<unsigned char> data;
|
|
||||||
data.reserve(_chunkSize);
|
|
||||||
|
|
||||||
transcoder->process(data, _chunkSize);
|
|
||||||
|
|
||||||
response.out().write(reinterpret_cast<char*>(&data[0]), data.size());
|
|
||||||
LMS_LOG(UI, DEBUG) << "Written " << data.size() << " bytes! complete = " << (transcoder->isComplete() ? "true" : "false");
|
|
||||||
|
|
||||||
if (!response.out())
|
|
||||||
{
|
|
||||||
LMS_LOG(UI, ERROR) << "Write failed!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!transcoder->isComplete() && response.out())
|
|
||||||
{
|
|
||||||
continuation = response.createContinuation();
|
|
||||||
continuation->setData(transcoder);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(UI, DEBUG) << "No more data!";
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace UserInterface
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AudioTranscodeResource.hpp"
|
||||||
|
|
||||||
|
#include <Wt/Http/Response.h>
|
||||||
|
|
||||||
|
#include "av/AvTranscoder.hpp"
|
||||||
|
#include "database/Track.hpp"
|
||||||
|
#include "database/User.hpp"
|
||||||
|
#include "utils/Logger.hpp"
|
||||||
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
|
#define LOG(level) LMS_LOG(UI, level) << "Audio transcode resource: "
|
||||||
|
|
||||||
|
namespace StringUtils
|
||||||
|
{
|
||||||
|
template <>
|
||||||
|
std::optional<Database::AudioFormat>
|
||||||
|
readAs(const std::string& str)
|
||||||
|
{
|
||||||
|
|
||||||
|
auto encodedFormat {readAs<int>(str)};
|
||||||
|
if (!encodedFormat)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
Database::AudioFormat format {static_cast<Database::AudioFormat>(*encodedFormat)};
|
||||||
|
|
||||||
|
// check
|
||||||
|
switch (static_cast<Database::AudioFormat>(*encodedFormat))
|
||||||
|
{
|
||||||
|
case Database::AudioFormat::MP3:
|
||||||
|
[[fallthrough]];
|
||||||
|
case Database::AudioFormat::OGG_OPUS:
|
||||||
|
[[fallthrough]];
|
||||||
|
case Database::AudioFormat::MATROSKA_OPUS:
|
||||||
|
[[fallthrough]];
|
||||||
|
case Database::AudioFormat::OGG_VORBIS:
|
||||||
|
[[fallthrough]];
|
||||||
|
case Database::AudioFormat::WEBM_VORBIS:
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(ERROR) << "Cannot determine audio format from value '" << str << "'";
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace UserInterface {
|
||||||
|
|
||||||
|
AudioTranscodeResource:: ~AudioTranscodeResource()
|
||||||
|
{
|
||||||
|
beingDeleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
std::optional<Av::Encoding>
|
||||||
|
AudioFormatToAvEncoding(Database::AudioFormat format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case Database::AudioFormat::MP3: return Av::Encoding::MP3;
|
||||||
|
case Database::AudioFormat::OGG_OPUS: return Av::Encoding::OGG_OPUS;
|
||||||
|
case Database::AudioFormat::MATROSKA_OPUS: return Av::Encoding::MATROSKA_OPUS;
|
||||||
|
case Database::AudioFormat::OGG_VORBIS: return Av::Encoding::OGG_VORBIS;
|
||||||
|
case Database::AudioFormat::WEBM_VORBIS: return Av::Encoding::WEBM_VORBIS;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(ERROR) << "Cannot convert from audio format to encoding";
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
AudioTranscodeResource::getUrlForUser(Database::IdType trackId, Database::User::pointer user) const
|
||||||
|
{
|
||||||
|
std::string computedUrl {url() + "&trackid=" + std::to_string(trackId)};
|
||||||
|
|
||||||
|
computedUrl += "&format=" + std::to_string(static_cast<std::size_t>(user->getAudioTranscodeFormat()));
|
||||||
|
computedUrl += "&bitrate=" + std::to_string(LmsApp->getUser()->getAudioTranscodeBitrate());
|
||||||
|
|
||||||
|
return computedUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::optional<T>
|
||||||
|
readParameterAs(const Wt::Http::Request& request, const std::string& parameterName)
|
||||||
|
{
|
||||||
|
auto paramStr {request.getParameter(parameterName)};
|
||||||
|
if (!paramStr)
|
||||||
|
{
|
||||||
|
LOG(ERROR) << "Missing parameter '" << parameterName << "'";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res {StringUtils::readAs<T>(*paramStr)};
|
||||||
|
if (!res)
|
||||||
|
LOG(ERROR) << "Cannot parse parameter '" << parameterName << "' from value '" << *paramStr << "'";
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
AudioTranscodeResource::handleRequest(const Wt::Http::Request& request,
|
||||||
|
Wt::Http::Response& response)
|
||||||
|
{
|
||||||
|
std::shared_ptr<Av::Transcoder> transcoder;
|
||||||
|
|
||||||
|
// First, see if this request is for a continuation
|
||||||
|
Wt::Http::ResponseContinuation *continuation = request.continuation();
|
||||||
|
if (continuation)
|
||||||
|
{
|
||||||
|
LOG(DEBUG) << "Continuation! " << continuation ;
|
||||||
|
transcoder = Wt::cpp17::any_cast<std::shared_ptr<Av::Transcoder>>(continuation->data());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG(DEBUG) << "First request: creating transcoder";
|
||||||
|
|
||||||
|
// mandatory parameters
|
||||||
|
auto trackId {readParameterAs<Database::IdType>(request, "trackid")};
|
||||||
|
auto format {readParameterAs<Database::AudioFormat>(request, "format")};
|
||||||
|
auto bitrate {readParameterAs<Database::Bitrate>(request, "bitrate")};
|
||||||
|
|
||||||
|
if (!trackId || !format || !bitrate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto encoding {AudioFormatToAvEncoding(*format)};
|
||||||
|
if (!encoding)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// optional parameter
|
||||||
|
auto offset {readParameterAs<std::size_t>(request, "offset")};
|
||||||
|
|
||||||
|
std::filesystem::path trackPath;
|
||||||
|
{
|
||||||
|
// DbSession are not thread safe
|
||||||
|
Wt::WApplication::UpdateLock lock(LmsApp);
|
||||||
|
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||||
|
|
||||||
|
const Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), *trackId)};
|
||||||
|
if (!track)
|
||||||
|
{
|
||||||
|
LOG(ERROR) << "Missing track";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
trackPath = track->getPath();
|
||||||
|
|
||||||
|
if (!LmsApp->getUser()->checkBitrate(*bitrate))
|
||||||
|
{
|
||||||
|
LOG(ERROR) << "Bitrate '" << *bitrate << "' is not allowed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Av::TranscodeParameters parameters {};
|
||||||
|
parameters.stripMetadata = true;
|
||||||
|
parameters.encoding = *encoding;
|
||||||
|
parameters.bitrate = *bitrate;
|
||||||
|
parameters.offset = std::chrono::seconds {offset ? *offset : 0};
|
||||||
|
|
||||||
|
transcoder = std::make_shared<Av::Transcoder>(trackPath, parameters);
|
||||||
|
if (!transcoder->start())
|
||||||
|
{
|
||||||
|
LOG(ERROR) << "Cannot start transcoder";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(DEBUG) << "Transcoder started";
|
||||||
|
|
||||||
|
std::string mimeType {transcoder->getOutputMimeType()};
|
||||||
|
response.setMimeType(mimeType);
|
||||||
|
LOG(DEBUG) << "Mime type set to '" << mimeType << "'";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transcoder->isComplete())
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> data;
|
||||||
|
data.reserve(_chunkSize);
|
||||||
|
|
||||||
|
transcoder->process(data, _chunkSize);
|
||||||
|
|
||||||
|
response.out().write(reinterpret_cast<char*>(&data[0]), data.size());
|
||||||
|
LOG(DEBUG) << "Written " << data.size() << " bytes! complete = " << (transcoder->isComplete() ? "true" : "false");
|
||||||
|
|
||||||
|
if (!response.out())
|
||||||
|
{
|
||||||
|
LOG(ERROR) << "Write failed!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transcoder->isComplete() && response.out())
|
||||||
|
{
|
||||||
|
continuation = response.createContinuation();
|
||||||
|
continuation->setData(transcoder);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LOG(DEBUG) << "No more data!";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|
||||||
+10
-6
@@ -19,28 +19,32 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Wt/Dbo/ptr.h>
|
||||||
#include <Wt/WResource.h>
|
#include <Wt/WResource.h>
|
||||||
|
|
||||||
#include "av/AvTranscoder.hpp"
|
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
|
|
||||||
|
namespace Database
|
||||||
|
{
|
||||||
|
class User;
|
||||||
|
}
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
class AudioResource : public Wt::WResource
|
class AudioTranscodeResource : public Wt::WResource
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
~AudioResource();
|
~AudioTranscodeResource();
|
||||||
|
|
||||||
std::string getUrl(Database::IdType trackId) const;
|
// Url depends on the user since settings are used in parameters
|
||||||
|
std::string getUrlForUser(Database::IdType trackId, Wt::Dbo::ptr<Database::User> user) const;
|
||||||
|
|
||||||
void handleRequest(const Wt::Http::Request& request,
|
void handleRequest(const Wt::Http::Request& request,
|
||||||
Wt::Http::Response& response);
|
Wt::Http::Response& response);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static const std::size_t _chunkSize = 65536*4;
|
static constexpr std::size_t _chunkSize {262144};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
@@ -31,6 +31,8 @@
|
|||||||
|
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
|
#define LOG(level) LMS_LOG(UI, level) << "Image resource: "
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
ImageResource::~ImageResource()
|
ImageResource::~ImageResource()
|
||||||
@@ -60,19 +62,30 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
|
|||||||
|
|
||||||
// Mandatory parameter size
|
// Mandatory parameter size
|
||||||
if (!sizeStr)
|
if (!sizeStr)
|
||||||
|
{
|
||||||
|
LOG(DEBUG) << "no size provided!";
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto size {StringUtils::readAs<std::size_t>(*sizeStr)};
|
const auto size {StringUtils::readAs<std::size_t>(*sizeStr)};
|
||||||
if (!size || *size > maxSize)
|
if (!size || *size > maxSize)
|
||||||
|
{
|
||||||
|
LOG(DEBUG) << "invalid size provided!";
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> cover;
|
std::vector<uint8_t> cover;
|
||||||
|
|
||||||
if (trackIdStr)
|
if (trackIdStr)
|
||||||
{
|
{
|
||||||
|
LOG(DEBUG) << "Requested cover for track " << *trackIdStr << ", size = " << *size;
|
||||||
|
|
||||||
const auto trackId {StringUtils::readAs<Database::IdType>(*trackIdStr)};
|
const auto trackId {StringUtils::readAs<Database::IdType>(*trackIdStr)};
|
||||||
if (!trackId)
|
if (!trackId)
|
||||||
|
{
|
||||||
|
LOG(DEBUG) << "track not found";
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// DbSession are not thread safe
|
// DbSession are not thread safe
|
||||||
{
|
{
|
||||||
@@ -82,6 +95,8 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
|
|||||||
}
|
}
|
||||||
else if (releaseIdStr)
|
else if (releaseIdStr)
|
||||||
{
|
{
|
||||||
|
LOG(DEBUG) << "Requested cover for release " << *releaseIdStr << ", size = " << *size;
|
||||||
|
|
||||||
const auto releaseId {StringUtils::readAs<Database::IdType>(*releaseIdStr)};
|
const auto releaseId {StringUtils::readAs<Database::IdType>(*releaseIdStr)};
|
||||||
if (!releaseId)
|
if (!releaseId)
|
||||||
return;
|
return;
|
||||||
@@ -93,7 +108,10 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
LOG(DEBUG) << "No track or release provided";
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
response.setMimeType(getMimeType());
|
response.setMimeType(getMimeType());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user