Isolate Audio file resource request handler to ease reuse. ref #36
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Emeric Poupon
|
|
||||||
*
|
*
|
||||||
* This file is part of LMS.
|
* This file is part of LMS.
|
||||||
*
|
*
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
namespace Av {
|
namespace Av {
|
||||||
|
|
||||||
#define LMS_LOG_TRANSCODE(sev) LMS_LOG(TRANSCODE, sev) << "[" << _id << "] - "
|
#define LOG(sev) LMS_LOG(TRANSCODE, sev) << "[" << _id << "] - "
|
||||||
|
|
||||||
static std::atomic<size_t> globalId {};
|
static std::atomic<size_t> globalId {};
|
||||||
static std::filesystem::path ffmpegPath;
|
static std::filesystem::path ffmpegPath;
|
||||||
@@ -59,7 +59,7 @@ Transcoder::start()
|
|||||||
else if (!std::filesystem::is_regular_file( _filePath) )
|
else if (!std::filesystem::is_regular_file( _filePath) )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
LMS_LOG_TRANSCODE(INFO) << "Transcoding file '" << _filePath.string() << "'";
|
LOG(INFO) << "Transcoding file '" << _filePath.string() << "'";
|
||||||
|
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
|
|
||||||
@@ -99,84 +99,57 @@ Transcoder::start()
|
|||||||
// Skip video flows (including covers)
|
// Skip video flows (including covers)
|
||||||
args.emplace_back("-vn");
|
args.emplace_back("-vn");
|
||||||
|
|
||||||
|
// Output bitrates
|
||||||
|
args.emplace_back("-b:a");
|
||||||
|
args.emplace_back(std::to_string(_parameters.bitrate));
|
||||||
|
|
||||||
// Codecs and formats
|
// Codecs and formats
|
||||||
if (_parameters.encoding)
|
switch (_parameters.encoding)
|
||||||
{
|
{
|
||||||
// Output bitrates
|
case Encoding::MP3:
|
||||||
args.emplace_back("-b:a");
|
args.emplace_back("-f");
|
||||||
args.emplace_back(std::to_string(_parameters.bitrate));
|
args.emplace_back("mp3");
|
||||||
|
break;
|
||||||
|
|
||||||
switch (*_parameters.encoding)
|
case Encoding::OGG_OPUS:
|
||||||
{
|
args.emplace_back("-acodec");
|
||||||
case Encoding::MP3:
|
args.emplace_back("libopus");
|
||||||
args.emplace_back("-f");
|
args.emplace_back("-f");
|
||||||
args.emplace_back("mp3");
|
args.emplace_back("ogg");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Encoding::OGG_OPUS:
|
case Encoding::MATROSKA_OPUS:
|
||||||
args.emplace_back("-acodec");
|
args.emplace_back("-acodec");
|
||||||
args.emplace_back("libopus");
|
args.emplace_back("libopus");
|
||||||
args.emplace_back("-f");
|
args.emplace_back("-f");
|
||||||
args.emplace_back("ogg");
|
args.emplace_back("matroska");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Encoding::MATROSKA_OPUS:
|
case Encoding::OGG_VORBIS:
|
||||||
args.emplace_back("-acodec");
|
args.emplace_back("-acodec");
|
||||||
args.emplace_back("libopus");
|
args.emplace_back("libvorbis");
|
||||||
args.emplace_back("-f");
|
args.emplace_back("-f");
|
||||||
args.emplace_back("matroska");
|
args.emplace_back("ogg");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Encoding::OGG_VORBIS:
|
case Encoding::WEBM_VORBIS:
|
||||||
args.emplace_back("-acodec");
|
args.emplace_back("-acodec");
|
||||||
args.emplace_back("libvorbis");
|
args.emplace_back("libvorbis");
|
||||||
args.emplace_back("-f");
|
args.emplace_back("-f");
|
||||||
args.emplace_back("ogg");
|
args.emplace_back("webm");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Encoding::WEBM_VORBIS:
|
default:
|
||||||
args.emplace_back("-acodec");
|
|
||||||
args.emplace_back("libvorbis");
|
|
||||||
args.emplace_back("-f");
|
|
||||||
args.emplace_back("webm");
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_outputMimeType = encodingToMimetype(*_parameters.encoding);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto mediaFileFormat {guessMediaFileFormat(_filePath)};
|
|
||||||
|
|
||||||
if (!mediaFileFormat)
|
|
||||||
{
|
|
||||||
LMS_LOG(AV, ERROR) << "Cannot guess media file format for '" << _filePath.string() << "'";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
args.emplace_back("-acodec");
|
|
||||||
args.emplace_back("copy");
|
|
||||||
args.emplace_back("-f");
|
|
||||||
args.emplace_back(mediaFileFormat->format);
|
|
||||||
|
|
||||||
// Workaround for ipod streams: make it compatible with non seekable output
|
|
||||||
if (mediaFileFormat->format == "ipod")
|
|
||||||
{
|
|
||||||
args.emplace_back("-movflags");
|
|
||||||
args.emplace_back("frag_keyframe");
|
|
||||||
}
|
|
||||||
|
|
||||||
_outputMimeType = mediaFileFormat->mimeType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_outputMimeType = encodingToMimetype(_parameters.encoding);
|
||||||
|
|
||||||
args.emplace_back("pipe:1");
|
args.emplace_back("pipe:1");
|
||||||
|
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Dumping args (" << args.size() << ")";
|
LOG(DEBUG) << "Dumping args (" << args.size() << ")";
|
||||||
for (const std::string& arg : args)
|
for (const std::string& arg : args)
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Arg = '" << arg << "'";
|
LOG(DEBUG) << "Arg = '" << arg << "'";
|
||||||
|
|
||||||
// make sure only one thread is executing this part of code
|
// make sure only one thread is executing this part of code
|
||||||
{
|
{
|
||||||
@@ -190,18 +163,18 @@ Transcoder::start()
|
|||||||
_child->open(ffmpegPath.string(), args);
|
_child->open(ffmpegPath.string(), args);
|
||||||
if (!_child->is_open())
|
if (!_child->is_open())
|
||||||
{
|
{
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Exec failed!";
|
LOG(DEBUG) << "Exec failed!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_child->out().eof())
|
if (_child->out().eof())
|
||||||
{
|
{
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Early end of file!";
|
LOG(DEBUG) << "Early end of file!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Stream opened!";
|
LOG(DEBUG) << "Stream opened!";
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -214,12 +187,12 @@ Transcoder::process(std::vector<unsigned char>& output, std::size_t maxSize)
|
|||||||
|
|
||||||
if (_child->out().fail())
|
if (_child->out().fail())
|
||||||
{
|
{
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Stdout FAILED 2";
|
LOG(DEBUG) << "Stdout FAILED 2";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_child->out().eof())
|
if (_child->out().eof())
|
||||||
{
|
{
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Stdout ENDED 2";
|
LOG(DEBUG) << "Stdout ENDED 2";
|
||||||
}
|
}
|
||||||
|
|
||||||
output.resize(maxSize);
|
output.resize(maxSize);
|
||||||
@@ -230,12 +203,12 @@ Transcoder::process(std::vector<unsigned char>& output, std::size_t maxSize)
|
|||||||
|
|
||||||
if (_child->out().fail())
|
if (_child->out().fail())
|
||||||
{
|
{
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Stdout FAILED";
|
LOG(DEBUG) << "Stdout FAILED";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_child->out().eof())
|
if (_child->out().eof())
|
||||||
{
|
{
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Stdout EOF!";
|
LOG(DEBUG) << "Stdout EOF!";
|
||||||
_child->clear();
|
_child->clear();
|
||||||
|
|
||||||
_isComplete = true;
|
_isComplete = true;
|
||||||
@@ -244,20 +217,20 @@ Transcoder::process(std::vector<unsigned char>& output, std::size_t maxSize)
|
|||||||
|
|
||||||
_total += output.size();
|
_total += output.size();
|
||||||
|
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "nb bytes = " << output.size() << ", total = " << _total;
|
LOG(DEBUG) << "nb bytes = " << output.size() << ", total = " << _total;
|
||||||
}
|
}
|
||||||
|
|
||||||
Transcoder::~Transcoder()
|
Transcoder::~Transcoder()
|
||||||
{
|
{
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << ", ~Transcoder called! Total produced bytes = " << _total;
|
LOG(DEBUG) << ", ~Transcoder called! Total produced bytes = " << _total;
|
||||||
|
|
||||||
if (_child)
|
if (_child)
|
||||||
{
|
{
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Child still here!";
|
LOG(DEBUG) << "Child still here!";
|
||||||
_child->rdbuf()->kill(SIGKILL);
|
_child->rdbuf()->kill(SIGKILL);
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Closing...";
|
LOG(DEBUG) << "Closing...";
|
||||||
_child->rdbuf()->close();
|
_child->rdbuf()->close();
|
||||||
LMS_LOG_TRANSCODE(DEBUG) << "Closing DONE";
|
LOG(DEBUG) << "Closing DONE";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace Av {
|
|||||||
|
|
||||||
struct TranscodeParameters
|
struct TranscodeParameters
|
||||||
{
|
{
|
||||||
std::optional<Encoding> encoding; // If not set, no transcoding is performed
|
Encoding encoding;
|
||||||
std::size_t bitrate {128000};
|
std::size_t bitrate {128000};
|
||||||
std::optional<std::size_t> stream; // Id of the stream to be transcoded (auto detect by default)
|
std::optional<std::size_t> stream; // Id of the stream to be transcoded (auto detect by default)
|
||||||
std::optional<std::chrono::seconds> offset;
|
std::optional<std::chrono::seconds> offset;
|
||||||
|
|||||||
@@ -1845,7 +1845,6 @@ handleStream(RequestContext& context, Wt::Http::ResponseContinuation* continuati
|
|||||||
{
|
{
|
||||||
MediaRetrievalResult res;
|
MediaRetrievalResult res;
|
||||||
|
|
||||||
// TODO store only weak ptrs and use a ring container to store shared_ptr?
|
|
||||||
std::shared_ptr<Av::Transcoder> transcoder;
|
std::shared_ptr<Av::Transcoder> transcoder;
|
||||||
|
|
||||||
if (!continuation)
|
if (!continuation)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
add_library(lmsutils SHARED
|
add_library(lmsutils SHARED
|
||||||
impl/Config.cpp
|
impl/Config.cpp
|
||||||
|
impl/FileResourceHandler.cpp
|
||||||
impl/Logger.cpp
|
impl/Logger.cpp
|
||||||
impl/NetAddress.cpp
|
impl/NetAddress.cpp
|
||||||
impl/Path.cpp
|
impl/Path.cpp
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* 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 "utils/FileResourceHandler.hpp"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "utils/Logger.hpp"
|
||||||
|
|
||||||
|
namespace FileResourceHandler
|
||||||
|
{
|
||||||
|
|
||||||
|
static constexpr std::size_t _chunkSize {262144};
|
||||||
|
|
||||||
|
static
|
||||||
|
std::optional<ContinuationData>
|
||||||
|
handleRequestPiecewise(const Wt::Http::Request& request,
|
||||||
|
Wt::Http::Response& response,
|
||||||
|
ContinuationData continuationData)
|
||||||
|
{
|
||||||
|
::uint64_t startByte {continuationData.offset};
|
||||||
|
std::ifstream ifs {continuationData.path.string().c_str(), std::ios::in | std::ios::binary};
|
||||||
|
|
||||||
|
LMS_LOG(UTILS, DEBUG) << "startByte = " << startByte;
|
||||||
|
|
||||||
|
if (startByte == 0)
|
||||||
|
{
|
||||||
|
if (!ifs)
|
||||||
|
{
|
||||||
|
LMS_LOG(UTILS, ERROR) << "Cannot open file stream for '" << continuationData.path.string() << "'";
|
||||||
|
response.setStatus(404);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.setStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
ifs.seekg(0, std::ios::end);
|
||||||
|
const ::uint64_t fileSize {static_cast<::uint64_t>(ifs.tellg())};
|
||||||
|
ifs.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
LMS_LOG(UTILS, DEBUG) << "fileSize = " << fileSize;
|
||||||
|
|
||||||
|
const Wt::Http::Request::ByteRangeSpecifier ranges {request.getRanges(fileSize)};
|
||||||
|
if (!ranges.isSatisfiable())
|
||||||
|
{
|
||||||
|
std::ostringstream contentRange;
|
||||||
|
contentRange << "bytes */" << fileSize;
|
||||||
|
response.setStatus(416); // Requested range not satisfiable
|
||||||
|
response.addHeader("Content-Range", contentRange.str());
|
||||||
|
|
||||||
|
LMS_LOG(UTILS, DEBUG) << "Range not satisfiable";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ranges.size() == 1)
|
||||||
|
{
|
||||||
|
LMS_LOG(UTILS, DEBUG) << "Range requested = " << ranges[0].firstByte() << "/" << ranges[0].lastByte();
|
||||||
|
|
||||||
|
response.setStatus(206);
|
||||||
|
startByte = ranges[0].firstByte();
|
||||||
|
continuationData.beyondLastByte = ranges[0].lastByte() + 1;
|
||||||
|
|
||||||
|
std::ostringstream contentRange;
|
||||||
|
contentRange << "bytes " << startByte << "-"
|
||||||
|
<< continuationData.beyondLastByte - 1 << "/" << fileSize;
|
||||||
|
|
||||||
|
response.addHeader("Content-Range", contentRange.str());
|
||||||
|
response.setContentLength(continuationData.beyondLastByte - startByte);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LMS_LOG(UTILS, DEBUG) << "No range requested";
|
||||||
|
|
||||||
|
continuationData.beyondLastByte = 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);
|
||||||
|
|
||||||
|
LMS_LOG(UTILS, DEBUG) << "Written " << actualPieceSize << " bytes";
|
||||||
|
|
||||||
|
LMS_LOG(UTILS, DEBUG) << "Progress: " << actualPieceSize << "/" << restSize;
|
||||||
|
if (ifs.good() && actualPieceSize < restSize)
|
||||||
|
{
|
||||||
|
ContinuationData newContinuationData {continuationData};
|
||||||
|
newContinuationData.offset = startByte + actualPieceSize;
|
||||||
|
|
||||||
|
LMS_LOG(UTILS, DEBUG) << "Job not complete! Next chunk offset = " << newContinuationData.offset;
|
||||||
|
|
||||||
|
return newContinuationData;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LMS_LOG(UTILS, DEBUG) << "Job complete!";
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<ContinuationData>
|
||||||
|
handleInitialRequest(const Wt::Http::Request& request,
|
||||||
|
Wt::Http::Response& response,
|
||||||
|
const std::filesystem::path& path)
|
||||||
|
{
|
||||||
|
ContinuationData continuationData;
|
||||||
|
continuationData.path = path;
|
||||||
|
continuationData.offset = 0;
|
||||||
|
continuationData.beyondLastByte = 0;
|
||||||
|
|
||||||
|
LMS_LOG(UTILS, DEBUG) << "Initial request for file '" << path << "'";
|
||||||
|
|
||||||
|
return handleRequestPiecewise(request, response, continuationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ContinuationData>
|
||||||
|
handleContinuationRequest(const Wt::Http::Request& request,
|
||||||
|
Wt::Http::Response& response,
|
||||||
|
const ContinuationData& continuationData)
|
||||||
|
{
|
||||||
|
LMS_LOG(UTILS, DEBUG) << "Continuation request for file '" << continuationData.path << "', offset = " << continuationData.offset;
|
||||||
|
return handleRequestPiecewise(request, response, continuationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // ns FileResourceHandler
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@ const char* getModuleName(Module mod)
|
|||||||
case Module::RECOMMENDATION: return "RECOMMENDATION";
|
case Module::RECOMMENDATION: return "RECOMMENDATION";
|
||||||
case Module::TRANSCODE: return "TRANSCODE";
|
case Module::TRANSCODE: return "TRANSCODE";
|
||||||
case Module::UI: return "UI";
|
case Module::UI: return "UI";
|
||||||
|
case Module::UTILS: return "UTILS";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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/Http/Request.h>
|
||||||
|
#include <Wt/Http/Response.h>
|
||||||
|
|
||||||
|
|
||||||
|
// Helper used to deliver file contents from a WResource
|
||||||
|
namespace FileResourceHandler
|
||||||
|
{
|
||||||
|
struct ContinuationData
|
||||||
|
{
|
||||||
|
std::filesystem::path path;
|
||||||
|
::uint64_t beyondLastByte;
|
||||||
|
::uint64_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<ContinuationData> handleInitialRequest(const Wt::Http::Request& request, Wt::Http::Response& response, const std::filesystem::path& path);
|
||||||
|
std::optional<ContinuationData> handleContinuationRequest(const Wt::Http::Request& request, Wt::Http::Response& response, const ContinuationData& continuationData);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -49,6 +49,7 @@ enum class Module
|
|||||||
RECOMMENDATION,
|
RECOMMENDATION,
|
||||||
TRANSCODE,
|
TRANSCODE,
|
||||||
UI,
|
UI,
|
||||||
|
UTILS,
|
||||||
};
|
};
|
||||||
|
|
||||||
const char* getModuleName(Module mod);
|
const char* getModuleName(Module mod);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include "av/AvInfo.hpp"
|
#include "av/AvInfo.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
|
#include "utils/FileResourceHandler.hpp"
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
@@ -87,126 +88,28 @@ void
|
|||||||
AudioFileResource::handleRequest(const Wt::Http::Request& request,
|
AudioFileResource::handleRequest(const Wt::Http::Request& request,
|
||||||
Wt::Http::Response& response)
|
Wt::Http::Response& response)
|
||||||
{
|
{
|
||||||
|
std::optional<FileResourceHandler::ContinuationData> continuationData;
|
||||||
if (!request.continuation())
|
if (!request.continuation())
|
||||||
{
|
{
|
||||||
LOG(DEBUG) << "Initial request";
|
|
||||||
|
|
||||||
auto trackPath {getTrackPathFromURLArgs(request)};
|
auto trackPath {getTrackPathFromURLArgs(request)};
|
||||||
if (!trackPath)
|
if (!trackPath)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ContinuationData continuationData;
|
continuationData = FileResourceHandler::handleInitialRequest(request, response, *trackPath);
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
ContinuationData continuationData {Wt::cpp17::any_cast<ContinuationData>(request.continuation()->data())};
|
auto currentContinuationData {Wt::cpp17::any_cast<FileResourceHandler::ContinuationData>(request.continuation()->data())};
|
||||||
handleRequestPiecewise(request, response, continuationData);
|
continuationData = FileResourceHandler::handleContinuationRequest(request, response, currentContinuationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
if (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()};
|
auto* continuation {response.createContinuation()};
|
||||||
ContinuationData newContinuationData {continuationData};
|
continuation->setData(*continuationData);
|
||||||
newContinuationData.offset = startByte + actualPieceSize;
|
|
||||||
continuation->setData(newContinuationData);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -38,20 +38,8 @@ class AudioFileResource : public Wt::WResource
|
|||||||
|
|
||||||
static constexpr std::size_t _chunkSize {262144};
|
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,
|
void handleRequest(const Wt::Http::Request& request,
|
||||||
Wt::Http::Response& response);
|
Wt::Http::Response& response) override;
|
||||||
|
|
||||||
void handleRequestPiecewise(const Wt::Http::Request& request,
|
|
||||||
Wt::Http::Response& response,
|
|
||||||
ContinuationData continuationData);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user