liveMedia/RTSPServerSupportingHTTPStreaming.cpp

Go to the documentation of this file.
00001 /**********
00002 This library is free software; you can redistribute it and/or modify it under
00003 the terms of the GNU Lesser General Public License as published by the
00004 Free Software Foundation; either version 2.1 of the License, or (at your
00005 option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
00006 
00007 This library is distributed in the hope that it will be useful, but WITHOUT
00008 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00009 FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
00010 more details.
00011 
00012 You should have received a copy of the GNU Lesser General Public License
00013 along with this library; if not, write to the Free Software Foundation, Inc.,
00014 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
00015 **********/
00016 // "liveMedia"
00017 // Copyright (c) 1996-2014 Live Networks, Inc.  All rights reserved.
00018 // A server that supports both RTSP, and HTTP streaming (using Apple's "HTTP Live Streaming" protocol)
00019 // Implementation
00020 
00021 #include "RTSPServerSupportingHTTPStreaming.hh"
00022 #include "RTSPCommon.hh"
00023 #ifndef _WIN32_WCE
00024 #include <sys/stat.h>
00025 #endif
00026 #include <time.h>
00027 
00028 RTSPServerSupportingHTTPStreaming*
00029 RTSPServerSupportingHTTPStreaming::createNew(UsageEnvironment& env, Port rtspPort,
00030                                              UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds) {
00031   int ourSocket = setUpOurSocket(env, rtspPort);
00032   if (ourSocket == -1) return NULL;
00033 
00034   return new RTSPServerSupportingHTTPStreaming(env, ourSocket, rtspPort, authDatabase, reclamationTestSeconds);
00035 }
00036 
00037 RTSPServerSupportingHTTPStreaming
00038 ::RTSPServerSupportingHTTPStreaming(UsageEnvironment& env, int ourSocket, Port rtspPort,
00039                                     UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds)
00040   : RTSPServer(env, ourSocket, rtspPort, authDatabase, reclamationTestSeconds) {
00041 }
00042 
00043 RTSPServerSupportingHTTPStreaming::~RTSPServerSupportingHTTPStreaming() {
00044 }
00045 
00046 RTSPServer::RTSPClientConnection*
00047 RTSPServerSupportingHTTPStreaming::createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) {
00048   return new RTSPClientConnectionSupportingHTTPStreaming(*this, clientSocket, clientAddr);
00049 }
00050 
00051 RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming
00052 ::RTSPClientConnectionSupportingHTTPStreaming(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
00053   : RTSPClientConnection(ourServer, clientSocket, clientAddr),
00054     fClientSessionId(0), fPlaylistSource(NULL), fTCPSink(NULL) {
00055 }
00056 
00057 RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming::~RTSPClientConnectionSupportingHTTPStreaming() {
00058   Medium::close(fPlaylistSource);
00059   Medium::close(fTCPSink);
00060 }
00061 
00062 static char const* lastModifiedHeader(char const* fileName) {
00063   static char buf[200];
00064   buf[0] = '\0'; // by default, return an empty string
00065 
00066 #ifndef _WIN32_WCE
00067   struct stat sb;
00068   int statResult = stat(fileName, &sb);
00069   if (statResult == 0) {
00070     strftime(buf, sizeof buf, "Last-Modified: %a, %b %d %Y %H:%M:%S GMT\r\n", gmtime((const time_t*)&sb.st_mtime));
00071   }
00072 #endif
00073 
00074   return buf;
00075 }
00076 
00077 void RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming
00078 ::handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* /*fullRequestStr*/) {
00079   // If "urlSuffix" ends with "?segment=<offset-in-seconds>,<duration-in-seconds>", then strip this off, and send the
00080   // specified segment.  Otherwise, construct and send a playlist that consists of segments from the specified file.
00081   do {
00082     char const* questionMarkPos = strrchr(urlSuffix, '?');
00083     if (questionMarkPos == NULL) break;
00084     unsigned offsetInSeconds, durationInSeconds;
00085     if (sscanf(questionMarkPos, "?segment=%u,%u", &offsetInSeconds, &durationInSeconds) != 2) break;
00086 
00087     char* streamName = strDup(urlSuffix);
00088     streamName[questionMarkPos-urlSuffix] = '\0';
00089 
00090     do {
00091       ServerMediaSession* session = fOurServer.lookupServerMediaSession(streamName);
00092       if (session == NULL) {
00093         handleHTTPCmd_notFound();
00094         break;
00095       }
00096 
00097       // We can't send multi-subsession streams over HTTP (because there's no defined way to multiplex more than one subsession).
00098       // Therefore, use the first (and presumed only) substream:
00099       ServerMediaSubsessionIterator iter(*session);
00100       ServerMediaSubsession* subsession = iter.next();
00101       if (subsession == NULL) {
00102         // Treat an 'empty' ServerMediaSession the same as one that doesn't exist at all:
00103         handleHTTPCmd_notFound();
00104         break;
00105       }
00106 
00107       // Call "getStreamParameters()" to create the stream's source.  (Because we're not actually streaming via RTP/RTCP, most
00108       // of the parameters to the call are dummy.)
00109       ++fClientSessionId;
00110       Port clientRTPPort(0), clientRTCPPort(0), serverRTPPort(0), serverRTCPPort(0);
00111       netAddressBits destinationAddress = 0;
00112       u_int8_t destinationTTL = 0;
00113       Boolean isMulticast = False;
00114       void* streamToken;
00115       subsession->getStreamParameters(fClientSessionId, 0, clientRTPPort,clientRTCPPort, -1,0,0, destinationAddress,destinationTTL, isMulticast, serverRTPPort,serverRTCPPort, streamToken);
00116       
00117       // Seek the stream source to the desired place, with the desired duration, and (as a side effect) get the number of bytes:
00118       double dOffsetInSeconds = (double)offsetInSeconds;
00119       u_int64_t numBytes;
00120       subsession->seekStream(fClientSessionId, streamToken, dOffsetInSeconds, (double)durationInSeconds, numBytes);
00121       unsigned numTSBytesToStream = (unsigned)numBytes;
00122       
00123       if (numTSBytesToStream == 0) {
00124         // For some reason, we do not know the size of the requested range.  We can't handle this request:
00125         handleHTTPCmd_notSupported();
00126         break;
00127       }
00128       
00129       // Construct our response:
00130       snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
00131                "HTTP/1.1 200 OK\r\n"
00132                "%s"
00133                "Server: LIVE555 Streaming Media v%s\r\n"
00134                "%s"
00135                "Content-Length: %d\r\n"
00136                "Content-Type: text/plain; charset=ISO-8859-1\r\n"
00137                "\r\n",
00138                dateHeader(),
00139                LIVEMEDIA_LIBRARY_VERSION_STRING,
00140                lastModifiedHeader(streamName),
00141                numTSBytesToStream);
00142       // Send the response now, because we're about to add more data (from the source):
00143       send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
00144       fResponseBuffer[0] = '\0'; // We've already sent the response.  This tells the calling code not to send it again.
00145       
00146       // Ask the media source to deliver - to the TCP sink - the desired data:
00147       FramedSource* mediaSource = subsession->getStreamSource(streamToken);
00148       if (mediaSource != NULL) {
00149         if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
00150         fTCPSink->startPlaying(*mediaSource, afterStreaming, this);
00151       }
00152     } while(0);
00153 
00154     delete[] streamName;
00155     return;
00156   } while (0);
00157 
00158   // "urlSuffix" does not end with "?segment=<offset-in-seconds>,<duration-in-seconds>".
00159   // Construct and send a playlist that describes segments from the specified file.
00160 
00161   // First, make sure that the named file exists, and is streamable:
00162   ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlSuffix);
00163   if (session == NULL) {
00164     handleHTTPCmd_notFound();
00165     return;
00166   }
00167 
00168   // To be able to construct a playlist for the requested file, we need to know its duration:
00169   float duration = session->duration();
00170   if (duration <= 0.0) {
00171     // We can't handle this request:
00172     handleHTTPCmd_notSupported();
00173     return;
00174   }
00175 
00176   // Now, construct the playlist.  It will consist of a prefix, one or more media file specifications, and a suffix:
00177   unsigned const maxIntLen = 10; // >= the maximum possible strlen() of an integer in the playlist
00178   char const* const playlistPrefixFmt =
00179     "#EXTM3U\r\n"
00180     "#EXT-X-ALLOW-CACHE:YES\r\n"
00181     "#EXT-X-MEDIA-SEQUENCE:0\r\n"
00182     "#EXT-X-TARGETDURATION:%d\r\n";
00183   unsigned const playlistPrefixFmt_maxLen = strlen(playlistPrefixFmt) + maxIntLen;
00184 
00185   char const* const playlistMediaFileSpecFmt =
00186     "#EXTINF:%d,\r\n"
00187     "%s?segment=%d,%d\r\n";
00188   unsigned const playlistMediaFileSpecFmt_maxLen = strlen(playlistMediaFileSpecFmt) + maxIntLen + strlen(urlSuffix) + 2*maxIntLen;
00189 
00190   char const* const playlistSuffixFmt =
00191     "#EXT-X-ENDLIST\r\n";
00192   unsigned const playlistSuffixFmt_maxLen = strlen(playlistSuffixFmt);
00193 
00194   // Figure out the 'target duration' that will produce a playlist that will fit in our response buffer.  (But make it at least 10s.)
00195   unsigned const playlistMaxSize = 10000;
00196   unsigned const mediaFileSpecsMaxSize = playlistMaxSize - (playlistPrefixFmt_maxLen + playlistSuffixFmt_maxLen);
00197   unsigned const maxNumMediaFileSpecs = mediaFileSpecsMaxSize/playlistMediaFileSpecFmt_maxLen;
00198 
00199   unsigned targetDuration = (unsigned)(duration/maxNumMediaFileSpecs + 1);
00200   if (targetDuration < 10) targetDuration = 10;
00201 
00202   char* playlist = new char[playlistMaxSize];
00203   char* s = playlist;
00204   sprintf(s, playlistPrefixFmt, targetDuration);
00205   s += strlen(s);
00206 
00207   unsigned durSoFar = 0;
00208   while (1) {
00209     unsigned dur = targetDuration < duration ? targetDuration : (unsigned)duration;
00210     duration -= dur;
00211     sprintf(s, playlistMediaFileSpecFmt, dur, urlSuffix, durSoFar, dur);
00212     s += strlen(s);
00213     if (duration < 1.0) break;
00214 
00215     durSoFar += dur;
00216   }
00217 
00218   sprintf(s, playlistSuffixFmt);
00219   s += strlen(s);
00220   unsigned playlistLen = s - playlist;
00221 
00222   // Construct our response:
00223   snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
00224            "HTTP/1.1 200 OK\r\n"
00225            "%s"
00226            "Server: LIVE555 Streaming Media v%s\r\n"
00227            "%s"
00228            "Content-Length: %d\r\n"
00229            "Content-Type: application/vnd.apple.mpegurl\r\n"
00230            "\r\n",
00231            dateHeader(),
00232            LIVEMEDIA_LIBRARY_VERSION_STRING,
00233            lastModifiedHeader(urlSuffix),
00234            playlistLen);
00235 
00236   // Send the response header now, because we're about to add more data (the playlist):
00237   send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
00238   fResponseBuffer[0] = '\0'; // We've already sent the response.  This tells the calling code not to send it again.
00239 
00240   // Then, send the playlist.  Because it's large, we don't do so using "send()", because that might not send it all at once.
00241   // Instead, we stream the playlist over the TCP socket:
00242   if (fPlaylistSource != NULL) { // sanity check
00243     if (fTCPSink != NULL) fTCPSink->stopPlaying();
00244     Medium::close(fPlaylistSource);
00245   }
00246   fPlaylistSource = ByteStreamMemoryBufferSource::createNew(envir(), (u_int8_t*)playlist, playlistLen);
00247   if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
00248   fTCPSink->startPlaying(*fPlaylistSource, afterStreaming, this);
00249 }
00250 
00251 void RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming::afterStreaming(void* clientData) {
00252    RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming* clientConnection
00253     = (RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming*)clientData;
00254    // Arrange to delete the 'client connection' object:
00255    if (clientConnection->fRecursionCount > 0) {
00256      // We're still in the midst of handling a request
00257      clientConnection->fIsActive = False; // will cause the object to get deleted at the end of handling the request
00258    } else {
00259      // We're no longer handling a request; delete the object now:
00260      delete clientConnection;
00261    }
00262 }

Generated on Wed Apr 23 16:12:01 2014 for live by  doxygen 1.5.2