Generic HTTP handler

# Conflicts:
#	src/output/output_mp4.cpp
#	src/output/output_mp4.h
This commit is contained in:
Thulinma 2021-04-22 16:35:11 +02:00
parent 6f6827607d
commit 9953cd6ee2
10 changed files with 129 additions and 191 deletions

View file

@ -274,7 +274,7 @@ void HTTP::Parser::SendResponse(std::string code, std::string message, Socket::C
/// a zero-content-length HTTP/1.0 response. \param code The HTTP response code. Usually you want /// a zero-content-length HTTP/1.0 response. \param code The HTTP response code. Usually you want
/// 200. \param message The HTTP response message. Usually you want "OK". \param request The HTTP /// 200. \param message The HTTP response message. Usually you want "OK". \param request The HTTP
/// request to respond to. \param conn The connection to send over. /// request to respond to. \param conn The connection to send over.
void HTTP::Parser::StartResponse(std::string code, std::string message, HTTP::Parser &request, void HTTP::Parser::StartResponse(std::string code, std::string message, const HTTP::Parser &request,
Socket::Connection &conn, bool bufferAllChunks){ Socket::Connection &conn, bool bufferAllChunks){
std::string prot = request.protocol; std::string prot = request.protocol;
sendingChunks = sendingChunks =

View file

@ -42,7 +42,7 @@ namespace HTTP{
void sendRequest(Socket::Connection &conn, const void *body = 0, const size_t bodyLen = 0, void sendRequest(Socket::Connection &conn, const void *body = 0, const size_t bodyLen = 0,
bool allAtOnce = false); bool allAtOnce = false);
void SendResponse(std::string code, std::string message, Socket::Connection &conn); void SendResponse(std::string code, std::string message, Socket::Connection &conn);
void StartResponse(std::string code, std::string message, Parser &request, void StartResponse(std::string code, std::string message, const Parser &request,
Socket::Connection &conn, bool bufferAllChunks = false); Socket::Connection &conn, bool bufferAllChunks = false);
void StartResponse(Parser &request, Socket::Connection &conn, bool bufferAllChunks = false); void StartResponse(Parser &request, Socket::Connection &conn, bool bufferAllChunks = false);
void Chunkify(const std::string &bodypart, Socket::Connection &conn); void Chunkify(const std::string &bodypart, Socket::Connection &conn);

View file

@ -24,8 +24,6 @@ namespace Mist{
cfg->addOption("target", opt); cfg->addOption("target", opt);
} }
bool OutAAC::isRecording(){return config->getString("target").size();}
void OutAAC::initialSeek(){ void OutAAC::initialSeek(){
if (!meta){return;} if (!meta){return;}
maxSkipAhead = 30000; maxSkipAhead = 30000;
@ -42,40 +40,20 @@ namespace Mist{
void OutAAC::sendNext(){ void OutAAC::sendNext(){
char *dataPointer = 0; char *dataPointer = 0;
size_t len = 0; size_t len = 0;
thisPacket.getString("data", dataPointer, len); thisPacket.getString("data", dataPointer, len);
std::string head = TS::getAudioHeader(len, M.getInit(thisIdx)); std::string head = TS::getAudioHeader(len, M.getInit(thisIdx));
myConn.SendNow(head); myConn.SendNow(head);
myConn.SendNow(dataPointer, len); myConn.SendNow(dataPointer, len);
} }
void OutAAC::sendHeader(){ void OutAAC::respondHTTP(const HTTP::Parser & req, bool headersOnly){
if (!isRecording()){ HTTPOutput::respondHTTP(req, headersOnly);
H.Clean(); H.protocol = "HTTP/1.0";
H.SetHeader("Content-Type", "audio/aac"); H.SendResponse("200", "OK", myConn);
H.SetHeader("Accept-Ranges", "none"); if (!headersOnly){
H.protocol = "HTTP/1.0"; parseData = true;
H.setCORSHeaders(); wantRequest = false;
H.SendResponse("200", "OK", myConn);
} }
sentHeader = true;
}
void OutAAC::onHTTP(){
std::string method = H.method;
if (method == "OPTIONS" || method == "HEAD"){
H.Clean();
H.SetHeader("Content-Type", "audio/aac");
H.SetHeader("Accept-Ranges", "none");
H.protocol = "HTTP/1.0";
H.setCORSHeaders();
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
}
parseData = true;
wantRequest = false;
} }
}// namespace Mist }// namespace Mist

View file

@ -5,13 +5,11 @@ namespace Mist{
public: public:
OutAAC(Socket::Connection &conn); OutAAC(Socket::Connection &conn);
static void init(Util::Config *cfg); static void init(Util::Config *cfg);
void onHTTP(); void respondHTTP(const HTTP::Parser & req, bool headersOnly);
void sendNext(); void sendNext();
void sendHeader();
void initialSeek(); void initialSeek();
private: private:
bool isRecording();
bool isFileTarget(){return isRecording();} bool isFileTarget(){return isRecording();}
}; };
}// namespace Mist }// namespace Mist

View file

@ -391,17 +391,15 @@ namespace Mist{
// End of file. This probably won't work right, but who cares, it's the end of the file. // End of file. This probably won't work right, but who cares, it's the end of the file.
} }
void OutEBML::onHTTP(){ void OutEBML::respondHTTP(const HTTP::Parser & req, bool headersOnly){
std::string method = H.method; //Set global defaults, first
if (method == "OPTIONS" || method == "HEAD"){ HTTPOutput::respondHTTP(req, headersOnly);
H.Clean();
H.setCORSHeaders(); //If non-live, we accept range requests
H.SetHeader("Content-Type", "video/MP4"); if (!M.getLive()){H.SetHeader("Accept-Ranges", "bytes, parsec");}
H.SetHeader("Accept-Ranges", "bytes, parsec");
H.SendResponse("200", "OK", myConn); //We change the header's document type based on file extension
return; if (req.url.find(".webm") != std::string::npos){
}
if (H.url.find(".webm") != std::string::npos){
doctype = "webm"; doctype = "webm";
}else{ }else{
doctype = "matroska"; doctype = "matroska";
@ -417,72 +415,46 @@ namespace Mist{
size_t byteEnd = totalSize - 1; size_t byteEnd = totalSize - 1;
size_t byteStart = 0; size_t byteStart = 0;
if (!M.getLive() && req.GetHeader("Range") != ""){
/*LTS-START*/ //Range request
// allow setting of max lead time through buffer variable. if (parseRange(req.GetHeader("Range"), byteStart, byteEnd)){
// max lead time is set in MS, but the variable is in integer seconds for simplicity. if (!req.GetVar("buffer").size()){
if (H.GetVar("buffer") != ""){maxSkipAhead = JSON::Value(H.GetVar("buffer")).asInt() * 1000;} size_t idx = getMainSelectedTrack();
// allow setting of play back rate through buffer variable. maxSkipAhead = (M.getLastms(idx) - M.getFirstms(idx)) / 20 + 7500;
// play back rate is set in MS per second, but the variable is a simple multiplier.
if (H.GetVar("rate") != ""){
long long int multiplier = JSON::Value(H.GetVar("rate")).asInt();
if (multiplier){
realTime = 1000 / multiplier;
}else{
realTime = 0;
}
}
if (H.GetHeader("X-Mist-Rate") != ""){
long long int multiplier = JSON::Value(H.GetHeader("X-Mist-Rate")).asInt();
if (multiplier){
realTime = 1000 / multiplier;
}else{
realTime = 0;
}
}
/*LTS-END*/
char rangeType = ' ';
if (M.getVod()){
if (H.GetHeader("Range") != ""){
if (parseRange(byteStart, byteEnd)){
if (H.GetVar("buffer") == ""){
size_t idx = getMainSelectedTrack();
maxSkipAhead = (M.getLastms(idx) - M.getFirstms(idx)) / 20 + 7500;
}
} }
rangeType = H.GetHeader("Range")[0];
} }
} //Failed range request
H.Clean(); // make sure no parts of old requests are left in any buffers
H.setCORSHeaders();
H.SetHeader("Content-Type", "video/webm");
if (M.getVod()){H.SetHeader("Accept-Ranges", "bytes, parsec");}
if (rangeType != ' '){
if (!byteEnd){ if (!byteEnd){
if (rangeType == 'p'){ if (req.GetHeader("Range")[0] == 'p'){
H.SetBody("Starsystem not in communications range"); if (!headersOnly){H.SetBody("Starsystem not in communications range");}
H.SendResponse("416", "Starsystem not in communications range", myConn); H.SendResponse("416", "Starsystem not in communications range", myConn);
return; return;
} }
H.SetBody("Requested Range Not Satisfiable"); if (!headersOnly){H.SetBody("Requested Range Not Satisfiable");}
H.SendResponse("416", "Requested Range Not Satisfiable", myConn); H.SendResponse("416", "Requested Range Not Satisfiable", myConn);
return; return;
} }
//Successful range request
std::stringstream rangeReply; std::stringstream rangeReply;
rangeReply << "bytes " << byteStart << "-" << byteEnd << "/" << totalSize; rangeReply << "bytes " << byteStart << "-" << byteEnd << "/" << totalSize;
H.SetHeader("Content-Length", byteEnd - byteStart + 1); H.SetHeader("Content-Length", byteEnd - byteStart + 1);
H.SetHeader("Content-Range", rangeReply.str()); H.SetHeader("Content-Range", rangeReply.str());
/// \todo Switch to chunked? /// \todo Switch to chunked?
H.SendResponse("206", "Partial content", myConn); H.SendResponse("206", "Partial content", myConn);
byteSeek(byteStart); if (!headersOnly){
byteSeek(byteStart);
}
}else{ }else{
if (M.getVod()){H.SetHeader("Content-Length", byteEnd - byteStart + 1);} //Non-range request
if (!M.getLive()){H.SetHeader("Content-Length", byteEnd - byteStart + 1);}
/// \todo Switch to chunked? /// \todo Switch to chunked?
H.SendResponse("200", "OK", myConn); H.SendResponse("200", "OK", myConn);
} }
parseData = true; //Start outputting data
wantRequest = false; if (!headersOnly){
parseData = true;
wantRequest = false;
}
} }
void OutEBML::calcVodSizes(){ void OutEBML::calcVodSizes(){

View file

@ -6,7 +6,7 @@ namespace Mist{
public: public:
OutEBML(Socket::Connection &conn); OutEBML(Socket::Connection &conn);
static void init(Util::Config *cfg); static void init(Util::Config *cfg);
void onHTTP(); void respondHTTP(const HTTP::Parser & req, bool headersOnly);
void sendNext(); void sendNext();
void sendHeader(); void sendHeader();
size_t clusterSize(uint64_t start, uint64_t end); size_t clusterSize(uint64_t start, uint64_t end);

View file

@ -1,5 +1,6 @@
#include "output_http.h" #include "output_http.h"
#include <mist/checksum.h> #include <mist/checksum.h>
#include <mist/encode.h>
#include <mist/langcodes.h> #include <mist/langcodes.h>
#include <mist/stream.h> #include <mist/stream.h>
#include <mist/util.h> #include <mist/util.h>
@ -221,8 +222,6 @@ namespace Mist{
myConn.close(); myConn.close();
return; return;
} }
std::string connHeader = H.GetHeader("Connection");
Util::stringToLower(connHeader);
if (handler != capa["name"].asStringRef() || H.GetVar("stream") != streamName){ if (handler != capa["name"].asStringRef() || H.GetVar("stream") != streamName){
MEDIUM_MSG("Switching from %s (%s) to %s (%s)", capa["name"].asStringRef().c_str(), MEDIUM_MSG("Switching from %s (%s) to %s (%s)", capa["name"].asStringRef().c_str(),
streamName.c_str(), handler.c_str(), H.GetVar("stream").c_str()); streamName.c_str(), handler.c_str(), H.GetVar("stream").c_str());
@ -263,8 +262,12 @@ namespace Mist{
if (H.GetVar("stop") != ""){targetParams["stop"] = H.GetVar("stop");} if (H.GetVar("stop") != ""){targetParams["stop"] = H.GetVar("stop");}
if (H.GetVar("startunix") != ""){targetParams["startunix"] = H.GetVar("startunix");} if (H.GetVar("startunix") != ""){targetParams["startunix"] = H.GetVar("startunix");}
if (H.GetVar("stopunix") != ""){targetParams["stopunix"] = H.GetVar("stopunix");} if (H.GetVar("stopunix") != ""){targetParams["stopunix"] = H.GetVar("stopunix");}
if (H.GetVar("buffer") != ""){targetParams["buffer"] = H.GetVar("buffer");} // allow setting of max lead time through buffer variable.
// allow setting of play back rate through buffer variable. // max lead time is set in MS, but the variable is in integer seconds for simplicity.
if (H.GetVar("buffer") != ""){
maxSkipAhead = JSON::Value(H.GetVar("buffer")).asInt() * 1000;
}
// allow setting of play back rate through rate variable or custom HTTP header.
// play back rate is set in MS per second, but the variable is a simple multiplier. // play back rate is set in MS per second, but the variable is a simple multiplier.
if (H.GetVar("rate") != ""){ if (H.GetVar("rate") != ""){
long long int multiplier = JSON::Value(H.GetVar("rate")).asInt(); long long int multiplier = JSON::Value(H.GetVar("rate")).asInt();
@ -301,18 +304,58 @@ namespace Mist{
} }
responded = false; responded = false;
preHTTP(); preHTTP();
if (!myConn){return;}
onHTTP(); onHTTP();
idleLast = Util::bootMS(); idleLast = Util::bootMS();
if (!H.bufferChunks){H.Clean();} if (!H.bufferChunks){H.Clean();}
} }
} }
/// Default HTTP handler.
/// Only takes care of OPTIONS and HEAD, saving the original request, and calling respondHTTP
void HTTPOutput::onHTTP(){
const HTTP::Parser reqH = H;
bool headersOnly = (reqH.method == "OPTIONS" || reqH.method == "HEAD");
H.Clean();
respondHTTP(reqH, headersOnly);
}
/// Default implementation of preHTTP simply calls initialize and selectDefaultTracks. /// Default implementation of preHTTP simply calls initialize and selectDefaultTracks.
void HTTPOutput::preHTTP(){ void HTTPOutput::preHTTP(){
initialize(); initialize();
selectDefaultTracks(); selectDefaultTracks();
} }
/// Sets common HTTP headers. Virtual, so child classes can/will implement further behaviour if needed.
/// Child classes are suggested to call the parent implementation and then add their own logic afterwards.
void HTTPOutput::respondHTTP(const HTTP::Parser & req, bool headersOnly){
//We generally want the CORS headers to be set for all responses
H.setCORSHeaders();
//Set attachment header to force download, if applicable
if (req.GetVar("dl").size()){
//If we want to download, and the string contains a dot, use as-is.
std::string dl = req.GetVar("dl");
//Without a dot, we use the last segment of the bare URL
if (dl.find('.') == std::string::npos){
dl = HTTP::URL(req.url).getFilePath();
size_t lSlash = dl.rfind('/');
if (lSlash != std::string::npos){dl = dl.substr(lSlash+1);}
}
H.SetHeader("Content-Disposition", "attachment; filename=" + Encodings::URL::encode(dl) + ";");
//Force max download speed when downloading
realTime = 0;
}
//If there is a method defined, and the first method has a type with two slashes in it,
//assume the last two sections are the content type.
if (capa.isMember("methods") && capa["methods"].isArray() && capa["methods"].size() && capa["methods"][0u].isMember("type")){
const std::string & cType = capa["methods"][0u]["type"].asStringRef();
size_t fSlash = cType.find('/');
if (fSlash != std::string::npos && cType.rfind('/') != fSlash){
H.SetHeader("Content-Type", cType.substr(fSlash+1));
}
}
}
static inline void builPipedPart(JSON::Value &p, char *argarr[], int &argnum, JSON::Value &argset){ static inline void builPipedPart(JSON::Value &p, char *argarr[], int &argnum, JSON::Value &argset){
jsonForEach(argset, it){ jsonForEach(argset, it){
if (it->isMember("option") && p.isMember(it.key())){ if (it->isMember("option") && p.isMember(it.key())){
@ -488,8 +531,7 @@ namespace Mist{
/// Parses a "Range: " header, setting byteStart and byteEnd. /// Parses a "Range: " header, setting byteStart and byteEnd.
/// Assumes byteStart and byteEnd are initialized to their minimum respectively maximum values /// Assumes byteStart and byteEnd are initialized to their minimum respectively maximum values
/// when the function is called. On error, byteEnd is set to zero and the function return false. /// when the function is called. On error, byteEnd is set to zero and the function return false.
bool HTTPOutput::parseRange(uint64_t &byteStart, uint64_t &byteEnd){ bool HTTPOutput::parseRange(std::string header, uint64_t &byteStart, uint64_t &byteEnd){
std::string header = H.GetHeader("Range");
if (header.size() < 6 || header.substr(0, 6) != "bytes="){ if (header.size() < 6 || header.substr(0, 6) != "bytes="){
byteEnd = 0; byteEnd = 0;
WARN_MSG("Invalid range header: %s", header.c_str()); WARN_MSG("Invalid range header: %s", header.c_str());

View file

@ -12,7 +12,8 @@ namespace Mist{
virtual ~HTTPOutput(); virtual ~HTTPOutput();
static void init(Util::Config *cfg); static void init(Util::Config *cfg);
virtual void onFail(const std::string &msg, bool critical = false); virtual void onFail(const std::string &msg, bool critical = false);
virtual void onHTTP(){}; virtual void onHTTP();
virtual void respondHTTP(const HTTP::Parser & req, bool headersOnly);
virtual void onIdle(){}; virtual void onIdle(){};
virtual void onWebsocketFrame(){}; virtual void onWebsocketFrame(){};
virtual void onWebsocketConnect(){}; virtual void onWebsocketConnect(){};
@ -23,7 +24,7 @@ namespace Mist{
virtual bool doesWebsockets(){return false;} virtual bool doesWebsockets(){return false;}
void reConnector(std::string &connector); void reConnector(std::string &connector);
std::string getHandler(); std::string getHandler();
bool parseRange(uint64_t &byteStart, uint64_t &byteEnd); bool parseRange(std::string header, uint64_t &byteStart, uint64_t &byteEnd);
protected: protected:
bool firstRun; bool firstRun;

View file

@ -1060,60 +1060,17 @@ namespace Mist{
H.Chunkify(mdatHeader, 8, myConn); H.Chunkify(mdatHeader, 8, myConn);
} }
void OutMP4::onHTTP(){ void OutMP4::respondHTTP(const HTTP::Parser & req, bool headersOnly){
//Set global defaults, first
HTTPOutput::respondHTTP(req, headersOnly);
if (webSock) { H.SetHeader("Content-Type", "video/MP4");
return; if (!M.getLive()){H.SetHeader("Accept-Ranges", "bytes, parsec");}
}
std::string dl;
if (H.GetVar("dl").size()){
dl = H.GetVar("dl");
if (dl.find('.') == std::string::npos){dl = streamName + ".mp4";}
}
if (H.method == "OPTIONS" || H.method == "HEAD"){
H.Clean();
H.setCORSHeaders();
if (dl.size()){
H.SetHeader("Content-Disposition", "attachment; filename=" + Encodings::URL::encode(dl) + ";");
}
H.SetHeader("Content-Type", "video/MP4");
H.SetHeader("Accept-Ranges", "bytes, parsec");
H.StartResponse(H, myConn);
return;
}
chromeWorkaround = (H.GetHeader("User-Agent").find("Chrome") != std::string::npos && chromeWorkaround = (req.GetHeader("User-Agent").find("Chrome") != std::string::npos &&
H.GetHeader("User-Agent").find("Edge") == std::string::npos && req.GetHeader("User-Agent").find("Edge") == std::string::npos &&
H.GetHeader("User-Agent").find("OPR/") == std::string::npos); req.GetHeader("User-Agent").find("OPR/") == std::string::npos);
/*LTS-START*/
// allow setting of max lead time through buffer variable.
// max lead time is set in MS, but the variable is in integer seconds for simplicity.
if (H.GetVar("buffer") != ""){maxSkipAhead = JSON::Value(H.GetVar("buffer")).asInt() * 1000;}
// allow setting of play back rate through buffer variable.
// play back rate is set in MS per second, but the variable is a simple multiplier.
if (H.GetVar("rate") != ""){
long long int multiplier = JSON::Value(H.GetVar("rate")).asInt();
if (multiplier){
realTime = 1000 / multiplier;
}else{
realTime = 0;
}
}
if (H.GetHeader("X-Mist-Rate") != ""){
long long int multiplier = JSON::Value(H.GetHeader("X-Mist-Rate")).asInt();
if (multiplier){
realTime = 1000 / multiplier;
}else{
realTime = 0;
}
}
/*LTS-END*/
// Always initialize before anything else, and break if this failed.
initialize();
if (!myConn){return;}
uint32_t mainTrack = M.mainTrack(); uint32_t mainTrack = M.mainTrack();
if (mainTrack == INVALID_TRACK_ID){ if (mainTrack == INVALID_TRACK_ID){
onFail("No main track found", true); onFail("No main track found", true);
@ -1122,9 +1079,9 @@ namespace Mist{
DTSC::Fragments fragments(M.fragments(mainTrack)); DTSC::Fragments fragments(M.fragments(mainTrack));
if (H.GetVar("startfrag") != ""){ if (req.GetVar("startfrag") != ""){
realTime = 0; realTime = 0;
size_t startFrag = JSON::Value(H.GetVar("startfrag")).asInt(); size_t startFrag = JSON::Value(req.GetVar("startfrag")).asInt();
if (startFrag >= fragments.getFirstValid() && startFrag < fragments.getEndValid()){ if (startFrag >= fragments.getFirstValid() && startFrag < fragments.getEndValid()){
startTime = M.getTimeForFragmentIndex(mainTrack, startFrag); startTime = M.getTimeForFragmentIndex(mainTrack, startFrag);
@ -1140,8 +1097,8 @@ namespace Mist{
} }
} }
if (H.GetVar("endfrag") != ""){ if (req.GetVar("endfrag") != ""){
size_t endFrag = JSON::Value(H.GetVar("endfrag")).asInt(); size_t endFrag = JSON::Value(req.GetVar("endfrag")).asInt();
if (endFrag < fragments.getEndValid()){ if (endFrag < fragments.getEndValid()){
endTime = M.getTimeForFragmentIndex(mainTrack, endFrag); endTime = M.getTimeForFragmentIndex(mainTrack, endFrag);
}else{ }else{
@ -1149,19 +1106,14 @@ namespace Mist{
} }
} }
if (H.GetVar("starttime") != ""){ if (req.GetVar("starttime") != ""){
startTime = std::max((uint64_t)JSON::Value(H.GetVar("starttime")).asInt(), M.getFirstms(mainTrack)); startTime = std::max((uint64_t)JSON::Value(req.GetVar("starttime")).asInt(), M.getFirstms(mainTrack));
} }
if (H.GetVar("endtime") != ""){ if (req.GetVar("endtime") != ""){
endTime = std::min((uint64_t)JSON::Value(H.GetVar("endtime")).asInt(), M.getLastms(mainTrack)); endTime = std::min((uint64_t)JSON::Value(req.GetVar("endtime")).asInt(), M.getLastms(mainTrack));
} }
// Make sure we start receiving data after this function
parseData = true;
wantRequest = false;
sentHeader = false;
// Check if the url contains .3gp --> if yes, we will send a 3gp header // Check if the url contains .3gp --> if yes, we will send a 3gp header
sending3GP = (H.url.find(".3gp") != std::string::npos); sending3GP = (H.url.find(".3gp") != std::string::npos);
@ -1169,14 +1121,9 @@ namespace Mist{
headerSize = mp4HeaderSize(fileSize, M.getLive()); headerSize = mp4HeaderSize(fileSize, M.getLive());
seekPoint = 0; seekPoint = 0;
if (M.getLive()){ // for live we use fragmented mode
// for live we use fragmented mode if (M.getLive()){fragSeqNum = 0;}
fragSeqNum = 0;
}
byteStart = 0;
byteEnd = fileSize - 1;
char rangeType = ' ';
currPos = 0;
sortSet.clear(); sortSet.clear();
for (std::map<size_t, Comms::Users>::const_iterator subIt = userSelect.begin(); for (std::map<size_t, Comms::Users>::const_iterator subIt = userSelect.begin();
subIt != userSelect.end(); subIt++){ subIt != userSelect.end(); subIt++){
@ -1186,23 +1133,14 @@ namespace Mist{
temp.index = 0; temp.index = 0;
sortSet.insert(temp); sortSet.insert(temp);
} }
if (M.getVod() && H.GetHeader("Range") != ""){
if (parseRange(byteStart, byteEnd)){findSeekPoint(byteStart, seekPoint, headerSize);}
rangeType = H.GetHeader("Range")[0];
}
HTTP::Parser request = H;
H.Clean(); // make sure no parts of old requests are left in any buffers
H.setCORSHeaders();
if (dl.size()){
H.SetHeader("Content-Disposition", "attachment; filename=" + Encodings::URL::encode(dl) + ";");
realTime = 0; // force max download speed when downloading
}
H.SetHeader("Content-Type", "video/MP4"); // Send the correct content-type for MP4 files
if (M.getVod()){H.SetHeader("Accept-Ranges", "bytes, parsec");}
if (rangeType != ' '){ byteStart = 0;
byteEnd = fileSize - 1;
currPos = 0;
if (!M.getLive() && req.GetHeader("Range") != ""){
if (parseRange(req.GetHeader("Range"), byteStart, byteEnd)){findSeekPoint(byteStart, seekPoint, headerSize);}
if (!byteEnd){ if (!byteEnd){
if (rangeType == 'p'){ if (req.GetHeader("Range")[0] == 'p'){
H.SetBody("Starsystem not in communications range"); H.SetBody("Starsystem not in communications range");
H.SendResponse("416", "Starsystem not in communications range", myConn); H.SendResponse("416", "Starsystem not in communications range", myConn);
parseData = false; parseData = false;
@ -1220,12 +1158,21 @@ namespace Mist{
rangeReply << "bytes " << byteStart << "-" << byteEnd << "/" << fileSize; rangeReply << "bytes " << byteStart << "-" << byteEnd << "/" << fileSize;
H.SetHeader("Content-Length", byteEnd - byteStart + 1); H.SetHeader("Content-Length", byteEnd - byteStart + 1);
H.SetHeader("Content-Range", rangeReply.str()); H.SetHeader("Content-Range", rangeReply.str());
H.StartResponse("206", "Partial content", request, myConn); H.StartResponse("206", "Partial content", req, myConn);
} }
}else{ }else{
if (M.getVod()){H.SetHeader("Content-Length", byteEnd - byteStart + 1);} if (M.getVod()){H.SetHeader("Content-Length", byteEnd - byteStart + 1);}
H.StartResponse("200", "OK", request, myConn); H.StartResponse("200", "OK", req, myConn);
} }
if (headersOnly){return;}
//Start sending data
parseData = true;
wantRequest = false;
sentHeader = false;
//Send MP4 header if needed
leftOver = byteEnd - byteStart + 1; // add one byte, because range "0-0" = 1 byte of data leftOver = byteEnd - byteStart + 1; // add one byte, because range "0-0" = 1 byte of data
byteEnd++; byteEnd++;
if (byteStart < headerSize){ if (byteStart < headerSize){

View file

@ -100,7 +100,7 @@ namespace Mist{
void findSeekPoint(uint64_t byteStart, uint64_t &seekPoint, uint64_t headerSize); void findSeekPoint(uint64_t byteStart, uint64_t &seekPoint, uint64_t headerSize);
void appendSinglePacketMoof(Util::ResizeablePointer& moofOut, size_t extraBytes = 0); void appendSinglePacketMoof(Util::ResizeablePointer& moofOut, size_t extraBytes = 0);
size_t fragmentHeaderSize(std::deque<size_t>& sortedTracks, std::set<keyPart>& trunOrder, uint64_t startFragmentTime, uint64_t endFragmentTime); size_t fragmentHeaderSize(std::deque<size_t>& sortedTracks, std::set<keyPart>& trunOrder, uint64_t startFragmentTime, uint64_t endFragmentTime);
void onHTTP(); void respondHTTP(const HTTP::Parser & req, bool headersOnly);
void sendNext(); void sendNext();
void sendHeader(); void sendHeader();
bool doesWebsockets() { return true; } bool doesWebsockets() { return true; }