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 | |
27 | u_int32_t HTTPServer::HTTPClientConnection::fClientSessionId = 0; |
28 | |
29 | void HTTPServer::HTTPClientConnection::(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 | |
50 | void 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 | |
57 | void 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 | |
78 | ServerMediaSubsession* 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 | |
90 | bool 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 | |
131 | bool 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 | |
170 | bool 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 | |
226 | void 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 | |
357 | void 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 | |
369 | void 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 | |
384 | HTTPServer::HTTPClientConnection::~HTTPClientConnection() |
385 | { |
386 | this->streamSource(NULL); |
387 | |
388 | if (fSubsession) { |
389 | fSubsession->deleteStream(fClientSessionId, fStreamToken); |
390 | } |
391 | } |
392 | |