/*
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 */
/*
    File:       QTSSFileModule.cpp

    Contains:   Implementation of module described in QTSSFileModule.h. 
                    


*/

#include <string.h>

#include "QTSSFileModule.h"

#include "QTRTPFile.h"
#include "QTFile.h"
#include "OSMemory.h"
#include "OSArrayObjectDeleter.h"
#include "QTSSMemoryDeleter.h"
#include "SDPSourceInfo.h"
#include "StringFormatter.h"
#include "QTSSModuleUtils.h"
#include "ResizeableStringFormatter.h"
#include "StringParser.h"
#include "SDPUtils.h"

#include <errno.h>

#include "QTSS.h"

class FileSession
{
    public:
    
        FileSession() : fAdjustedPlayTime(0), fNextPacketLen(0), fLastQualityCheck(0),
                        fAllowNegativeTTs(false), fSpeed(1),
                        fStartTime(-1), fStopTime(-1), fStopTrackID(0), fStopPN(0),
                        fLastRTPTime(0), fLastPauseTime(0),fTotalPauseTime(0), fPaused(false)
        {}
        
        ~FileSession() {}
        
        QTRTPFile           fFile;
        SInt64              fAdjustedPlayTime;
        QTSS_PacketStruct   fPacketStruct;
        int                 fNextPacketLen;
        SInt64              fLastQualityCheck;
        SDPSourceInfo       fSDPSource;
        Bool16              fAllowNegativeTTs;
        Float32             fSpeed;
        Float64             fStartTime;
        Float64             fStopTime;
        
        UInt32              fStopTrackID;
        UInt64              fStopPN;

        UInt32              fLastRTPTime;
        UInt64              fLastPauseTime;
        SInt64              fTotalPauseTime;
        Bool16              fPaused;
};

// ref to the prefs dictionary object
static QTSS_ModulePrefsObject       sPrefs;
static QTSS_PrefsObject             sServerPrefs;
static QTSS_Object                  sServer;

static StrPtrLen sSDPSuffix(".sdp");
static  StrPtrLen sVersionHeader("v=0");
static  StrPtrLen sSessionNameHeader("s=");
static  StrPtrLen sPermanentTimeHeader("t=0 0");
static  StrPtrLen sConnectionHeader("c=IN IP4 0.0.0.0");
static StrPtrLen  sStaticControlHeader("a=control:*");
static  StrPtrLen  sEmailHeader;
static  StrPtrLen  sURLHeader;
static  StrPtrLen  sEOL("\r\n");
static  StrPtrLen sSDPNotValidMessage("Movie SDP is not valid.");

const   SInt16    sNumSDPVectors = 22;

// ATTRIBUTES IDs

static QTSS_AttributeID sFileSessionAttr                = qtssIllegalAttrID;

static QTSS_AttributeID sSeekToNonexistentTimeErr       = qtssIllegalAttrID;
static QTSS_AttributeID sNoSDPFileFoundErr              = qtssIllegalAttrID;
static QTSS_AttributeID sBadQTFileErr                   = qtssIllegalAttrID;
static QTSS_AttributeID sFileIsNotHintedErr             = qtssIllegalAttrID;
static QTSS_AttributeID sExpectedDigitFilenameErr       = qtssIllegalAttrID;
static QTSS_AttributeID sTrackDoesntExistErr            = qtssIllegalAttrID;

static QTSS_AttributeID sFileSessionPlayCountAttrID     = qtssIllegalAttrID;
static QTSS_AttributeID sFileSessionBufferDelayAttrID   = qtssIllegalAttrID;

static QTSS_AttributeID sRTPStreamLastSentPacketSeqNumAttrID   = qtssIllegalAttrID;

static QTSS_AttributeID sRTPStreamLastPacketSeqNumAttrID   = qtssIllegalAttrID;

// OTHER DATA

static UInt32				sFlowControlProbeInterval	= 10;
static UInt32				sDefaultFlowControlProbeInterval= 10;
static Float32              sMaxAllowedSpeed            = 4;
static Float32              sDefaultMaxAllowedSpeed     = 4;

// File Caching Prefs
static Bool16               sEnableSharedBuffers    = false;
static Bool16               sEnablePrivateBuffers   = false;

static UInt32               sSharedBufferUnitKSize  = 0;
static UInt32               sSharedBufferInc        = 0;
static UInt32               sSharedBufferUnitSize   = 0;
static UInt32               sSharedBufferMaxUnits   = 0;

static UInt32               sPrivateBufferUnitKSize = 0;
static UInt32               sPrivateBufferUnitSize  = 0;
static UInt32               sPrivateBufferMaxUnits  = 0;

static Float32              sAddClientBufferDelaySecs = 0;

static Bool16               sRecordMovieFileSDP = false;
static Bool16               sEnableMovieFileSDP = false;
static Float64              sDefaultStartTime = 0.0;

static Bool16               sPlayerCompatibility = true;
static UInt32               sAdjustMediaBandwidthPercent = 50;

static const StrPtrLen              kCacheControlHeader("must-revalidate");
static const QTSS_RTSPStatusCode    kNotModifiedStatus          = qtssRedirectNotModified;



  
// FUNCTIONS

static QTSS_Error QTSSFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock);
static QTSS_Error Register(QTSS_Register_Params* inParams);
static QTSS_Error Initialize(QTSS_Initialize_Params* inParamBlock);
static QTSS_Error RereadPrefs();
static QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error CreateQTRTPFile(QTSS_StandardRTSP_Params* inParamBlock, char* inPath, FileSession** outFile);
static QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error SendPackets(QTSS_RTPSendPackets_Params* inParams);
static QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams);
static void       DeleteFileSession(FileSession* inFileSession);
static UInt32   WriteSDPHeader(FILE* sdpFile, iovec *theSDPVec, SInt16 *ioVectorIndex, StrPtrLen *sdpHeader);
static void     BuildPrefBasedHeaders();

QTSS_Error QTSSFileModule_Main(void* inPrivateArgs)
{
    return _stublibrary_main(inPrivateArgs, QTSSFileModuleDispatch);
}

inline UInt16 GetPacketSequenceNumber(void * packetDataPtr)
{
    return ntohs( ((UInt16*)packetDataPtr)[1]);
}

inline UInt16 GetLastPacketSeqNum(QTSS_Object stream)
{

    UInt16 lastSeqNum = 0;
    UInt32  theLen = sizeof(lastSeqNum);
    (void) QTSS_GetValue(stream, sRTPStreamLastPacketSeqNumAttrID, 0, (void*)&lastSeqNum, &theLen);

    return lastSeqNum;
}


inline SInt32 GetLastSentSeqNumber(QTSS_Object stream)
{
    UInt16 lastSeqNum = 0;
    UInt32  theLen = sizeof(lastSeqNum);
    QTSS_Error error = QTSS_GetValue(stream, sRTPStreamLastSentPacketSeqNumAttrID, 0, (void*)&lastSeqNum, &theLen);
    if (error == QTSS_ValueNotFound) // first packet
    {    return -1;
    }

    return (SInt32)lastSeqNum; // return UInt16 seq num value or -1.
} 

inline void SetPacketSequenceNumber(UInt16 newSequenceNumber, void * packetDataPtr)
{
    ((UInt16*)packetDataPtr)[1] = htons(newSequenceNumber);
}


inline UInt32 GetPacketTimeStamp(void * packetDataPtr)
{
    return ntohl( ((UInt32*)packetDataPtr)[1]);
}

inline void SetPacketTimeStamp(UInt32 newTimeStamp, void * packetDataPtr)
{
    ((UInt32*)packetDataPtr)[1] = htonl(newTimeStamp);
}

inline UInt32 CalculatePauseTimeStamp(UInt32 timescale, SInt64 totalPauseTime, UInt32 currentTimeStamp)
{
    SInt64 pauseTime = (SInt64) ( (Float64) timescale * ( ( (Float64) totalPauseTime) / 1000.0));     
    UInt32 pauseTimeStamp = (UInt32) (pauseTime + currentTimeStamp);

    return pauseTimeStamp;
}

UInt32 SetPausetimeTimeStamp(FileSession *fileSessionPtr, QTSS_Object theRTPStream, UInt32 currentTimeStamp)
{ 
    if (fileSessionPtr->fTotalPauseTime == 0)
        return currentTimeStamp;

    UInt32 timeScale = 0;
    UInt32 theLen = sizeof(timeScale);
    (void) QTSS_GetValue(theRTPStream, qtssRTPStrTimescale, 0, (void*)&timeScale, &theLen);    
    if (theLen != sizeof(timeScale) || timeScale == 0)
        return currentTimeStamp;

    UInt32 pauseTimeStamp = CalculatePauseTimeStamp( timeScale,  fileSessionPtr->fTotalPauseTime, currentTimeStamp);
    if (pauseTimeStamp != currentTimeStamp)
        SetPacketTimeStamp(pauseTimeStamp, fileSessionPtr->fPacketStruct.packetData);

    return pauseTimeStamp;
}


UInt32 WriteSDPHeader(FILE* sdpFile, iovec *theSDPVec, SInt16 *ioVectorIndex, StrPtrLen *sdpHeader)
{

    Assert (ioVectorIndex != NULL);
    Assert (theSDPVec != NULL);
    Assert (sdpHeader != NULL);
    Assert (*ioVectorIndex < sNumSDPVectors); // if adding an sdp param you need to increase sNumSDPVectors
    
    SInt16 theIndex = *ioVectorIndex;
    *ioVectorIndex += 1;

    theSDPVec[theIndex].iov_base =  sdpHeader->Ptr;
    theSDPVec[theIndex].iov_len = sdpHeader->Len;
    
    if (sdpFile !=NULL)
        ::fwrite(theSDPVec[theIndex].iov_base,theSDPVec[theIndex].iov_len,sizeof(char),sdpFile);
    
    return theSDPVec[theIndex].iov_len;
}



QTSS_Error  QTSSFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock)
{
    switch (inRole)
    {
        case QTSS_Register_Role:
            return Register(&inParamBlock->regParams);
        case QTSS_Initialize_Role:
            return Initialize(&inParamBlock->initParams);
        case QTSS_RereadPrefs_Role:
            return RereadPrefs();
        case QTSS_RTSPRequest_Role:
            return ProcessRTSPRequest(&inParamBlock->rtspRequestParams);
        case QTSS_RTPSendPackets_Role:
            return SendPackets(&inParamBlock->rtpSendPacketsParams);
        case QTSS_ClientSessionClosing_Role:
            return DestroySession(&inParamBlock->clientSessionClosingParams);
    }
    return QTSS_NoErr;
}

QTSS_Error Register(QTSS_Register_Params* inParams)
{
    // Register for roles
    (void)QTSS_AddRole(QTSS_Initialize_Role);
    (void)QTSS_AddRole(QTSS_RTSPRequest_Role);
    (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role);
    (void)QTSS_AddRole(QTSS_RereadPrefs_Role);

    // Add text messages attributes
    static char*        sSeekToNonexistentTimeName  = "QTSSFileModuleSeekToNonExistentTime";
    static char*        sNoSDPFileFoundName         = "QTSSFileModuleNoSDPFileFound";
    static char*        sBadQTFileName              = "QTSSFileModuleBadQTFile";
    static char*        sFileIsNotHintedName        = "QTSSFileModuleFileIsNotHinted";
    static char*        sExpectedDigitFilenameName  = "QTSSFileModuleExpectedDigitFilename";
    static char*        sTrackDoesntExistName       = "QTSSFileModuleTrackDoesntExist";
    
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sSeekToNonexistentTimeName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sSeekToNonexistentTimeName, &sSeekToNonexistentTimeErr);

    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sNoSDPFileFoundName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sNoSDPFileFoundName, &sNoSDPFileFoundErr);

    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadQTFileName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadQTFileName, &sBadQTFileErr);

    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sFileIsNotHintedName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sFileIsNotHintedName, &sFileIsNotHintedErr);

    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sExpectedDigitFilenameName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sExpectedDigitFilenameName, &sExpectedDigitFilenameErr);

    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sTrackDoesntExistName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sTrackDoesntExistName, &sTrackDoesntExistErr);
    
    // Add an RTP session attribute for tracking FileSession objects
    static char*        sFileSessionName    = "QTSSFileModuleSession";
    (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sFileSessionName, NULL, qtssAttrDataTypeVoidPointer);
    (void)QTSS_IDForAttr(qtssClientSessionObjectType, sFileSessionName, &sFileSessionAttr);
    
    static char*        sFileSessionPlayCountName   = "QTSSFileModulePlayCount";
    (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sFileSessionPlayCountName, NULL, qtssAttrDataTypeUInt32);
    (void)QTSS_IDForAttr(qtssClientSessionObjectType, sFileSessionPlayCountName, &sFileSessionPlayCountAttrID);
    
    static char*        sFileSessionBufferDelayName = "QTSSFileModuleSDPBufferDelay";
    (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sFileSessionBufferDelayName, NULL, qtssAttrDataTypeFloat32);
    (void)QTSS_IDForAttr(qtssClientSessionObjectType, sFileSessionBufferDelayName, &sFileSessionBufferDelayAttrID);
    
     static char*        sRTPStreamLastSentPacketSeqNumName   = "QTSSFileModuleLastSentPacketSeqNum";
    (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sRTPStreamLastSentPacketSeqNumName, NULL, qtssAttrDataTypeUInt16);
    (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sRTPStreamLastSentPacketSeqNumName, &sRTPStreamLastSentPacketSeqNumAttrID);
   

    static char*        sRTPStreamLastPacketSeqNumName   = "QTSSFileModuleLastPacketSeqNum";
    (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sRTPStreamLastPacketSeqNumName, NULL, qtssAttrDataTypeUInt16);
    (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sRTPStreamLastPacketSeqNumName, &sRTPStreamLastPacketSeqNumAttrID);

    // Tell the server our name!
    static char* sModuleName = "QTSSFileModule";
    ::strcpy(inParams->outModuleName, sModuleName);

    return QTSS_NoErr;
}

QTSS_Error Initialize(QTSS_Initialize_Params* inParams)
{
    QTRTPFile::Initialize();
    QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream);

    sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule);
    sServerPrefs = inParams->inPrefs;
    sServer = inParams->inServer;
        
    // Read our preferences
    RereadPrefs();
    
    // Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN
    static QTSS_RTSPMethod sSupportedMethods[] = { qtssDescribeMethod, qtssSetupMethod, qtssTeardownMethod, qtssPlayMethod, qtssPauseMethod };
    QTSSModuleUtils::SetupSupportedMethods(inParams->inServer, sSupportedMethods, 5);

    return QTSS_NoErr;
}

void BuildPrefBasedHeaders()
{
    //build the sdp that looks like: \r\ne=http://streaming.apple.com\r\ne=qts@apple.com.
    static StrPtrLen sUHeader("u=");
    static StrPtrLen sEHeader("e=");
    static StrPtrLen sHTTP("http://");
    static StrPtrLen sAdmin("admin@");

    // Get the default DNS name of the server
    StrPtrLen theDefaultDNS;
    (void)QTSS_GetValuePtr(sServer, qtssSvrDefaultDNSName, 0, (void**)&theDefaultDNS.Ptr, &theDefaultDNS.Len);
    
    //-------- URL Header
    StrPtrLen sdpURL;
    sdpURL.Ptr = QTSSModuleUtils::GetStringAttribute(sPrefs, "sdp_url", "");
    sdpURL.Len = ::strlen(sdpURL.Ptr);
    
    UInt32 sdpURLLen = sdpURL.Len;
    if (sdpURLLen == 0)
        sdpURLLen = theDefaultDNS.Len + sHTTP.Len + 1;
        
    sURLHeader.Delete();
    sURLHeader.Len = sdpURLLen + 10;
    sURLHeader.Ptr = NEW char[sURLHeader.Len];
    StringFormatter urlFormatter(sURLHeader);
    urlFormatter.Put(sUHeader);
    if (sdpURL.Len == 0)
    {
        urlFormatter.Put(sHTTP);
        urlFormatter.Put(theDefaultDNS);
        urlFormatter.PutChar('/');
    }
    else
        urlFormatter.Put(sdpURL);
    
    sURLHeader.Len = (UInt32)urlFormatter.GetCurrentOffset();


    //-------- Email Header
    StrPtrLen adminEmail;
    adminEmail.Ptr = QTSSModuleUtils::GetStringAttribute(sPrefs, "admin_email", "");
    adminEmail.Len = ::strlen(adminEmail.Ptr);
    
    UInt32 adminEmailLen = adminEmail.Len;
    if (adminEmailLen == 0)
        adminEmailLen = theDefaultDNS.Len + sAdmin.Len; 
        
    sEmailHeader.Delete();
    sEmailHeader.Len = (sEHeader.Len * 2) + adminEmailLen + 10;
    sEmailHeader.Ptr = NEW char[sEmailHeader.Len];
    StringFormatter sdpFormatter(sEmailHeader);
    sdpFormatter.Put(sEHeader);
    
    if (adminEmail.Len == 0)
    {
        sdpFormatter.Put(sAdmin);
        sdpFormatter.Put(theDefaultDNS);
    }
    else
        sdpFormatter.Put(adminEmail);
        
    sEmailHeader.Len = (UInt32)sdpFormatter.GetCurrentOffset();
    
    
    sdpURL.Delete();
    adminEmail.Delete();
}

QTSS_Error RereadPrefs()
{
    
    QTSSModuleUtils::GetAttribute(sPrefs, "flow_control_probe_interval",    qtssAttrDataTypeUInt32,
                                &sFlowControlProbeInterval, &sDefaultFlowControlProbeInterval, sizeof(sFlowControlProbeInterval));

    QTSSModuleUtils::GetAttribute(sPrefs, "max_allowed_speed",  qtssAttrDataTypeFloat32,
                                &sMaxAllowedSpeed, &sDefaultMaxAllowedSpeed, sizeof(sMaxAllowedSpeed));
                                
// File Cache prefs     
                    
    sEnableSharedBuffers = true;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "enable_shared_file_buffers", qtssAttrDataTypeBool16, &sEnableSharedBuffers,  sizeof(sEnableSharedBuffers));

    sEnablePrivateBuffers = false;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "enable_private_file_buffers", qtssAttrDataTypeBool16, &sEnablePrivateBuffers, sizeof(sEnablePrivateBuffers));

    sSharedBufferInc = 8;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "num_shared_buffer_increase_per_session", qtssAttrDataTypeUInt32,&sSharedBufferInc, sizeof(sSharedBufferInc));
                            
    sSharedBufferUnitKSize = 32;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "shared_buffer_unit_k_size", qtssAttrDataTypeUInt32, &sSharedBufferUnitKSize, sizeof(sSharedBufferUnitKSize));

    sPrivateBufferUnitKSize = 32;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "private_buffer_unit_k_size", qtssAttrDataTypeUInt32, &sPrivateBufferUnitKSize, sizeof(sPrivateBufferUnitKSize));

    sSharedBufferUnitSize = 1;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "num_shared_buffer_units_per_buffer", qtssAttrDataTypeUInt32,&sSharedBufferUnitSize, sizeof(sSharedBufferUnitSize));

    sPrivateBufferUnitSize = 1;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "num_private_buffer_units_per_buffer", qtssAttrDataTypeUInt32,&sPrivateBufferUnitSize, sizeof(sPrivateBufferUnitSize));
                                
    sSharedBufferMaxUnits = 8;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "max_shared_buffer_units_per_buffer", qtssAttrDataTypeUInt32, &sSharedBufferMaxUnits, sizeof(sSharedBufferMaxUnits));
                                
    sPrivateBufferMaxUnits = 8;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "max_private_buffer_units_per_buffer", qtssAttrDataTypeUInt32, &sPrivateBufferMaxUnits, sizeof(sPrivateBufferMaxUnits));

    sAddClientBufferDelaySecs = 0;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "add_seconds_to_client_buffer_delay", qtssAttrDataTypeFloat32, &sAddClientBufferDelaySecs, sizeof(sAddClientBufferDelaySecs));

    sRecordMovieFileSDP = false;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "record_movie_file_sdp", qtssAttrDataTypeBool16, &sRecordMovieFileSDP, sizeof(sRecordMovieFileSDP));

    sEnableMovieFileSDP = false;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "enable_movie_file_sdp", qtssAttrDataTypeBool16, &sEnableMovieFileSDP, sizeof(sEnableMovieFileSDP));
    
    sPlayerCompatibility = true;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "enable_player_compatibility", qtssAttrDataTypeBool16, &sPlayerCompatibility, sizeof(sPlayerCompatibility));

    sAdjustMediaBandwidthPercent = 50;
    QTSSModuleUtils::GetIOAttribute(sPrefs, "compatibility_adjust_sdp_media_bandwidth_percent", qtssAttrDataTypeUInt32, &sAdjustMediaBandwidthPercent, sizeof(sAdjustMediaBandwidthPercent));

    if (sAdjustMediaBandwidthPercent > 100)
        sAdjustMediaBandwidthPercent = 100;
        
    if (sAdjustMediaBandwidthPercent < 1)
        sAdjustMediaBandwidthPercent = 1;
        
    BuildPrefBasedHeaders();
    
    return QTSS_NoErr;
}



QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParamBlock)
{
    QTSS_RTSPMethod* theMethod = NULL;
    UInt32 theMethodLen = 0;
    if ((QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqMethod, 0,
            (void**)&theMethod, &theMethodLen) != QTSS_NoErr) || (theMethodLen != sizeof(QTSS_RTSPMethod)))
    {
        Assert(0);
        return QTSS_RequestFailed;
    }
    
    QTSS_Error err = QTSS_NoErr;
    switch (*theMethod)
    {
        case qtssDescribeMethod:
            err = DoDescribe(inParamBlock);
            break;
        case qtssSetupMethod:
            err = DoSetup(inParamBlock);
            break;
        case qtssPlayMethod:
            err = DoPlay(inParamBlock);
            break;
        case qtssTeardownMethod:
            (void)QTSS_Teardown(inParamBlock->inClientSession);
            (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, 0);
            break;
        case qtssPauseMethod:
        {    (void)QTSS_Pause(inParamBlock->inClientSession);
            (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, 0);
                
            FileSession** theFile = NULL;
            UInt32 theLen = 0;
            QTSS_Error theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen);
            if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)))
                return QTSS_RequestFailed;

            (**theFile).fPaused = true;
            (**theFile).fLastPauseTime = OS::Milliseconds();

            break;
        }
        default:
            break;
    }
    if (err != QTSS_NoErr)
        (void)QTSS_Teardown(inParamBlock->inClientSession);

    return QTSS_NoErr;
}

Bool16 isSDP(QTSS_StandardRTSP_Params* inParamBlock)
{
	Bool16 sdpSuffix = false;
	
    char* path = NULL;
    UInt32 len = 0;
	QTSS_LockObject(inParamBlock->inRTSPRequest);
    QTSS_Error theErr = QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, (void**)&path, &len);
    Assert(theErr == QTSS_NoErr);
	
    if (sSDPSuffix.Len <= len)
	{
		StrPtrLen thePath(&path[len - sSDPSuffix.Len],sSDPSuffix.Len);
		sdpSuffix = thePath.Equal(sSDPSuffix);
	}
	
	QTSS_UnlockObject(inParamBlock->inRTSPRequest);
	
	return sdpSuffix;
}

QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParamBlock)
{
    if (isSDP(inParamBlock))
    {
        StrPtrLen pathStr;
        (void)QTSS_LockObject(inParamBlock->inRTSPRequest);
        (void)QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, (void**)&pathStr.Ptr, &pathStr.Len);
        QTSS_Error err = QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, qtssClientNotFound, sNoSDPFileFoundErr, &pathStr);
        (void)QTSS_UnlockObject(inParamBlock->inRTSPRequest);
        return err;
    }
    
    //
    // Get the FileSession for this DESCRIBE, if any.
    UInt32 theLen = sizeof(FileSession*);
    FileSession*    theFile = NULL;
    QTSS_Error      theErr = QTSS_NoErr;
    Bool16          pathEndsWithMOV = false;
    static StrPtrLen sMOVSuffix(".mov");
    SInt16 vectorIndex = 1;
    ResizeableStringFormatter theFullSDPBuffer(NULL,0);
    StrPtrLen bufferDelayStr;
    char tempBufferDelay[64];
    StrPtrLen theSDPData;
        
    (void)QTSS_GetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, (void*)&theFile, &theLen);
    // Generate the complete file path
    UInt32 thePathLen = 0;
    OSCharArrayDeleter thePath(QTSSModuleUtils::GetFullPath(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath,&thePathLen, &sSDPSuffix));
        
    //first locate the target movie
    thePath.GetObject()[thePathLen - sSDPSuffix.Len] = '\0';//truncate the .sdp added in the GetFullPath call
    StrPtrLen   requestPath(thePath.GetObject(), ::strlen(thePath.GetObject()));
    if (requestPath.Len > sMOVSuffix.Len )
    {   StrPtrLen endOfPath(&requestPath.Ptr[requestPath.Len -  sMOVSuffix.Len], sMOVSuffix.Len);
        if (endOfPath.Equal(sMOVSuffix)) // it is a .mov
        {   pathEndsWithMOV = true;
        }
    }
    
    if ( theFile != NULL )  
    {
        //
        // There is already a file for this session. This can happen if there are multiple DESCRIBES,
        // or a DESCRIBE has been issued with a Session ID, or some such thing.
        StrPtrLen   moviePath( theFile->fFile.GetMoviePath() );
        
        //
        // This describe is for a different file. Delete the old FileSession.
        if ( !requestPath.Equal( moviePath ) )
        {
            DeleteFileSession(theFile);
            theFile = NULL;
            
            // NULL out the attribute value, just in case.
            (void)QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile));
        }
    }

    if ( theFile == NULL )
    {   
        theErr = CreateQTRTPFile(inParamBlock, thePath.GetObject(), &theFile);
        if (theErr != QTSS_NoErr)
            return theErr;
    
        // Store this newly created file object in the RTP session.
        theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile));
    }
    
    //replace the sacred character we have trodden on in order to truncate the path.
    thePath.GetObject()[thePathLen - sSDPSuffix.Len] = sSDPSuffix.Ptr[0];

    iovec theSDPVec[sNumSDPVectors];//1 for the RTSP header, 6 for the sdp header, 1 for the sdp body
    ::memset(&theSDPVec[0], 0, sizeof(theSDPVec));
    
    if (sEnableMovieFileSDP)
    {
        // Check to see if there is an sdp file, if so, return that file instead
        // of the built-in sdp. ReadEntireFile allocates memory but if all goes well theSDPData will be managed by the File Session
        (void)QTSSModuleUtils::ReadEntireFile(thePath.GetObject(), &theSDPData);
    }
    OSCharArrayDeleter sdpDataDeleter(theSDPData.Ptr); // Just in case we fail we know to clean up. But we clear the deleter if we succeed.

    if (theSDPData.Len > 0)
    {   
        SDPContainer fileSDPContainer; 
        fileSDPContainer.SetSDPBuffer(&theSDPData);  
        if (!fileSDPContainer.IsSDPBufferValid())
        {    return QTSSModuleUtils::SendErrorResponseWithMessage(inParamBlock->inRTSPRequest, qtssUnsupportedMediaType, &sSDPNotValidMessage);
        }
    
        
        // Append the Last Modified header to be a good caching proxy citizen before sending the Describe
        (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader,
                                        theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen);
        (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader,
                                        kCacheControlHeader.Ptr, kCacheControlHeader.Len);

        //Now that we have the file data, send an appropriate describe
        //response to the client
        theSDPVec[1].iov_base = theSDPData.Ptr;
        theSDPVec[1].iov_len = theSDPData.Len;

        QTSSModuleUtils::SendDescribeResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession,
                                                                &theSDPVec[0], 3, theSDPData.Len);  
    }
    else
    {
        // Before generating the SDP and sending it, check to see if there is an If-Modified-Since
        // date. If there is, and the content hasn't been modified, then just return a 304 Not Modified
        QTSS_TimeVal* theTime = NULL;
        (void) QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqIfModSinceDate, 0, (void**)&theTime, &theLen);
        if ((theLen == sizeof(QTSS_TimeVal)) && (*theTime > 0))
        {
            // There is an If-Modified-Since header. Check it vs. the content.
            if (*theTime == theFile->fFile.GetQTFile()->GetModDate())
            {
                theErr = QTSS_SetValue( inParamBlock->inRTSPRequest, qtssRTSPReqStatusCode, 0,
                                        &kNotModifiedStatus, sizeof(kNotModifiedStatus) );
                Assert(theErr == QTSS_NoErr);
                // Because we are using this call to generate a 304 Not Modified response, we do not need
                // to pass in a RTP Stream
                theErr = QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, 0);
                Assert(theErr == QTSS_NoErr);
                return QTSS_NoErr;
            }
        }
        
        FILE* sdpFile = NULL;
        if (sRecordMovieFileSDP && pathEndsWithMOV) // don't auto create sdp for a non .mov file because it would look like a broadcast
        {   
            sdpFile = ::fopen(thePath.GetObject(),"r"); // see if there already is a .sdp for the movie
            if (sdpFile != NULL) // one already exists don't mess with it
            {   ::fclose(sdpFile);
                sdpFile = NULL;
            }
            else
                sdpFile = ::fopen(thePath.GetObject(),"w"); // create the .sdp
        }
        
        UInt32 totalSDPLength = 0;
        
        //Get filename 
        //StrPtrLen fileNameStr;
        //(void)QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, (void**)&fileNameStr.Ptr, (UInt32*)&fileNameStr.Len);
        char* fileNameStr = NULL;
        (void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, &fileNameStr);
        QTSSCharArrayDeleter fileNameStrDeleter(fileNameStr);
        	
        //Get IP addr
        StrPtrLen ipStr;
        (void)QTSS_GetValuePtr(inParamBlock->inRTSPSession, qtssRTSPSesLocalAddrStr, 0, (void**)&ipStr.Ptr, &ipStr.Len);


//      
// *** The order of sdp headers is specified and required by rfc 2327
//
// -------- version header 

        theFullSDPBuffer.Put(sVersionHeader);
        theFullSDPBuffer.Put(sEOL);
        
// -------- owner header

        const SInt16 sLineSize = 256;
        char ownerLine[sLineSize]="";
        ownerLine[sLineSize - 1] = 0;
        
        char *ipCstr = ipStr.GetAsCString();
        OSCharArrayDeleter ipDeleter(ipCstr);
        
        // the first number is the NTP time used for the session identifier (this changes for each request)
        // the second number is the NTP date time of when the file was modified (this changes when the file changes)
        qtss_sprintf(ownerLine, "o=StreamingServer %"_64BITARG_"d %"_64BITARG_"d IN IP4 %s", (SInt64) OS::UnixTime_Secs() + 2208988800LU, (SInt64) theFile->fFile.GetQTFile()->GetModDate(),ipCstr);
        Assert(ownerLine[sLineSize - 1] == 0);

        StrPtrLen ownerStr(ownerLine);
        theFullSDPBuffer.Put(ownerStr); 
        theFullSDPBuffer.Put(sEOL); 
        
// -------- session header

        theFullSDPBuffer.Put(sSessionNameHeader);
        theFullSDPBuffer.Put(fileNameStr);
        theFullSDPBuffer.Put(sEOL);
    
// -------- uri header

        theFullSDPBuffer.Put(sURLHeader);
        theFullSDPBuffer.Put(sEOL);

    
// -------- email header

        theFullSDPBuffer.Put(sEmailHeader);
        theFullSDPBuffer.Put(sEOL);

// -------- connection information header
        
        theFullSDPBuffer.Put(sConnectionHeader); 
        theFullSDPBuffer.Put(sEOL);

// -------- time header

        // t=0 0 is a permanent always available movie (doesn't ever change unless we change the code)
        theFullSDPBuffer.Put(sPermanentTimeHeader);
        theFullSDPBuffer.Put(sEOL);
        
// -------- control header

        theFullSDPBuffer.Put(sStaticControlHeader);
        theFullSDPBuffer.Put(sEOL);
        
                
// -------- add buffer delay

        if (sAddClientBufferDelaySecs > 0) // increase the client buffer delay by the preference amount.
        {
            Float32 bufferDelay = 3.0; // the client doesn't advertise it's default value so we guess.
            
            static StrPtrLen sBuffDelayStr("a=x-bufferdelay:");
        
            StrPtrLen delayStr;
            theSDPData.FindString(sBuffDelayStr, &delayStr);
            if (delayStr.Len > 0)
            {
                UInt32 offset = (delayStr.Ptr - theSDPData.Ptr) + delayStr.Len; // step past the string
                delayStr.Ptr = theSDPData.Ptr + offset;
                delayStr.Len = theSDPData.Len - offset;
                StringParser theBufferSecsParser(&delayStr);
                theBufferSecsParser.ConsumeWhitespace();
                bufferDelay = theBufferSecsParser.ConsumeFloat();
            }
            
            bufferDelay += sAddClientBufferDelaySecs;

           
            qtss_sprintf(tempBufferDelay, "a=x-bufferdelay:%.2f",bufferDelay);
            bufferDelayStr.Set(tempBufferDelay); 
                
            theFullSDPBuffer.Put(bufferDelayStr); 
            theFullSDPBuffer.Put(sEOL);
        }
        
 // -------- movie file sdp data

        //now append content-determined sdp ( cached in QTRTPFile )
        int sdpLen = 0;
        theSDPData.Ptr = theFile->fFile.GetSDPFile(&sdpLen);
        theSDPData.Len = sdpLen;

// ----------- Add the movie's sdp headers to our sdp headers
 
		theFullSDPBuffer.Put(theSDPData); 
        StrPtrLen fullSDPBuffSPL(theFullSDPBuffer.GetBufPtr(),theFullSDPBuffer.GetBytesWritten());

// ------------ Check the headers
        SDPContainer rawSDPContainer;
        rawSDPContainer.SetSDPBuffer( &fullSDPBuffSPL );  
        if (!rawSDPContainer.IsSDPBufferValid())
        {    return QTSSModuleUtils::SendErrorResponseWithMessage(inParamBlock->inRTSPRequest, qtssUnsupportedMediaType, &sSDPNotValidMessage);
        }
		
// ------------ reorder the sdp headers to make them proper.
        Float32 adjustMediaBandwidthPercent = 1.0;
        Bool16 adjustMediaBandwidth = false;
        if (sPlayerCompatibility )
            adjustMediaBandwidth = QTSSModuleUtils::HavePlayerProfile(inParamBlock,QTSSModuleUtils::kAdjustBandwidth);
		    
		if (adjustMediaBandwidth)
		    adjustMediaBandwidthPercent = (Float32) sAdjustMediaBandwidthPercent / 100.0;
		    
		SDPLineSorter sortedSDP(&rawSDPContainer,adjustMediaBandwidthPercent);
		StrPtrLen *theSessionHeadersPtr = sortedSDP.GetSessionHeaders();
		StrPtrLen *theMediaHeadersPtr = sortedSDP.GetMediaHeaders();
		
// ----------- write out the sdp

		totalSDPLength += ::WriteSDPHeader(sdpFile, theSDPVec, &vectorIndex, theSessionHeadersPtr);
        totalSDPLength += ::WriteSDPHeader(sdpFile, theSDPVec, &vectorIndex, theMediaHeadersPtr);
 

// -------- done with SDP processing
         
        if (sdpFile !=NULL)
            ::fclose(sdpFile);
            

        Assert(theSDPData.Len > 0);
        Assert(theSDPVec[2].iov_base != NULL);
        //ok, we have a filled out iovec. Let's send the response!
        
        // Append the Last Modified header to be a good caching proxy citizen before sending the Describe
        (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader,
                                        theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen);
        (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader,
                                        kCacheControlHeader.Ptr, kCacheControlHeader.Len);
        QTSSModuleUtils::SendDescribeResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession,
                                                                        &theSDPVec[0], vectorIndex, totalSDPLength);    
    }
    
    Assert(theSDPData.Ptr != NULL);
    Assert(theSDPData.Len > 0);
    
    //now parse the movie media sdp data. We need to do this in order to extract payload information.
    //The SDP parser object will not take responsibility of the memory (one exception... see above)
    theFile->fSDPSource.Parse(theSDPData.Ptr, theSDPData.Len);
    sdpDataDeleter.ClearObject(); // don't delete theSDPData, theFile has it now.
    
    return QTSS_NoErr;
}

QTSS_Error CreateQTRTPFile(QTSS_StandardRTSP_Params* inParamBlock, char* inPath, FileSession** outFile)
{   
    *outFile = NEW FileSession();
    QTRTPFile::ErrorCode theErr = (*outFile)->fFile.Initialize(inPath);
    if (theErr != QTRTPFile::errNoError)
    {
        delete *outFile;
        *outFile = NULL;

        char* thePathStr = NULL;
        (void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, &thePathStr);
        QTSSCharArrayDeleter thePathStrDeleter(thePathStr);
        StrPtrLen thePath(thePathStr);        
		
        if (theErr == QTRTPFile::errFileNotFound)
            return QTSSModuleUtils::SendErrorResponse(  inParamBlock->inRTSPRequest,
                                                        qtssClientNotFound,
                                                        sNoSDPFileFoundErr,&thePath);
        if (theErr == QTRTPFile::errInvalidQuickTimeFile)
            return QTSSModuleUtils::SendErrorResponse(  inParamBlock->inRTSPRequest,
                                                        qtssUnsupportedMediaType,
                                                        sBadQTFileErr,&thePath);
        if (theErr == QTRTPFile::errNoHintTracks)
            return QTSSModuleUtils::SendErrorResponse(  inParamBlock->inRTSPRequest,
                                                        qtssUnsupportedMediaType,
                                                        sFileIsNotHintedErr,&thePath);
        if (theErr == QTRTPFile::errInternalError)
            return QTSSModuleUtils::SendErrorResponse(  inParamBlock->inRTSPRequest,
                                                        qtssServerInternal,
                                                        sBadQTFileErr,&thePath);

        AssertV(0, theErr);
    }
    
    return QTSS_NoErr;
}


QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParamBlock)
{

    if (isSDP(inParamBlock))
    {
        StrPtrLen pathStr;
        (void)QTSS_LockObject(inParamBlock->inRTSPRequest);
        (void)QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, (void**)&pathStr.Ptr, &pathStr.Len);
        QTSS_Error err = QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, qtssClientNotFound, sNoSDPFileFoundErr, &pathStr);
        (void)QTSS_UnlockObject(inParamBlock->inRTSPRequest);
        return err;
    }
    
    //setup this track in the file object 
    FileSession* theFile = NULL;
    UInt32 theLen = sizeof(FileSession*);
    QTSS_Error theErr = QTSS_GetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, (void*)&theFile, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)))
    {
        char* theFullPath = NULL;
        //theErr = QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, (void**)&theFullPath, &theLen);
		theErr = QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPath);
        Assert(theErr == QTSS_NoErr);
        // This is possible, as clients are not required to send a DESCRIBE. If we haven't set
        // anything up yet, set everything up
        theErr = CreateQTRTPFile(inParamBlock, theFullPath, &theFile);
		QTSS_Delete(theFullPath);
        if (theErr != QTSS_NoErr)
            return theErr;

        int theSDPBodyLen = 0;
        char* theSDPData = theFile->fFile.GetSDPFile(&theSDPBodyLen);

        //now parse the sdp. We need to do this in order to extract payload information.
        //The SDP parser object will not take responsibility of the memory (one exception... see above)
        theFile->fSDPSource.Parse(theSDPData, theSDPBodyLen);

        // Store this newly created file object in the RTP session.
        theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile));
    }

    //unless there is a digit at the end of this path (representing trackID), don't
    //even bother with the request
    char* theDigitStr = NULL;
    (void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFileDigit, 0, &theDigitStr);
    QTSSCharArrayDeleter theDigitStrDeleter(theDigitStr);
	if (theDigitStr == NULL)
        return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest,
                                                    qtssClientBadRequest, sExpectedDigitFilenameErr);
	
    UInt32 theTrackID = ::strtol(theDigitStr, NULL, 10);
    
//    QTRTPFile::ErrorCode qtfileErr = theFile->fFile.AddTrack(theTrackID, false); //test for 3gpp monotonic wall clocktime and sequence
    QTRTPFile::ErrorCode qtfileErr = theFile->fFile.AddTrack(theTrackID, true);
    
    //if we get an error back, forward that error to the client
    if (qtfileErr == QTRTPFile::errTrackIDNotFound)
        return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest,
                                                    qtssClientNotFound, sTrackDoesntExistErr);
    else if (qtfileErr != QTRTPFile::errNoError)
        return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest,
                                                    qtssUnsupportedMediaType, sBadQTFileErr);

    // Before setting up this track, check to see if there is an If-Modified-Since
    // date. If there is, and the content hasn't been modified, then just return a 304 Not Modified
    QTSS_TimeVal* theTime = NULL;
    (void) QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqIfModSinceDate, 0, (void**)&theTime, &theLen);
    if ((theLen == sizeof(QTSS_TimeVal)) && (*theTime > 0))
    {
        // There is an If-Modified-Since header. Check it vs. the content.
        if (*theTime == theFile->fFile.GetQTFile()->GetModDate())
        {
            theErr = QTSS_SetValue( inParamBlock->inRTSPRequest, qtssRTSPReqStatusCode, 0,
                                            &kNotModifiedStatus, sizeof(kNotModifiedStatus) );
            Assert(theErr == QTSS_NoErr);
            // Because we are using this call to generate a 304 Not Modified response, we do not need
            // to pass in a RTP Stream
            theErr = QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, 0);
            Assert(theErr == QTSS_NoErr);
            return QTSS_NoErr;
        }
    }

    //find the payload for this track ID (if applicable)
    StrPtrLen* thePayload = NULL;
    UInt32 thePayloadType = qtssUnknownPayloadType;
    Float32 bufferDelay = (Float32) 3.0; // FIXME need a constant defined for 3.0 value. It is used multiple places

    for (UInt32 x = 0; x < theFile->fSDPSource.GetNumStreams(); x++)
    {
        SourceInfo::StreamInfo* theStreamInfo = theFile->fSDPSource.GetStreamInfo(x);
        if (theStreamInfo->fTrackID == theTrackID)
        {
            thePayload = &theStreamInfo->fPayloadName;
            thePayloadType = theStreamInfo->fPayloadType;
            bufferDelay = theStreamInfo->fBufferDelay;
            break;
        }   
    }
    //Create a new RTP stream           
    QTSS_RTPStreamObject newStream = NULL;
    theErr = QTSS_AddRTPStream(inParamBlock->inClientSession, inParamBlock->inRTSPRequest, &newStream, 0);
    if (theErr != QTSS_NoErr)
        return theErr;
    
    // Set the payload type, payload name & timescale of this track
    SInt32 theTimescale = theFile->fFile.GetTrackTimeScale(theTrackID);
    

    theErr = QTSS_SetValue(newStream, qtssRTPStrBufferDelayInSecs, 0, &bufferDelay, sizeof(bufferDelay));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadName, 0, thePayload->Ptr, thePayload->Len);
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadType, 0, &thePayloadType, sizeof(thePayloadType));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrTimescale, 0, &theTimescale, sizeof(theTimescale));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrTrackID, 0, &theTrackID, sizeof(theTrackID));
    Assert(theErr == QTSS_NoErr);
    
    // Set the number of quality levels. Allow up to 6
    static UInt32 sNumQualityLevels = 5;
    
    theErr = QTSS_SetValue(newStream, qtssRTPStrNumQualityLevels, 0, &sNumQualityLevels, sizeof(sNumQualityLevels));
    Assert(theErr == QTSS_NoErr);
    
    // Get the SSRC of this track
    UInt32* theTrackSSRC = NULL;
    UInt32 theTrackSSRCSize = 0;
    (void)QTSS_GetValuePtr(newStream, qtssRTPStrSSRC, 0, (void**)&theTrackSSRC, &theTrackSSRCSize);

    // The RTP stream should ALWAYS have an SSRC assuming QTSS_AddStream succeeded.
    Assert((theTrackSSRC != NULL) && (theTrackSSRCSize == sizeof(UInt32)));
    
    //give the file some info it needs.
    theFile->fFile.SetTrackSSRC(theTrackID, *theTrackSSRC);
    theFile->fFile.SetTrackCookies(theTrackID, newStream, thePayloadType);
    
    StrPtrLen theHeader;
    theErr = QTSS_GetValuePtr(inParamBlock->inRTSPHeaders, qtssXRTPMetaInfoHeader, 0, (void**)&theHeader.Ptr, &theHeader.Len);
    if (theErr == QTSS_NoErr)
    {
        //
        // If there is an x-RTP-Meta-Info header in the request, mirror that header in the
        // response. We will support any fields supported by the QTFileLib.
        RTPMetaInfoPacket::FieldID* theFields = NEW RTPMetaInfoPacket::FieldID[RTPMetaInfoPacket::kNumFields];
        ::memcpy(theFields, QTRTPFile::GetSupportedRTPMetaInfoFields(), sizeof(RTPMetaInfoPacket::FieldID) * RTPMetaInfoPacket::kNumFields);

        //
        // This function does the work of appending the response header based on the
        // fields we support and the requested fields.
        theErr = QTSSModuleUtils::AppendRTPMetaInfoHeader(inParamBlock->inRTSPRequest, &theHeader, theFields);

        //
        // This returns QTSS_NoErr only if there are some valid, useful fields
        Bool16 isVideo = false;
        if (thePayloadType == qtssVideoPayloadType)
            isVideo = true;
        if (theErr == QTSS_NoErr)
            theFile->fFile.SetTrackRTPMetaInfo(theTrackID, theFields, isVideo);
    }
    
    //
    // Our array has now been updated to reflect the fields requested by the client.
    //send the setup response
    (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader,
                                theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen);
    (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader,
                                kCacheControlHeader.Ptr, kCacheControlHeader.Len);
    theErr = QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, newStream, 0);
    Assert(theErr == QTSS_NoErr);
    return QTSS_NoErr;
}



QTSS_Error SetupCacheBuffers(QTSS_StandardRTSP_Params* inParamBlock, FileSession** theFile)
{
    
    UInt32 playCount = 0;
    UInt32 theLen = sizeof(playCount);
    QTSS_Error theErr = QTSS_GetValue(inParamBlock->inClientSession, sFileSessionPlayCountAttrID, 0, (void*)&playCount, &theLen);
    if ( (theErr != QTSS_NoErr) || (theLen != sizeof(playCount)) )
    {
        playCount = 1;
        theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionPlayCountAttrID, 0, &playCount, sizeof(playCount));
        if (theErr != QTSS_NoErr)
            return QTSS_RequestFailed;
    }
    
    if (sEnableSharedBuffers && playCount == 1) // increments num buffers after initialization so do only once per session
        (*theFile)->fFile.AllocateSharedBuffers(sSharedBufferUnitKSize, sSharedBufferInc, sSharedBufferUnitSize,sSharedBufferMaxUnits);
    
    if (sEnablePrivateBuffers) // reinitializes buffers to current location so do every time 
        (*theFile)->fFile.AllocatePrivateBuffers(sSharedBufferUnitKSize, sPrivateBufferUnitSize, sPrivateBufferMaxUnits);

    playCount ++;
    theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionPlayCountAttrID, 0, &playCount, sizeof(playCount));
    if (theErr != QTSS_NoErr)
        return QTSS_RequestFailed;  
        
    return theErr;

}

QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParamBlock)
{
    QTRTPFile::ErrorCode qtFileErr = QTRTPFile::errNoError;

    if (isSDP(inParamBlock))
    {
        StrPtrLen pathStr;
        (void)QTSS_LockObject(inParamBlock->inRTSPRequest);
        (void)QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, (void**)&pathStr.Ptr, &pathStr.Len);
        QTSS_Error err = QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, qtssClientNotFound, sNoSDPFileFoundErr, &pathStr);
        (void)QTSS_UnlockObject(inParamBlock->inRTSPRequest);
        return err;
    }

    FileSession** theFile = NULL;
    UInt32 theLen = 0;
    QTSS_Error theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)))
        return QTSS_RequestFailed;

    theErr = SetupCacheBuffers(inParamBlock, theFile);  
    if (theErr != QTSS_NoErr)
        return theErr;
    

    // Set the default quality before playing.
    QTRTPFile::RTPTrackListEntry* thePacketTrack;
    for (UInt32 x = 0; x < (*theFile)->fSDPSource.GetNumStreams(); x++)
    {
         SourceInfo::StreamInfo* theStreamInfo = (*theFile)->fSDPSource.GetStreamInfo(x);
         if (!(*theFile)->fFile.FindTrackEntry(theStreamInfo->fTrackID,&thePacketTrack))
            break;
         (*theFile)->fFile.SetTrackQualityLevel(thePacketTrack, QTRTPFile::kAllPackets);
    }


    // How much are we going to tell the client to back up?
    Float32 theBackupTime = 0;

    char* thePacketRangeHeader = NULL;
    theErr = QTSS_GetValuePtr(inParamBlock->inRTSPHeaders, qtssXPacketRangeHeader, 0, (void**)&thePacketRangeHeader, &theLen);
    if (theErr == QTSS_NoErr)
    {
        StrPtrLen theRangeHdrPtr(thePacketRangeHeader, theLen);
        StringParser theRangeParser(&theRangeHdrPtr);
        
        theRangeParser.ConsumeUntil(NULL, StringParser::sDigitMask);
        UInt64 theStartPN = theRangeParser.ConsumeInteger();
        
        theRangeParser.ConsumeUntil(NULL, StringParser::sDigitMask);
        (*theFile)->fStopPN = theRangeParser.ConsumeInteger();

        theRangeParser.ConsumeUntil(NULL, StringParser::sDigitMask);
        (*theFile)->fStopTrackID = theRangeParser.ConsumeInteger();

        qtFileErr = (*theFile)->fFile.SeekToPacketNumber((*theFile)->fStopTrackID, theStartPN);
        (*theFile)->fStartTime = (*theFile)->fFile.GetRequestedSeekTime();
    }
    else
    {
        Float64* theStartTimeP = 0;
        Float64 currentTime = 0;
        theErr = QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqStartTime, 0, (void**)&theStartTimeP, &theLen);
        if ((theErr != QTSS_NoErr) || (theLen != sizeof(Float64)))
        {   // No start time so just start at the last packet ready to send
            // This packet could be somewhere out in the middle of the file.
             currentTime =  (*theFile)->fFile.GetFirstPacketTransmitTime(); 
             theStartTimeP = &currentTime;  
        }    

                               
        Float32* theMaxBackupTime = NULL;
        theErr = QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqPrebufferMaxTime, 0, (void**)&theMaxBackupTime, &theLen);
        Assert(theMaxBackupTime != NULL);
    
        if (*theMaxBackupTime == -1)
        {
            //
            // If this is an old client (doesn't send the x-prebuffer header) or an mp4 client, 
            // - don't back up to a key frame, and do not adjust the buffer time
            qtFileErr = (*theFile)->fFile.Seek(*theStartTimeP, 0);
            (*theFile)->fStartTime = *theStartTimeP;
            
            //
            // burst out -transmit time packets
            (*theFile)->fAllowNegativeTTs = false;
        }
        else
        {
            qtFileErr = (*theFile)->fFile.Seek(*theStartTimeP, *theMaxBackupTime);
            Float64 theFirstPacketTransmitTime = (*theFile)->fFile.GetFirstPacketTransmitTime();
            theBackupTime = (Float32) ( *theStartTimeP - theFirstPacketTransmitTime);
            
            //
            // For oddly authored movies, there are situations in which the packet
            // transmit time can be before the sample time. In that case, the backup
            // time may exceed the max backup time. In that case, just make the backup
            // time the max backup time.
            if (theBackupTime > *theMaxBackupTime)
                theBackupTime = *theMaxBackupTime;
            //
            // If client specifies that it can do extra buffering (new client), use the first
            // packet transmit time as the start time for this play burst. We don't need to
            // burst any packets because the client can do the extra buffering
			Bool16* overBufferEnabledPtr = NULL;
			theLen = 0;
			theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesOverBufferEnabled, 0, (void**)&overBufferEnabledPtr, &theLen);	
			if ((theErr == QTSS_NoErr) && (theLen == sizeof(Bool16)) && *overBufferEnabledPtr)
				(*theFile)->fStartTime = *theStartTimeP;
			else
                (*theFile)->fStartTime = *theStartTimeP - theBackupTime;            

            (*theFile)->fAllowNegativeTTs = true;
        }
    }
    
    if (qtFileErr == QTRTPFile::errCallAgain)
    {
        //
        // If we are doing RTP-Meta-Info stuff, we might be asked to get called again here.
        // This is simply because seeking might be a long operation and we don't want to
        // monopolize the CPU, but there is no other reason to wait, so just set a timeout of 0
        theErr = QTSS_SetIdleTimer(1);
        Assert(theErr == QTSS_NoErr);
        return theErr;
    }
    else if (qtFileErr != QTRTPFile::errNoError)
        return QTSSModuleUtils::SendErrorResponse(  inParamBlock->inRTSPRequest,
                                                    qtssClientBadRequest, sSeekToNonexistentTimeErr);
                                                        
    //make sure to clear the next packet the server would have sent!
    (*theFile)->fPacketStruct.packetData = NULL;
    
    // Set the movie duration and size parameters
    Float64 movieDuration = (*theFile)->fFile.GetMovieDuration();
    (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieDurationInSecs, 0, &movieDuration, sizeof(movieDuration));
    
    UInt64 movieSize = (*theFile)->fFile.GetAddedTracksRTPBytes();
    (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieSizeInBytes, 0, &movieSize, sizeof(movieSize));
    
    UInt32 bitsPerSecond =  (*theFile)->fFile.GetBytesPerSecond() * 8;
    (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieAverageBitRate, 0, &bitsPerSecond, sizeof(bitsPerSecond));

    (**theFile).fPaused = false;
    if ((**theFile).fLastPauseTime > 0)
        (**theFile).fTotalPauseTime += OS::Milliseconds() - (**theFile).fLastPauseTime;

    //
    // For the purposes of the speed header, check to make sure all tracks are
    // over a reliable transport
    Bool16 allTracksReliable = true;
    
    // Set the timestamp & sequence number parameters for each track.
    QTSS_RTPStreamObject* theRef = NULL;
    for (   UInt32 theStreamIndex = 0;
            QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesStreamObjects, theStreamIndex, (void**)&theRef, &theLen) == QTSS_NoErr;
            theStreamIndex++)
    {
        UInt32* theTrackID = NULL;
        theErr = QTSS_GetValuePtr(*theRef, qtssRTPStrTrackID, 0, (void**)&theTrackID, &theLen);
        Assert(theErr == QTSS_NoErr);
        Assert(theTrackID != NULL);
        Assert(theLen == sizeof(UInt32));
        
        UInt16 theSeqNum = 0;
        UInt32 theTimestamp = (*theFile)->fFile.GetSeekTimestamp(*theTrackID); // this is the base timestamp need to add in paused time.        

        Assert(theRef != NULL);

        UInt32* theTimescale = NULL;
        QTSS_GetValuePtr(*theRef, qtssRTPStrTimescale, 0,  (void**)&theTimescale, &theLen);
        if (theLen != 0) // adjust the timestamps to reflect paused time else leave it alone we can't calculate the timestamp without a timescale.
        {
            UInt32 pauseTimeStamp = CalculatePauseTimeStamp( *theTimescale,  (*theFile)->fTotalPauseTime, (UInt32) theTimestamp);
            if (pauseTimeStamp != theTimestamp)
                  theTimestamp = pauseTimeStamp;
       }

       
	theSeqNum = (*theFile)->fFile.GetNextTrackSequenceNumber(*theTrackID);       
        theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstSeqNumber, 0, &theSeqNum, sizeof(theSeqNum));
        Assert(theErr == QTSS_NoErr);
        theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstTimestamp, 0, &theTimestamp, sizeof(theTimestamp));
        Assert(theErr == QTSS_NoErr);

        if (allTracksReliable)
        {
            QTSS_RTPTransportType theTransportType = qtssRTPTransportTypeUDP;
            theLen = sizeof(theTransportType);
            theErr = QTSS_GetValue(*theRef, qtssRTPStrTransportType, 0, &theTransportType, &theLen);
            Assert(theErr == QTSS_NoErr);
            
            if (theTransportType == qtssRTPTransportTypeUDP)
                allTracksReliable = false;
        }
    }
    
    //Tell the QTRTPFile whether repeat packets are wanted based on the transport
    // we don't care if it doesn't set (i.e. this is a meta info session)
     (void)  (*theFile)->fFile.SetDropRepeatPackets(allTracksReliable);// if alltracks are reliable then drop repeat packets.
        
    //Tell the server to start playing this movie. We do want it to send RTCP SRs, but
    //we DON'T want it to write the RTP header
    theErr = QTSS_Play(inParamBlock->inClientSession, inParamBlock->inRTSPRequest, qtssPlayFlagsSendRTCP);
    if (theErr != QTSS_NoErr)
        return theErr;

    SInt64* thePlayTime = 0;
    theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesPlayTimeInMsec, 0, (void**)&thePlayTime, &theLen);
    Assert(theErr == QTSS_NoErr);
    Assert(thePlayTime != NULL);
    Assert(theLen == sizeof(SInt64));
    (*theFile)->fAdjustedPlayTime = *thePlayTime - ((SInt64)((*theFile)->fStartTime * 1000));
    
    //
    // This module supports the Speed header if the client wants the stream faster than normal.
    Float32 theSpeed = 1;
    theLen = sizeof(theSpeed);
    theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqSpeed, 0, &theSpeed, &theLen);
    Assert(theErr != QTSS_BadArgument);
    Assert(theErr != QTSS_NotEnoughSpace);
    
    if (theErr == QTSS_NoErr)
    {
        if (theSpeed > sMaxAllowedSpeed)
            theSpeed = sMaxAllowedSpeed;
        if ((theSpeed <= 0) || (!allTracksReliable))
            theSpeed = 1;
    }
    
    (*theFile)->fSpeed = theSpeed;
    
    if (theSpeed != 1)
    {
        //
        // If our speed is not 1, append the RTSP speed header in the response
        char speedBuf[32];
        qtss_sprintf(speedBuf, "%10.5f", theSpeed);
        StrPtrLen speedBufPtr(speedBuf);
        (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssSpeedHeader,
                                    speedBufPtr.Ptr, speedBufPtr.Len);
    }
    
    //
    // Record the requested stop time, if there is one
    (*theFile)->fStopTime = -1;
    theLen = sizeof((*theFile)->fStopTime);
    theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqStopTime, 0, &(*theFile)->fStopTime, &theLen);
    
    //
    // Append x-Prebuffer header if provided & nonzero prebuffer needed
    if (theBackupTime > 0)
    {
        char prebufferBuf[32];
        qtss_sprintf(prebufferBuf, "time=%.5f", theBackupTime);
        StrPtrLen backupTimePtr(prebufferBuf);
        (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssXPreBufferHeader,
                                    backupTimePtr.Ptr, backupTimePtr.Len);
    
    }

    {
        char rangeHeader[64];
        if (-1 == (*theFile)->fStopTime)
           (*theFile)->fStopTime = (*theFile)->fFile.GetMovieDuration();

        qtss_snprintf(rangeHeader,sizeof(rangeHeader) -1, "npt=%.5f-%.5f", (*theFile)->fStartTime, (*theFile)->fStopTime);
        rangeHeader[sizeof(rangeHeader) -1] = 0;
        
        StrPtrLen rangeHeaderPtr(rangeHeader);
        (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssRangeHeader,
                                    rangeHeaderPtr.Ptr, rangeHeaderPtr.Len);
    
    }
    (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, qtssPlayRespWriteTrackInfo);

    return QTSS_NoErr;
}

QTSS_Error SendPackets(QTSS_RTPSendPackets_Params* inParams)
{
    static const UInt32 kQualityCheckIntervalInMsec = 250;  // v331=v107

    FileSession** theFile = NULL;
    UInt32 theLen = 0;
    QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen);
    Assert(theErr == QTSS_NoErr);
    Assert(theLen == sizeof(FileSession*));
    bool isBeginningOfWriteBurst = true;
    QTSS_Object theStream = NULL;

    QTRTPFile::RTPTrackListEntry* theLastPacketTrack = (*theFile)->fFile.GetLastPacketTrack();
    
    while (true)
    {   
        if ((*theFile)->fPacketStruct.packetData == NULL)
        {
            Float64 theTransmitTime = (*theFile)->fFile.GetNextPacket((char**)&(*theFile)->fPacketStruct.packetData, &(*theFile)->fNextPacketLen);
            if ( QTRTPFile::errNoError != (*theFile)->fFile.Error() )
            {
                QTSS_CliSesTeardownReason reason = qtssCliSesTearDownUnsupportedMedia;
                (void) QTSS_SetValue(inParams->inClientSession, qtssCliTeardownReason, 0, &reason, sizeof(reason));
                (void)QTSS_Teardown(inParams->inClientSession);
                return QTSS_RequestFailed;
            }
            
            theLastPacketTrack = (*theFile)->fFile.GetLastPacketTrack();

			if (theLastPacketTrack == NULL)
				break;

            theStream = (QTSS_Object)theLastPacketTrack->Cookie1;
			Assert(theStream != NULL);
			if (theStream == NULL)
				return 0;


            //
            // Check to see if we should stop playing now
            
            if (((*theFile)->fStopTime != -1) && (theTransmitTime > (*theFile)->fStopTime))
            {
                // We should indeed stop playing
                (void)QTSS_Pause(inParams->inClientSession);
                inParams->outNextPacketTime = qtssDontCallSendPacketsAgain;

                (**theFile).fPaused = true;
                (**theFile).fLastPauseTime = OS::Milliseconds();
  
                return QTSS_NoErr;
            }
            if (((*theFile)->fStopTrackID != 0) && ((*theFile)->fStopTrackID == theLastPacketTrack->TrackID) && (theLastPacketTrack->HTCB->fCurrentPacketNumber > (*theFile)->fStopPN))
            {
                // We should indeed stop playing
                (void)QTSS_Pause(inParams->inClientSession);
                inParams->outNextPacketTime = qtssDontCallSendPacketsAgain;

                (**theFile).fPaused = true;
                (**theFile).fLastPauseTime = OS::Milliseconds();

                return QTSS_NoErr;
            }
            
            //
            // Find out what our play speed is. Send packets out at the specified rate,
            // and do so by altering the transmit time of the packet based on the Speed rate.
            Float64 theOffsetFromStartTime = theTransmitTime - (*theFile)->fStartTime;
            theTransmitTime = (*theFile)->fStartTime + (theOffsetFromStartTime / (*theFile)->fSpeed);
            
            //
            // correct for first packet xmit times that are < 0
            if (( theTransmitTime < 0.0 ) && ( !(*theFile)->fAllowNegativeTTs ))
                theTransmitTime = 0.0;
            
            (*theFile)->fPacketStruct.packetTransmitTime = (*theFile)->fAdjustedPlayTime + ((SInt64)(theTransmitTime * 1000));

        }
        
        //We are done playing all streams!
        if ((*theFile)->fPacketStruct.packetData == NULL)
        {
            //TODO not quite good to the last drop -- we -really- should guarantee this, also reflector
            // a write of 0 len to QTSS_Write will flush any buffered data if we're sending over tcp
            //(void)QTSS_Write((QTSS_Object)(*theFile)->fFile.GetLastPacketTrack()->Cookie1, NULL, 0, NULL, qtssWriteFlagsIsRTP);
            inParams->outNextPacketTime = qtssDontCallSendPacketsAgain;
            return QTSS_NoErr;
        }
        
        //we have a packet that needs to be sent now
        Assert(theLastPacketTrack != NULL);

        //If the stream is video, we need to make sure that QTRTPFile knows what quality level we're at
        if (inParams->inCurrentTime > ((*theFile)->fLastQualityCheck + kQualityCheckIntervalInMsec))
        {
            QTSS_RTPPayloadType thePayloadType = (QTSS_RTPPayloadType)theLastPacketTrack->Cookie2;
            if (thePayloadType == qtssVideoPayloadType)
            {
                (*theFile)->fLastQualityCheck = inParams->inCurrentTime;

				theStream = (QTSS_Object)theLastPacketTrack->Cookie1;
				Assert(theStream != NULL);
				if (theStream == NULL)
					return 0;


                // Get the current quality level in the stream, and this stream's TrackID.
                UInt32* theQualityLevel = 0;
                theErr = QTSS_GetValuePtr(theStream, qtssRTPStrQualityLevel, 0, (void**)&theQualityLevel, &theLen);
                Assert(theErr == QTSS_NoErr);
                Assert(theQualityLevel != NULL);
                Assert(theLen == sizeof(UInt32));
        
                (*theFile)->fFile.SetTrackQualityLevel(theLastPacketTrack, *theQualityLevel);
            }
        }

        // Send the packet!
        QTSS_WriteFlags theFlags = qtssWriteFlagsIsRTP;
        if (isBeginningOfWriteBurst)
            theFlags |= qtssWriteFlagsWriteBurstBegin;

        theStream = (QTSS_Object)theLastPacketTrack->Cookie1;
		Assert(theStream != NULL);
		if (theStream == NULL)
			return 0;

        //adjust the timestamp so it reflects paused time.
        void* packetDataPtr =  (*theFile)->fPacketStruct.packetData;
        UInt32 currentTimeStamp = GetPacketTimeStamp(packetDataPtr);
        UInt32 pauseTimeStamp = SetPausetimeTimeStamp(*theFile, theStream, currentTimeStamp);
        
  		UInt16 curSeqNum = GetPacketSequenceNumber(theStream);
        (void) QTSS_SetValue(theStream, sRTPStreamLastPacketSeqNumAttrID, 0, &curSeqNum, sizeof(curSeqNum));
 

		theErr = QTSS_Write(theStream, &(*theFile)->fPacketStruct, (*theFile)->fNextPacketLen, NULL, theFlags);
        
        isBeginningOfWriteBurst = false;
        
        if ( theErr == QTSS_WouldBlock )
        { 

            if (currentTimeStamp != pauseTimeStamp) // reset the packet time stamp so we adjust it again when we really do send it
                SetPacketTimeStamp(currentTimeStamp, packetDataPtr);
           
            //
            // In the case of a QTSS_WouldBlock error, the packetTransmitTime field of the packet struct will be set to
            // the time to wakeup, or -1 if not known.
            // If the time to wakeup is not given by the server, just give a fixed guess interval
            if ((*theFile)->fPacketStruct.suggestedWakeupTime == -1)
                inParams->outNextPacketTime = sFlowControlProbeInterval;    // for buffering, try me again in # MSec
            else
            {
                Assert((*theFile)->fPacketStruct.suggestedWakeupTime > inParams->inCurrentTime);
                inParams->outNextPacketTime = (*theFile)->fPacketStruct.suggestedWakeupTime - inParams->inCurrentTime;
            }
            
            //qtss_printf("Call again: %qd\n", inParams->outNextPacketTime);
                
            return QTSS_NoErr;
        }
        else
        {

          (void) QTSS_SetValue(theStream, sRTPStreamLastSentPacketSeqNumAttrID, 0, &curSeqNum, sizeof(curSeqNum));
          (*theFile)->fPacketStruct.packetData = NULL;
        }
    }
    
    return QTSS_NoErr;
}

QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams)
{
    FileSession** theFile = NULL;
    UInt32 theLen = 0;
    QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)) || (theFile == NULL))
        return QTSS_RequestFailed;

    //
    // Tell the ClientSession how many samples we skipped because of stream thinning
    UInt32 theNumSkippedSamples = (*theFile)->fFile.GetNumSkippedSamples();
    (void)QTSS_SetValue(inParams->inClientSession, qtssCliSesFramesSkipped, 0, &theNumSkippedSamples, sizeof(theNumSkippedSamples));
    
    DeleteFileSession(*theFile);
    return QTSS_NoErr;
}

void    DeleteFileSession(FileSession* inFileSession)
{   
    delete inFileSession;
}
