1/* ---------------------------------------------------------------------------
2** This software is in the public domain, furnished "as is", without technical
3** support, and with no warranty, express or implied, as to its usefulness for
4** any purpose.
5**
6** HTTPServer.cpp
7**
8** V4L2 RTSP streamer
9**
10** HTTP server that serves HLS & MPEG-DASH playlist and segments
11**
12** -------------------------------------------------------------------------*/
13
14
15#include <sstream>
16#include <fstream>
17#include <algorithm>
18
19#include "RTSPServer.hh"
20#include "RTSPCommon.hh"
21#include <time.h>
22#include "ByteStreamMemoryBufferSource.hh"
23#include "TCPStreamSink.hh"
24
25#include "HTTPServer.h"
26
27u_int32_t HTTPServer::HTTPClientConnection::fClientSessionId = 0;
28
29void HTTPServer::HTTPClientConnection::sendHeader(const char* contentType, unsigned int contentLength)
30{
31 // Construct our response:
32 snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
33 "HTTP/1.1 200 OK\r\n"
34 "%s"
35 "Server: LIVE555 Streaming Media v%s\r\n"
36 "Access-Control-Allow-Origin: *\r\n"
37 "Content-Type: %s\r\n"
38 "Content-Length: %d\r\n"
39 "\r\n",
40 dateHeader(),
41 LIVEMEDIA_LIBRARY_VERSION_STRING,
42 contentType,
43 contentLength);
44
45 // Send the response header
46 send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
47 fResponseBuffer[0] = '\0'; // We've already sent the response. This tells the calling code not to send it again.
48}
49
50void HTTPServer::HTTPClientConnection::streamSource(const std::string & content)
51{
52 u_int8_t* buffer = new u_int8_t[content.size()];
53 memcpy(buffer, content.c_str(), content.size());
54 this->streamSource(ByteStreamMemoryBufferSource::createNew(envir(), buffer, content.size()));
55}
56
57void HTTPServer::HTTPClientConnection::streamSource(FramedSource* source)
58{
59 if (fTCPSink != NULL)
60 {
61 fTCPSink->stopPlaying();
62 Medium::close(fTCPSink);
63 fTCPSink = NULL;
64 }
65 if (fSource != NULL)
66 {
67 Medium::close(fSource);
68 fSource = NULL;
69 }
70 if (source != NULL)
71 {
72 fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
73 fTCPSink->startPlaying(*source, afterStreaming, this);
74 fSource = source; // we need to keep tracking of source, because sink do not release it
75 }
76}
77
78ServerMediaSubsession* HTTPServer::HTTPClientConnection::getSubsesion(const char* urlSuffix)
79{
80 ServerMediaSubsession* subsession = NULL;
81 ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlSuffix);
82 if (session != NULL)
83 {
84 ServerMediaSubsessionIterator iter(*session);
85 subsession = iter.next();
86 }
87 return subsession;
88}
89
90bool HTTPServer::HTTPClientConnection::sendM3u8PlayList(char const* urlSuffix)
91{
92 ServerMediaSubsession* subsession = this->getSubsesion(urlSuffix);
93 if (subsession == NULL)
94 {
95 return false;
96 }
97
98 float duration = subsession->duration();
99 if (duration <= 0.0)
100 {
101 return false;
102 }
103
104 unsigned int startTime = subsession->getCurrentNPT(NULL);
105 HTTPServer* httpServer = (HTTPServer*)(&fOurServer);
106 unsigned sliceDuration = httpServer->m_hlsSegment;
107 std::ostringstream os;
108 os << "#EXTM3U\r\n"
109 << "#EXT-X-ALLOW-CACHE:NO\r\n"
110 << "#EXT-X-MEDIA-SEQUENCE:" << startTime << "\r\n"
111 << "#EXT-X-TARGETDURATION:" << sliceDuration << "\r\n";
112
113 for (unsigned int slice=0; slice*sliceDuration<duration; slice++)
114 {
115 os << "#EXTINF:" << sliceDuration << ",\r\n";
116 os << urlSuffix << "?segment=" << (startTime+slice*sliceDuration) << "\r\n";
117 }
118
119 envir() << "send M3u8 playlist:" << urlSuffix <<"\n";
120 const std::string& playList(os.str());
121
122 // send response header
123 this->sendHeader("application/vnd.apple.mpegurl", playList.size());
124
125 // stream body
126 this->streamSource(playList);
127
128 return true;
129}
130
131bool HTTPServer::HTTPClientConnection::sendMpdPlayList(char const* urlSuffix)
132{
133 ServerMediaSubsession* subsession = this->getSubsesion(urlSuffix);
134 if (subsession == NULL)
135 {
136 return false;
137 }
138
139 float duration = subsession->duration();
140 if (duration <= 0.0)
141 {
142 return false;
143 }
144
145 unsigned int startTime = subsession->getCurrentNPT(NULL);
146 HTTPServer* httpServer = (HTTPServer*)(&fOurServer);
147 unsigned sliceDuration = httpServer->m_hlsSegment;
148 std::ostringstream os;
149
150 os << "<?xml version='1.0' encoding='UTF-8'?>\r\n"
151 << "<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"
152 << "<Period start='PT0S'><AdaptationSet segmentAlignment='true'><Representation mimeType='video/mp2t' codecs='' >\r\n";
153
154 os << "<SegmentTemplate duration='" << sliceDuration << "' media='" << urlSuffix << "?segment=$Number$' startNumber='" << startTime << "' />\r\n";
155 os << "</Representation></AdaptationSet></Period>\r\n";
156 os << "</MPD>\r\n";
157
158 envir() << "send MPEG-DASH playlist:" << urlSuffix <<"\n";
159 const std::string& playList(os.str());
160
161 // send response header
162 this->sendHeader("application/dash+xml", playList.size());
163
164 // stream body
165 this->streamSource(playList);
166
167 return true;
168}
169
170bool HTTPServer::HTTPClientConnection::sendFile(char const* urlSuffix)
171{
172 bool ok = false;
173
174 std::string url(urlSuffix);
175 size_t pos = url.find_first_of(" ");
176 if (pos != std::string::npos)
177 {
178 url.erase(0,pos+1);
179 }
180 pos = url.find_first_of(" ");
181 if (pos != std::string::npos)
182 {
183 url.erase(pos);
184 }
185 pos = url.find_first_of("/");
186 if (pos != std::string::npos)
187 {
188 url.erase(0,1);
189 }
190 std::string pattern("../");
191 while ((pos = url.find(pattern, pos)) != std::string::npos) {
192 url.erase(pos, pattern.length());
193 }
194
195 std::string ext;
196 pos = url.find_last_of(".");
197 if (pos != std::string::npos)
198 {
199 ext.assign(url.substr(pos+1));
200 }
201
202 if (url.empty())
203 {
204 url = "index.html";
205 ext = "html";
206 }
207 if (ext=="js") ext ="javascript";
208 HTTPServer* httpServer = (HTTPServer*)(&fOurServer);
209 if (!httpServer->m_webroot.empty()) {
210 url.insert(0, httpServer->m_webroot);
211 }
212 std::ifstream file(url.c_str());
213 if (file.is_open())
214 {
215 envir() << "send file:" << url.c_str() <<"\n";
216 std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
217 std::string mime("text/");
218 mime.append(ext);
219 this->sendHeader(mime.c_str(), content.size());
220 this->streamSource(content);
221 ok = true;
222 }
223 return ok;
224}
225
226void HTTPServer::HTTPClientConnection::handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* fullRequestStr)
227{
228 char const* questionMarkPos = strrchr(urlSuffix, '?');
229 if (strcmp(urlSuffix, "getVersion") == 0)
230 {
231 std::ostringstream os;
232 os << VERSION;
233 std::string content(os.str());
234 this->sendHeader("text/plain", content.size());
235 this->streamSource(content);
236 }
237 else if (strncmp(urlSuffix, "getStreamList", strlen("getStreamList")) == 0)
238 {
239 std::ostringstream os;
240 HTTPServer* httpServer = (HTTPServer*)(&fOurServer);
241 ServerMediaSessionIterator it(*httpServer);
242 ServerMediaSession* serverSession = NULL;
243 if (questionMarkPos != NULL) {
244 questionMarkPos++;
245 os << "var " << questionMarkPos << "=";
246 }
247 os << "[\n";
248 bool first = true;
249 while ( (serverSession = it.next()) != NULL) {
250 if (serverSession->duration() > 0) {
251 if (first)
252 {
253 first = false;
254 os << " ";
255 }
256 else
257 {
258 os << ",";
259 }
260 os << "\"" << serverSession->streamName() << "\"";
261 os << "\n";
262 }
263 }
264 os << "]\n";
265 std::string content(os.str());
266 this->sendHeader("text/plain", content.size());
267 this->streamSource(content);
268 }
269 else if (questionMarkPos == NULL)
270 {
271 std::string streamName(urlSuffix);
272 std::string ext;
273
274 std::string url(urlSuffix);
275 size_t pos = url.find_last_of(".");
276 if (pos != std::string::npos)
277 {
278 streamName.assign(url.substr(0,pos));
279 ext.assign(url.substr(pos+1));
280 }
281 bool ok;
282 if (ext == "mpd")
283 {
284 // MPEG-DASH Playlist
285 ok = this->sendMpdPlayList(streamName.c_str());
286 }
287 else
288 {
289 // HLS Playlist
290 ok = this->sendM3u8PlayList(streamName.c_str());
291 }
292
293 if (!ok)
294 {
295 // send local files
296 ok = this->sendFile(fullRequestStr);
297 }
298
299 if (!ok)
300 {
301 handleHTTPCmd_notSupported();
302 fIsActive = False;
303 }
304 }
305 else
306 {
307 unsigned offsetInSeconds;
308 if (sscanf(questionMarkPos, "?segment=%u", &offsetInSeconds) != 1)
309 {
310 handleHTTPCmd_notSupported();
311 return;
312 }
313
314 std::string streamName(urlSuffix, questionMarkPos-urlSuffix);
315 ServerMediaSubsession* subsession = this->getSubsesion(streamName.c_str());
316 if (subsession == NULL)
317 {
318 handleHTTPCmd_notSupported();
319 fIsActive = False;
320 return;
321 }
322
323 // Call "getStreamParameters()" to create the stream's source. (Because we're not actually streaming via RTP/RTCP, most
324 // of the parameters to the call are dummy.)
325 ++fClientSessionId;
326 Port clientRTPPort(0), clientRTCPPort(0), serverRTPPort(0), serverRTCPPort(0);
327 netAddressBits destinationAddress = 0;
328 u_int8_t destinationTTL = 0;
329 Boolean isMulticast = False;
330 subsession->getStreamParameters(fClientSessionId, 0, clientRTPPort,clientRTCPPort, -1,0,0, destinationAddress,destinationTTL, isMulticast, serverRTPPort,serverRTCPPort, fStreamToken);
331
332 // Seek the stream source to the desired place, with the desired duration, and (as a side effect) get the number of bytes:
333 double dOffsetInSeconds = (double)offsetInSeconds;
334 u_int64_t numBytes = 0;
335 subsession->seekStream(fClientSessionId, fStreamToken, dOffsetInSeconds, 0.0, numBytes);
336
337 if (numBytes == 0)
338 {
339 // For some reason, we do not know the size of the requested range. We can't handle this request:
340 handleHTTPCmd_notSupported();
341 fIsActive = False;
342 }
343 else
344 {
345 // send response header
346 this->sendHeader("video/mp2t", numBytes);
347
348 // stream body
349 this->streamSource(subsession->getStreamSource(fStreamToken));
350
351 // pointer to subsession to close it
352 fSubsession = subsession;
353 }
354 }
355}
356
357void HTTPServer::HTTPClientConnection::handleCmd_notFound() {
358 std::ostringstream os;
359 HTTPServer* httpServer = (HTTPServer*)(&fOurServer);
360 ServerMediaSessionIterator it(*httpServer);
361 ServerMediaSession* serverSession = NULL;
362 while ( (serverSession = it.next()) != NULL) {
363 os << serverSession->streamName() << "\n";
364 }
365
366 setRTSPResponse("404 Stream Not Found", os.str().c_str());
367}
368
369void HTTPServer::HTTPClientConnection::afterStreaming(void* clientData)
370{
371 HTTPServer::HTTPClientConnection* clientConnection = (HTTPServer::HTTPClientConnection*)clientData;
372
373 // Arrange to delete the 'client connection' object:
374 if (clientConnection->fRecursionCount > 0) {
375 // We're still in the midst of handling a request
376 clientConnection->fIsActive = False; // will cause the object to get deleted at the end of handling the request
377
378 } else {
379 // We're no longer handling a request; delete the object now:
380 delete clientConnection;
381 }
382}
383
384HTTPServer::HTTPClientConnection::~HTTPClientConnection()
385{
386 this->streamSource(NULL);
387
388 if (fSubsession) {
389 fSubsession->deleteStream(fClientSessionId, fStreamToken);
390 }
391}
392