|
|
|
/* ---------------------------------------------------------------------------
|
|
|
|
** This software is in the public domain, furnished "as is", without technical
|
|
|
|
** support, and with no warranty, express or implied, as to its usefulness for
|
|
|
|
** any purpose.
|
|
|
|
**
|
|
|
|
** HTTPServer.cpp
|
|
|
|
**
|
|
|
|
** V4L2 RTSP streamer
|
|
|
|
**
|
|
|
|
** HTTP server that serves HLS & MPEG-DASH playlist and segments
|
|
|
|
**
|
|
|
|
** -------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
#include <sstream>
|
|
|
|
#include <fstream>
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include "RTSPServer.hh"
|
|
|
|
#include "RTSPCommon.hh"
|
|
|
|
#include <time.h>
|
|
|
|
#include "ByteStreamMemoryBufferSource.hh"
|
|
|
|
|
|
|
|
#include "HTTPServer.h"
|
|
|
|
|
|
|
|
u_int32_t HTTPServer::HTTPClientConnection::m_ClientSessionId = 0;
|
|
|
|
|
|
|
|
void HTTPServer::HTTPClientConnection::sendHeader(const char* contentType, unsigned int contentLength)
|
|
|
|
{
|
|
|
|
// Construct our response:
|
|
|
|
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
|
|
|
|
"HTTP/1.1 200 OK\r\n"
|
|
|
|
"%s"
|
|
|
|
"Server: LIVE555 Streaming Media v%s\r\n"
|
|
|
|
"Access-Control-Allow-Origin: *\r\n"
|
|
|
|
"Content-Type: %s\r\n"
|
|
|
|
"Content-Length: %d\r\n"
|
|
|
|
"\r\n",
|
|
|
|
dateHeader(),
|
|
|
|
LIVEMEDIA_LIBRARY_VERSION_STRING,
|
|
|
|
contentType,
|
|
|
|
contentLength);
|
|
|
|
|
|
|
|
// Send the response header
|
|
|
|
send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
|
|
|
|
fResponseBuffer[0] = '\0'; // We've already sent the response. This tells the calling code not to send it again.
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTTPServer::HTTPClientConnection::streamSource(const std::string & content)
|
|
|
|
{
|
|
|
|
u_int8_t* buffer = new u_int8_t[content.size()];
|
|
|
|
memcpy(buffer, content.c_str(), content.size());
|
|
|
|
this->streamSource(ByteStreamMemoryBufferSource::createNew(envir(), buffer, content.size()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTTPServer::HTTPClientConnection::streamSource(FramedSource* source)
|
|
|
|
{
|
|
|
|
if (m_TCPSink != NULL)
|
|
|
|
{
|
|
|
|
m_TCPSink->stopPlaying();
|
|
|
|
Medium::close(m_TCPSink);
|
|
|
|
}
|
|
|
|
if (m_Source != NULL)
|
|
|
|
{
|
|
|
|
Medium::close(m_Source);
|
|
|
|
}
|
|
|
|
if (source != NULL)
|
|
|
|
{
|
|
|
|
m_TCPSink = new TCPSink(envir(), fClientOutputSocket);
|
|
|
|
m_TCPSink->startPlaying(*source, afterStreaming, this);
|
|
|
|
m_Source = source;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void lookupServerMediaSessionCompletionFuncCallback(void* clientData, ServerMediaSession* sessionLookedUp) {
|
|
|
|
ServerMediaSession** ptr = (ServerMediaSession**)clientData;
|
|
|
|
*ptr = sessionLookedUp;
|
|
|
|
}
|
|
|
|
|
|
|
|
ServerMediaSubsession* HTTPServer::HTTPClientConnection::getSubsesion(const char* urlSuffix)
|
|
|
|
{
|
|
|
|
ServerMediaSubsession* subsession = NULL;
|
|
|
|
ServerMediaSession* session = NULL;
|
|
|
|
#if LIVEMEDIA_LIBRARY_VERSION_INT < 1610582400
|
|
|
|
session = fOurServer.lookupServerMediaSession(urlSuffix);
|
|
|
|
#else
|
|
|
|
fOurServer.lookupServerMediaSession(urlSuffix, lookupServerMediaSessionCompletionFuncCallback, &session);
|
|
|
|
#endif
|
|
|
|
if (session != NULL)
|
|
|
|
{
|
|
|
|
ServerMediaSubsessionIterator iter(*session);
|
|
|
|
subsession = iter.next();
|
|
|
|
}
|
|
|
|
return subsession;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HTTPServer::HTTPClientConnection::sendM3u8PlayList(char const* urlSuffix)
|
|
|
|
{
|
|
|
|
ServerMediaSubsession* subsession = this->getSubsesion(urlSuffix);
|
|
|
|
if (subsession == NULL)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
float duration = subsession->duration();
|
|
|
|
if (duration <= 0.0)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int startTime = subsession->getCurrentNPT(NULL);
|
|
|
|
HTTPServer* httpServer = (HTTPServer*)(&fOurServer);
|
|
|
|
unsigned sliceDuration = httpServer->m_hlsSegment;
|
|
|
|
std::ostringstream os;
|
|
|
|
os << "#EXTM3U\r\n"
|
|
|
|
<< "#EXT-X-ALLOW-CACHE:NO\r\n"
|
|
|
|
<< "#EXT-X-MEDIA-SEQUENCE:" << startTime << "\r\n"
|
|
|
|
<< "#EXT-X-TARGETDURATION:" << sliceDuration << "\r\n";
|
|
|
|
|
|
|
|
for (unsigned int slice=0; slice*sliceDuration<duration; slice++)
|
|
|
|
{
|
|
|
|
os << "#EXTINF:" << sliceDuration << ",\r\n";
|
|
|
|
os << urlSuffix << "?segment=" << (startTime+slice*sliceDuration) << "\r\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
envir() << "send M3u8 playlist:" << urlSuffix <<"\n";
|
|
|
|
const std::string& playList(os.str());
|
|
|
|
|
|
|
|
// send response header
|
|
|
|
this->sendHeader("application/vnd.apple.mpegurl", playList.size());
|
|
|
|
|
|
|
|
// stream body
|
|
|
|
this->streamSource(playList);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HTTPServer::HTTPClientConnection::sendMpdPlayList(char const* urlSuffix)
|
|
|
|
{
|
|
|
|
ServerMediaSubsession* subsession = this->getSubsesion(urlSuffix);
|
|
|
|
if (subsession == NULL)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
float duration = subsession->duration();
|
|
|
|
if (duration <= 0.0)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int startTime = subsession->getCurrentNPT(NULL);
|
|
|
|
HTTPServer* httpServer = (HTTPServer*)(&fOurServer);
|
|
|
|
unsigned sliceDuration = httpServer->m_hlsSegment;
|
|
|
|
std::ostringstream os;
|
|
|
|
|
|
|
|
os << "<?xml version='1.0' encoding='UTF-8'?>\r\n"
|
|
|
|
<< "<MPD type='dynamic' xmlns='urn:mpeg:DASH:schema:MPD:2011' profiles='urn:mpeg:dash:profile:full:2011' minimumUpdatePeriod='PT"<< sliceDuration <<"S' minBufferTime='" << sliceDuration << "'>\r\n"
|
|
|
|
<< "<Period start='PT0S'><AdaptationSet segmentAlignment='true'><Representation mimeType='video/mp2t' codecs='' >\r\n";
|
|
|
|
|
|
|
|
os << "<SegmentTemplate duration='" << sliceDuration << "' media='" << urlSuffix << "?segment=$Number$' startNumber='" << startTime << "' />\r\n";
|
|
|
|
os << "</Representation></AdaptationSet></Period>\r\n";
|
|
|
|
os << "</MPD>\r\n";
|
|
|
|
|
|
|
|
envir() << "send MPEG-DASH playlist:" << urlSuffix <<"\n";
|
|
|
|
const std::string& playList(os.str());
|
|
|
|
|
|
|
|
// send response header
|
|
|
|
this->sendHeader("application/dash+xml", playList.size());
|
|
|
|
|
|
|
|
// stream body
|
|
|
|
this->streamSource(playList);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HTTPServer::HTTPClientConnection::sendFile(char const* urlSuffix)
|
|
|
|
{
|
|
|
|
bool ok = false;
|
|
|
|
|
|
|
|
std::string url(urlSuffix);
|
|
|
|
size_t pos = url.find_first_of(" ");
|
|
|
|
if (pos != std::string::npos)
|
|
|
|
{
|
|
|
|
url.erase(0,pos+1);
|
|
|
|
}
|
|
|
|
pos = url.find_first_of(" ");
|
|
|
|
if (pos != std::string::npos)
|
|
|
|
{
|
|
|
|
url.erase(pos);
|
|
|
|
}
|
|
|
|
pos = url.find_first_of("/");
|
|
|
|
if (pos != std::string::npos)
|
|
|
|
{
|
|
|
|
url.erase(0,1);
|
|
|
|
}
|
|
|
|
std::string pattern("../");
|
|
|
|
while ((pos = url.find(pattern, pos)) != std::string::npos) {
|
|
|
|
url.erase(pos, pattern.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string ext;
|
|
|
|
pos = url.find_last_of(".");
|
|
|
|
if (pos != std::string::npos)
|
|
|
|
{
|
|
|
|
ext.assign(url.substr(pos+1));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (url.empty())
|
|
|
|
{
|
|
|
|
url = "index.html";
|
|
|
|
ext = "html";
|
|
|
|
}
|
|
|
|
if (ext=="js") ext ="javascript";
|
|
|
|
HTTPServer* httpServer = (HTTPServer*)(&fOurServer);
|
|
|
|
if (!httpServer->m_webroot.empty()) {
|
|
|
|
url.insert(0, httpServer->m_webroot);
|
|
|
|
}
|
|
|
|
std::ifstream file(url.c_str());
|
|
|
|
if (file.is_open())
|
|
|
|
{
|
|
|
|
envir() << "send file:" << url.c_str() <<"\n";
|
|
|
|
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
|
|
|
std::string mime("text/");
|
|
|
|
mime.append(ext);
|
|
|
|
this->sendHeader(mime.c_str(), content.size());
|
|
|
|
this->streamSource(content);
|
|
|
|
ok = true;
|
|
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTTPServer::HTTPClientConnection::handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* fullRequestStr)
|
|
|
|
{
|
|
|
|
char const* questionMarkPos = strrchr(urlSuffix, '?');
|
|
|
|
if (strcmp(urlSuffix, "getVersion") == 0)
|
|
|
|
{
|
|
|
|
std::ostringstream os;
|
|
|
|
os << VERSION;
|
|
|
|
std::string content(os.str());
|
|
|
|
this->sendHeader("text/plain", content.size());
|
|
|
|
this->streamSource(content);
|
|
|
|
}
|
|
|
|
else if (strncmp(urlSuffix, "getSnapshot", strlen("getSnapshot")) == 0)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
else if (strncmp(urlSuffix, "getStreamList", strlen("getStreamList")) == 0)
|
|
|
|
{
|
|
|
|
std::ostringstream os;
|
|
|
|
if (questionMarkPos != NULL) {
|
|
|
|
questionMarkPos++;
|
|
|
|
os << "var " << questionMarkPos << "=";
|
|
|
|
}
|
|
|
|
os << "[\n";
|
|
|
|
bool first = true;
|
|
|
|
ServerMediaSessionIterator it(fOurServer);
|
|
|
|
ServerMediaSession* serverSession = NULL;
|
|
|
|
while ( (serverSession = it.next()) != NULL) {
|
|
|
|
if (serverSession->duration() > 0) {
|
|
|
|
if (first)
|
|
|
|
{
|
|
|
|
first = false;
|
|
|
|
os << " ";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
os << ",";
|
|
|
|
}
|
|
|
|
os << "\"" << serverSession->streamName() << "\"";
|
|
|
|
os << "\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
os << "]\n";
|
|
|
|
std::string content(os.str());
|
|
|
|
this->sendHeader("text/plain", content.size());
|
|
|
|
this->streamSource(content);
|
|
|
|
}
|
|
|
|
else if (questionMarkPos == NULL)
|
|
|
|
{
|
|
|
|
std::string streamName(urlSuffix);
|
|
|
|
std::string ext;
|
|
|
|
|
|
|
|
std::string url(urlSuffix);
|
|
|
|
size_t pos = url.find_last_of(".");
|
|
|
|
if (pos != std::string::npos)
|
|
|
|
{
|
|
|
|
streamName.assign(url.substr(0,pos));
|
|
|
|
ext.assign(url.substr(pos+1));
|
|
|
|
}
|
|
|
|
bool ok;
|
|
|
|
if (ext == "mpd")
|
|
|
|
{
|
|
|
|
// MPEG-DASH Playlist
|
|
|
|
ok = this->sendMpdPlayList(streamName.c_str());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// HLS Playlist
|
|
|
|
ok = this->sendM3u8PlayList(streamName.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ok)
|
|
|
|
{
|
|
|
|
// send local files
|
|
|
|
ok = this->sendFile(fullRequestStr);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ok)
|
|
|
|
{
|
|
|
|
handleHTTPCmd_notSupported();
|
|
|
|
fIsActive = False;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unsigned offsetInSeconds;
|
|
|
|
if (sscanf(questionMarkPos, "?segment=%u", &offsetInSeconds) != 1)
|
|
|
|
{
|
|
|
|
handleHTTPCmd_notSupported();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string streamName(urlSuffix, questionMarkPos-urlSuffix);
|
|
|
|
ServerMediaSubsession* subsession = this->getSubsesion(streamName.c_str());
|
|
|
|
if (subsession == NULL)
|
|
|
|
{
|
|
|
|
handleHTTPCmd_notSupported();
|
|
|
|
fIsActive = False;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call "getStreamParameters()" to create the stream's source. (Because we're not actually streaming via RTP/RTCP, most
|
|
|
|
// of the parameters to the call are dummy.)
|
|
|
|
++m_ClientSessionId;
|
|
|
|
Port clientRTPPort(0), clientRTCPPort(0), serverRTPPort(0), serverRTCPPort(0);
|
|
|
|
u_int8_t destinationTTL = 0;
|
|
|
|
Boolean isMulticast = False;
|
|
|
|
#if LIVEMEDIA_LIBRARY_VERSION_INT < 1606953600
|
|
|
|
netAddressBits clientAddress = 0;
|
|
|
|
netAddressBits destinationAddress = 0;
|
|
|
|
#else
|
|
|
|
sockaddr_storage clientAddress = { 0 };
|
|
|
|
sockaddr_storage destinationAddress = { 0 };
|
|
|
|
#endif
|
|
|
|
#if LIVEMEDIA_LIBRARY_VERSION_INT < 1636848000
|
|
|
|
subsession->getStreamParameters(m_ClientSessionId, clientAddress, clientRTPPort, clientRTCPPort, -1, 0, 0, destinationAddress, destinationTTL, isMulticast, serverRTPPort, serverRTCPPort, m_StreamToken);
|
|
|
|
#else
|
|
|
|
|
|
|
|
subsession->getStreamParameters(m_ClientSessionId, clientAddress, clientRTPPort, clientRTCPPort, -1, 0, 0, NULL, destinationAddress, destinationTTL, isMulticast, serverRTPPort, serverRTCPPort, m_StreamToken);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Seek the stream source to the desired place, with the desired duration, and (as a side effect) get the number of bytes:
|
|
|
|
double dOffsetInSeconds = (double)offsetInSeconds;
|
|
|
|
u_int64_t numBytes = 0;
|
|
|
|
subsession->seekStream(m_ClientSessionId, m_StreamToken, dOffsetInSeconds, 0.0, numBytes);
|
|
|
|
|
|
|
|
if (numBytes == 0)
|
|
|
|
{
|
|
|
|
// For some reason, we do not know the size of the requested range. We can't handle this request:
|
|
|
|
handleHTTPCmd_notSupported();
|
|
|
|
fIsActive = False;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// send response header
|
|
|
|
this->sendHeader("video/mp2t", numBytes);
|
|
|
|
|
|
|
|
// stream body
|
|
|
|
this->streamSource(subsession->getStreamSource(m_StreamToken));
|
|
|
|
|
|
|
|
// pointer to subsession to close it
|
|
|
|
m_Subsession = subsession;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTTPServer::HTTPClientSession::handleCmd_SETUP(RTSPServer::RTSPClientConnection* ourClientConnection, char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {
|
|
|
|
envir() << "handleCmd_SETUP:" << fullRequestStr;
|
|
|
|
RTSPServer::RTSPClientSession::handleCmd_SETUP(ourClientConnection, urlPreSuffix, urlSuffix, fullRequestStr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTTPServer::HTTPClientConnection::handleCmd_notFound() {
|
|
|
|
std::ostringstream os;
|
|
|
|
ServerMediaSessionIterator it(fOurServer);
|
|
|
|
ServerMediaSession* serverSession = NULL;
|
|
|
|
while ( (serverSession = it.next()) != NULL) {
|
|
|
|
os << serverSession->streamName() << "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
setRTSPResponse("404 Stream Not Found", os.str().c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTTPServer::HTTPClientConnection::afterStreaming(void* clientData)
|
|
|
|
{
|
|
|
|
HTTPServer::HTTPClientConnection* clientConnection = (HTTPServer::HTTPClientConnection*)clientData;
|
|
|
|
|
|
|
|
// Arrange to delete the 'client connection' object:
|
|
|
|
if (clientConnection->fRecursionCount > 0) {
|
|
|
|
// We're still in the midst of handling a request
|
|
|
|
clientConnection->fIsActive = False; // will cause the object to get deleted at the end of handling the request
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// We're no longer handling a request; delete the object now:
|
|
|
|
delete clientConnection;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
HTTPServer::HTTPClientConnection::~HTTPClientConnection()
|
|
|
|
{
|
|
|
|
this->streamSource(NULL);
|
|
|
|
|
|
|
|
if (m_Subsession) {
|
|
|
|
m_Subsession->deleteStream(m_ClientSessionId, m_StreamToken);
|
|
|
|
}
|
|
|
|
}
|