void RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming
::handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* /*fullRequestStr*/) {
  // If "urlSuffix" ends with "?segment=<offset-in-seconds>,<duration-in-seconds>", then strip this off, and send the
  // specified segment.  Otherwise, construct and send a playlist that consists of segments from the specified file.
  do {
    char const* questionMarkPos = strrchr(urlSuffix, '?');
    if (questionMarkPos == NULL) break;
    unsigned offsetInSeconds, durationInSeconds;
    if (sscanf(questionMarkPos, "?segment=%u,%u", &offsetInSeconds, &durationInSeconds) != 2) break;

    char* streamName = strDup(urlSuffix);
    streamName[questionMarkPos-urlSuffix] = '\0';

    do {
      ServerMediaSession* session = fOurServer.lookupServerMediaSession(streamName);
      if (session == NULL) {
	handleHTTPCmd_notFound();
	break;
      }

      // We can't send multi-subsession streams over HTTP (because there's no defined way to multiplex more than one subsession).
      // Therefore, use the first (and presumed only) substream:
      ServerMediaSubsessionIterator iter(*session);
      ServerMediaSubsession* subsession = iter.next();
      if (subsession == NULL) {
	// Treat an 'empty' ServerMediaSession the same as one that doesn't exist at all:
	handleHTTPCmd_notFound();
	break;
      }

      // 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.)
      ++fClientSessionId;
      Port clientRTPPort(0), clientRTCPPort(0), serverRTPPort(0), serverRTCPPort(0);
      netAddressBits destinationAddress = 0;
      u_int8_t destinationTTL = 0;
      Boolean isMulticast = False;
      void* streamToken;
      subsession->getStreamParameters(fClientSessionId, 0, clientRTPPort,clientRTCPPort, -1,0,0, destinationAddress,destinationTTL, isMulticast, serverRTPPort,serverRTCPPort, streamToken);
      
      // 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;
      subsession->seekStream(fClientSessionId, streamToken, dOffsetInSeconds, (double)durationInSeconds, numBytes);
      unsigned numTSBytesToStream = (unsigned)numBytes;
      
      if (numTSBytesToStream == 0) {
	// For some reason, we do not know the size of the requested range.  We can't handle this request:
	handleHTTPCmd_notSupported();
	break;
      }
      
      // Construct our response:
      snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
	       "HTTP/1.1 200 OK\r\n"
	       "%s"
	       "Server: LIVE555 Streaming Media v%s\r\n"
	       "%s"
	       "Content-Length: %d\r\n"
	       "Content-Type: text/plain; charset=ISO-8859-1\r\n"
	       "\r\n",
	       dateHeader(),
	       LIVEMEDIA_LIBRARY_VERSION_STRING,
	       lastModifiedHeader(streamName),
	       numTSBytesToStream);
      // Send the response now, because we're about to add more data (from the source):
      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.
      
      // Ask the media source to deliver - to the TCP sink - the desired data:
      if (fStreamSource != NULL) { // sanity check
	if (fTCPSink != NULL) fTCPSink->stopPlaying();
	Medium::close(fStreamSource);
      }
      fStreamSource = subsession->getStreamSource(streamToken);
      if (fStreamSource != NULL) {
	if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
	fTCPSink->startPlaying(*fStreamSource, afterStreaming, this);
      }
    } while(0);

    delete[] streamName;
    return;
  } while (0);

  // "urlSuffix" does not end with "?segment=<offset-in-seconds>,<duration-in-seconds>".
  // Construct and send a playlist that describes segments from the specified file.

  // First, make sure that the named file exists, and is streamable:
  ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlSuffix);
  if (session == NULL) {
    handleHTTPCmd_notFound();
    return;
  }

  // To be able to construct a playlist for the requested file, we need to know its duration:
  float duration = session->duration();
  if (duration <= 0.0) {
    // We can't handle this request:
    handleHTTPCmd_notSupported();
    return;
  }

  // Now, construct the playlist.  It will consist of a prefix, one or more media file specifications, and a suffix:
  unsigned const maxIntLen = 10; // >= the maximum possible strlen() of an integer in the playlist
  char const* const playlistPrefixFmt =
    "#EXTM3U\r\n"
    "#EXT-X-ALLOW-CACHE:YES\r\n"
    "#EXT-X-MEDIA-SEQUENCE:0\r\n"
    "#EXT-X-TARGETDURATION:%d\r\n";
  unsigned const playlistPrefixFmt_maxLen = strlen(playlistPrefixFmt) + maxIntLen;

  char const* const playlistMediaFileSpecFmt =
    "#EXTINF:%d,\r\n"
    "%s?segment=%d,%d\r\n";
  unsigned const playlistMediaFileSpecFmt_maxLen = strlen(playlistMediaFileSpecFmt) + maxIntLen + strlen(urlSuffix) + 2*maxIntLen;

  char const* const playlistSuffixFmt =
    "#EXT-X-ENDLIST\r\n";
  unsigned const playlistSuffixFmt_maxLen = strlen(playlistSuffixFmt);

  // Figure out the 'target duration' that will produce a playlist that will fit in our response buffer.  (But make it at least 10s.)
  unsigned const playlistMaxSize = 10000;
  unsigned const mediaFileSpecsMaxSize = playlistMaxSize - (playlistPrefixFmt_maxLen + playlistSuffixFmt_maxLen);
  unsigned const maxNumMediaFileSpecs = mediaFileSpecsMaxSize/playlistMediaFileSpecFmt_maxLen;

  unsigned targetDuration = (unsigned)(duration/maxNumMediaFileSpecs + 1);
  if (targetDuration < 10) targetDuration = 10;

  char* playlist = new char[playlistMaxSize];
  char* s = playlist;
  sprintf(s, playlistPrefixFmt, targetDuration);
  s += strlen(s);

  unsigned durSoFar = 0;
  while (1) {
    unsigned dur = targetDuration < duration ? targetDuration : (unsigned)duration;
    duration -= dur;
    sprintf(s, playlistMediaFileSpecFmt, dur, urlSuffix, durSoFar, dur);
    s += strlen(s);
    if (duration < 1.0) break;

    durSoFar += dur;
  }

  sprintf(s, playlistSuffixFmt);
  s += strlen(s);
  unsigned playlistLen = s - playlist;

  // Construct our response:
  snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
	   "HTTP/1.1 200 OK\r\n"
	   "%s"
	   "Server: LIVE555 Streaming Media v%s\r\n"
	   "%s"
	   "Content-Length: %d\r\n"
	   "Content-Type: application/vnd.apple.mpegurl\r\n"
	   "\r\n",
	   dateHeader(),
	   LIVEMEDIA_LIBRARY_VERSION_STRING,
	   lastModifiedHeader(urlSuffix),
	   playlistLen);

  // Send the response header now, because we're about to add more data (the playlist):
  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.

  // Then, send the playlist.  Because it's large, we don't do so using "send()", because that might not send it all at once.
  // Instead, we stream the playlist over the TCP socket:
  if (fPlaylistSource != NULL) { // sanity check
    if (fTCPSink != NULL) fTCPSink->stopPlaying();
    Medium::close(fPlaylistSource);
  }
  fPlaylistSource = ByteStreamMemoryBufferSource::createNew(envir(), (u_int8_t*)playlist, playlistLen);
  if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
  fTCPSink->startPlaying(*fPlaylistSource, afterStreaming, this);
}