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-2012 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 #include <sys/stat.h>
00024 #include <time.h>
00025 
00026 RTSPServerSupportingHTTPStreaming*
00027 RTSPServerSupportingHTTPStreaming::createNew(UsageEnvironment& env, Port rtspPort,
00028                                              UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds) {
00029   int ourSocket = setUpOurSocket(env, rtspPort);
00030   if (ourSocket == -1) return NULL;
00031 
00032   return new RTSPServerSupportingHTTPStreaming(env, ourSocket, rtspPort, authDatabase, reclamationTestSeconds);
00033 }
00034 
00035 RTSPServerSupportingHTTPStreaming
00036 ::RTSPServerSupportingHTTPStreaming(UsageEnvironment& env, int ourSocket, Port rtspPort,
00037                                     UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds)
00038   : RTSPServer(env, ourSocket, rtspPort, authDatabase, reclamationTestSeconds) {
00039 }
00040 
00041 RTSPServerSupportingHTTPStreaming::~RTSPServerSupportingHTTPStreaming() {
00042 }
00043 
00044 RTSPServer::RTSPClientSession*
00045 RTSPServerSupportingHTTPStreaming::createNewClientSession(unsigned sessionId, int clientSocket, struct sockaddr_in clientAddr) {
00046   return new RTSPClientSessionSupportingHTTPStreaming(*this, sessionId, clientSocket, clientAddr);
00047 }
00048 
00049 RTSPServerSupportingHTTPStreaming::RTSPClientSessionSupportingHTTPStreaming
00050 ::RTSPClientSessionSupportingHTTPStreaming(RTSPServer& ourServer, unsigned sessionId, int clientSocket, struct sockaddr_in clientAddr)
00051   : RTSPClientSession(ourServer, sessionId, clientSocket, clientAddr),
00052     fPlaylistSource(NULL), fTCPSink(NULL) {
00053 }
00054 
00055 RTSPServerSupportingHTTPStreaming::RTSPClientSessionSupportingHTTPStreaming::~RTSPClientSessionSupportingHTTPStreaming() {
00056   Medium::close(fPlaylistSource);
00057   Medium::close(fTCPSink);
00058 }
00059 
00060 static char const* lastModifiedHeader(char const* fileName) {
00061   static char buf[200];
00062 
00063   struct stat sb;
00064   int statResult = stat(fileName, &sb);
00065   if (statResult != 0) {
00066     // Failed to 'stat' the file; return an empty string
00067     buf[0] = '\0';
00068   } else {
00069     strftime(buf, sizeof buf, "Last-Modified: %a, %b %d %Y %H:%M:%S GMT\r\n", gmtime((const time_t*)&sb.st_mtime));
00070   }
00071 
00072   return buf;
00073 }
00074 
00075 void RTSPServerSupportingHTTPStreaming::RTSPClientSessionSupportingHTTPStreaming
00076 ::handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* /*fullRequestStr*/) {
00077   // If "urlSuffix" ends with "?segment=<offset-in-seconds>,<duration-in-seconds>", then strip this off, and send the
00078   // specified segment.  Otherwise, construct and send a playlist that consists of segments from the specified file.
00079   do {
00080     char const* questionMarkPos = strrchr(urlSuffix, '?');
00081     if (questionMarkPos == NULL) break;
00082     unsigned offsetInSeconds, durationInSeconds;
00083     if (sscanf(questionMarkPos, "?segment=%u,%u", &offsetInSeconds, &durationInSeconds) != 2) break;
00084 
00085     char* streamName = strDup(urlSuffix);
00086     streamName[questionMarkPos-urlSuffix] = '\0';
00087 
00088     do {
00089       ServerMediaSession* session = fOurServer.lookupServerMediaSession(streamName);
00090       if (session == NULL) {
00091         handleHTTPCmd_notFound();
00092         break;
00093       }
00094 
00095       // We can't send multi-subsession streams over HTTP (because there's no defined way to multiplex more than one subsession).
00096       // Therefore, use the first (and presumed only) substream:
00097       ServerMediaSubsessionIterator iter(*session);
00098       ServerMediaSubsession* subsession = iter.next();
00099       if (subsession == NULL) {
00100         // Treat an 'empty' ServerMediaSession the same as one that doesn't exist at all:
00101         handleHTTPCmd_notFound();
00102         break;
00103       }
00104 
00105       // Call "getStreamParameters()" to create the stream's source.  (Because we're not actually streaming via RTP/RTCP, most
00106       // of the parameters to the call are dummy.)
00107       Port clientRTPPort(0), clientRTCPPort(0), serverRTPPort(0), serverRTCPPort(0);
00108       netAddressBits destinationAddress = 0;
00109       u_int8_t destinationTTL = 0;
00110       Boolean isMulticast = False;
00111       void* streamToken;
00112       subsession->getStreamParameters(fOurSessionId, 0, clientRTPPort,clientRTCPPort, 0,0,0, destinationAddress,destinationTTL, isMulticast, serverRTPPort,serverRTCPPort, streamToken);
00113       
00114       // Set up our "fStreamStates", just as we would if we were handling a RTSP "SETUP":
00115       reclaimStreamStates();
00116       fNumStreamStates = 1;
00117       fStreamStates = new struct streamState[fNumStreamStates];
00118       fStreamStates[0].subsession = subsession;
00119       fStreamStates[0].streamToken = streamToken;
00120       
00121       // Seek the stream source to the desired place, with the desired duration, and (as a side effect) get the number of bytes:
00122       double dOffsetInSeconds = (double)offsetInSeconds;
00123       u_int64_t numBytes;
00124       subsession->seekStream(fOurSessionId, streamToken, dOffsetInSeconds, (double)durationInSeconds, numBytes);
00125       unsigned numTSBytesToStream = (unsigned)numBytes;
00126       
00127       if (numTSBytesToStream == 0) {
00128         // For some reason, we do not know the size of the requested range.  We can't handle this request:
00129         handleHTTPCmd_notSupported();
00130         break;
00131       }
00132       
00133       // Construct our response:
00134       snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
00135                "HTTP/1.1 200 OK\r\n"
00136                "%s"
00137                "Server: LIVE555 Streaming Media v%s\r\n"
00138                "%s"
00139                "Content-Length: %d\r\n"
00140                "Content-Type: text/plain; charset=ISO-8859-1\r\n"
00141                "\r\n",
00142                dateHeader(),
00143                LIVEMEDIA_LIBRARY_VERSION_STRING,
00144                lastModifiedHeader(streamName),
00145                numTSBytesToStream);
00146       // Send the response now, because we're about to add more data (from the source):
00147       send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
00148       fResponseBuffer[0] = '\0'; // We've already sent the response.  This tells the calling code not to send it again.
00149       
00150       // Ask the media source to deliver - to the TCP sink - the desired data:
00151       FramedSource* mediaSource = subsession->getStreamSource(streamToken);
00152       if (mediaSource != NULL) {
00153         if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
00154         fTCPSink->startPlaying(*mediaSource, afterStreaming, this);
00155       }
00156     } while(0);
00157 
00158     delete[] streamName;
00159     return;
00160   } while (0);
00161 
00162   // "urlSuffix" does not end with "?segment=<offset-in-seconds>,<duration-in-seconds>".
00163   // Construct and send a playlist that describes segments from the specified file.
00164 
00165   // First, make sure that the named file exists, and is streamable:
00166   ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlSuffix);
00167   if (session == NULL) {
00168     handleHTTPCmd_notFound();
00169     return;
00170   }
00171 
00172   // To be able to construct a playlist for the requested file, we need to know its duration:
00173   float duration = session->duration();
00174   if (duration <= 0.0) {
00175     // We can't handle this request:
00176     handleHTTPCmd_notSupported();
00177     return;
00178   }
00179 
00180   // Now, construct the playlist.  It will consist of a prefix, one or more media file specifications, and a suffix:
00181   unsigned const maxIntLen = 10; // >= the maximum possible strlen() of an integer in the playlist
00182   char const* const playlistPrefixFmt =
00183     "#EXTM3U\r\n"
00184     "#EXT-X-ALLOW-CACHE:YES\r\n"
00185     "#EXT-X-MEDIA-SEQUENCE:0\r\n"
00186     "#EXT-X-TARGETDURATION:%d\r\n";
00187   unsigned const playlistPrefixFmt_maxLen = strlen(playlistPrefixFmt) + maxIntLen;
00188 
00189   char const* const playlistMediaFileSpecFmt =
00190     "#EXTINF:%d,\r\n"
00191     "%s?segment=%d,%d\r\n";
00192   unsigned const playlistMediaFileSpecFmt_maxLen = strlen(playlistMediaFileSpecFmt) + maxIntLen + strlen(urlSuffix) + 2*maxIntLen;
00193 
00194   char const* const playlistSuffixFmt =
00195     "#EXT-X-ENDLIST\r\n";
00196   unsigned const playlistSuffixFmt_maxLen = strlen(playlistSuffixFmt);
00197 
00198   // Figure out the 'target duration' that will produce a playlist that will fit in our response buffer.  (But make it at least 10s.)
00199   unsigned const playlistMaxSize = 10000;
00200   unsigned const mediaFileSpecsMaxSize = playlistMaxSize - (playlistPrefixFmt_maxLen + playlistSuffixFmt_maxLen);
00201   unsigned const maxNumMediaFileSpecs = mediaFileSpecsMaxSize/playlistMediaFileSpecFmt_maxLen;
00202 
00203   unsigned targetDuration = (unsigned)(duration/maxNumMediaFileSpecs + 1);
00204   if (targetDuration < 10) targetDuration = 10;
00205 
00206   char* playlist = new char[playlistMaxSize];
00207   char* s = playlist;
00208   sprintf(s, playlistPrefixFmt, targetDuration);
00209   s += strlen(s);
00210 
00211   unsigned durSoFar = 0;
00212   while (1) {
00213     unsigned dur = targetDuration < duration ? targetDuration : (unsigned)duration;
00214     duration -= dur;
00215     sprintf(s, playlistMediaFileSpecFmt, dur, urlSuffix, durSoFar, dur);
00216     s += strlen(s);
00217     if (duration < 1.0) break;
00218 
00219     durSoFar += dur;
00220   }
00221 
00222   sprintf(s, playlistSuffixFmt);
00223   s += strlen(s);
00224   unsigned playlistLen = s - playlist;
00225 
00226   // Construct our response:
00227   snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
00228            "HTTP/1.1 200 OK\r\n"
00229            "%s"
00230            "Server: LIVE555 Streaming Media v%s\r\n"
00231            "%s"
00232            "Content-Length: %d\r\n"
00233            "Content-Type: application/vnd.apple.mpegurl\r\n"
00234            "\r\n",
00235            dateHeader(),
00236            LIVEMEDIA_LIBRARY_VERSION_STRING,
00237            lastModifiedHeader(urlSuffix),
00238            playlistLen);
00239 
00240   // Send the response header now, because we're about to add more data (the playlist):
00241   send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
00242   fResponseBuffer[0] = '\0'; // We've already sent the response.  This tells the calling code not to send it again.
00243 
00244   // Then, send the playlist.  Because it's large, we don't do so using "send()", because that might not send it all at once.
00245   // Instead, we stream the playlist over the TCP socket:
00246   if (fPlaylistSource != NULL) { // sanity check
00247     if (fTCPSink != NULL) fTCPSink->stopPlaying();
00248     Medium::close(fPlaylistSource);
00249   }
00250   fPlaylistSource = ByteStreamMemoryBufferSource::createNew(envir(), (u_int8_t*)playlist, playlistLen);
00251   if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
00252   fTCPSink->startPlaying(*fPlaylistSource, afterStreaming, this);
00253 }
00254 
00255 void RTSPServerSupportingHTTPStreaming::RTSPClientSessionSupportingHTTPStreaming::afterStreaming(void* clientData) {
00256    RTSPServerSupportingHTTPStreaming::RTSPClientSessionSupportingHTTPStreaming* clientSession
00257     = (RTSPServerSupportingHTTPStreaming::RTSPClientSessionSupportingHTTPStreaming*)clientData;
00258    delete clientSession;
00259 }

Generated on Thu May 17 07:11:47 2012 for live by  doxygen 1.5.2