csgo-2018-source/engine/demostreamhttp.cpp
2021-07-24 21:11:47 -07:00

658 lines
20 KiB
C++

//========= Copyright (c), Valve Corporation, All rights reserved. ============//
#include "client_pch.h"
#include "demostreamhttp.h"
#include "cl_steamauth.h"
#include "tier1/keyvaluesjson.h"
#include "tier0/memalloc.h"
#include "cl_demo.h"
#include "sv_steamauth.h"
#include "engine_gcmessages.pb.h"
static ISteamHTTP *s_pSteamHTTP = NULL;
ConVar demo_debug( "demo_debug", "0", 0, "Demo debug info." );
ConVar tv_playcast_origin_auth( "tv_playcast_origin_auth", "", FCVAR_RELEASE | FCVAR_HIDDEN, "Get request X-Origin-Auth string" );
ConVar tv_playcast_max_rcvage( "tv_playcast_max_rcvage", "15", FCVAR_RELEASE | FCVAR_HIDDEN );
ConVar tv_playcast_max_rtdelay( "tv_playcast_max_rtdelay", "55", FCVAR_RELEASE | FCVAR_HIDDEN );
ConVar tv_playcast_delay_prediction( "tv_playcast_delay_prediction", "1", FCVAR_RELEASE );
CDemoStreamHttp::CDemoStreamHttp() :
m_nState( STATE_IDLE ),
m_pStreamSignup( NULL ),
m_pClient( NULL ),
m_bSyncFromGc( false ),
m_flBroadcastKeyframeInterval( 3 )
{
V_memset( &m_SyncResponse, 0, sizeof( m_SyncResponse ) );
m_dSyncTimeoutEnd = -1;
}
void CDemoStreamHttp::StartStreaming( const char *pUrl, SyncParams_t syncParams )
{
if ( !PrepareForStreaming( pUrl ) )
return;
m_SyncParams = syncParams;
SendSync( );
}
bool CDemoStreamHttp::PrepareForStreaming( const char * pUrl )
{
StopStreaming();
if ( pUrl[ 0 ] == 'g' && pUrl[ 1 ] == 'c' && pUrl[ 2 ] == '-' )
{
m_Url = pUrl + 3;
m_bSyncFromGc = true;
}
else
{
m_Url = pUrl;
m_bSyncFromGc = false;
}
m_Url.StripTrailingSlash();
#ifndef DEDICATED
s_pSteamHTTP = Steam3Client().SteamHTTP();
#endif
if ( !s_pSteamHTTP )
s_pSteamHTTP = Steam3Server().SteamHTTP();
if ( !s_pSteamHTTP )
{
DevMsg( "Cannot get Steam HTTP interface\n" );
return false ;
}
DevMsg( "Broadcast: Synchronizing stream\n" );
m_nState = STATE_SYNC;
return true;
}
// this is only called in special cases for debugging, to play back stale contents
void CDemoStreamHttp::StartStreamingCached( const char *pUrl, int nFragment)
{
if ( !PrepareForStreaming( pUrl ) )
return;
m_nState = STATE_START;
// guess parameters of the stream
int nStartTick = 1;
m_nDemoProtocol = 4; // DEMO_PROTOCOL == 4 is where I started writing this
int nSignupFragment = 0;
m_SyncParams.m_nStartFragment = 0;
m_SyncResponse.flTicksPerSecond = 128;
m_SyncResponse.flKeyframeInterval = 3;
m_SyncResponse.flRealTimeDelay = 0;
m_SyncResponse.flReceiveAge = 0;
m_SyncResponse.nFragment = nFragment;
m_SyncResponse.nSignupFragment = nSignupFragment;
m_SyncResponse.nStartTick = nStartTick;
m_SyncResponse.dPlatTimeReceived = Plat_FloatTime();
SendGet( CFmtStr( "/%d/start", nSignupFragment ), new CStartRequest( ) );
BeginBuffering( nFragment );
}
void CDemoStreamHttp::SendSync( int nResync )
{
Assert( m_nState == STATE_SYNC );
#ifndef DEDICATED
if ( m_bSyncFromGc )
{
GotvHttpStreamId_t params = GetStreamId( m_Url );
DevMsg( "Requesting sync from GC, start fragment %d match id %llu instance %d\n", m_SyncParams.m_nStartFragment, params.m_nMatchId, params.m_nInstanceId );
CEngineGotvSyncPacket msg;
msg.set_match_id( params.m_nMatchId );
msg.set_instance_id( params.m_nInstanceId );
if ( m_SyncParams.m_nStartFragment > 0 )
{
msg.set_currentfragment( m_SyncParams.m_nStartFragment );
}
g_ClientDLL->EngineGotvSyncPacket( &msg );
m_dSyncTimeoutEnd = Plat_FloatTime() + 10;
}
else
#endif
{
char request[ 128 ];
m_SyncParams.PrintSyncRequest( request, sizeof( request ) );
DevMsg( "Requesting sync from relay %s\n", request );
SendGet( request, new CSyncRequest( m_SyncParams, nResync ) );
}
}
void CDemoStreamHttp::Resync( )
{
m_nState = STATE_SYNC;
SendSync( 1 );
}
void CDemoStreamHttp::Update()
{
if ( m_nState == STATE_SYNC && m_bSyncFromGc && m_dSyncTimeoutEnd > 0 && m_dSyncTimeoutEnd < Plat_FloatTime() )
{
StopStreaming();
}
if ( m_nState == STATE_RANDOM_WAIT_AND_SYNC && m_bSyncFromGc && m_dSyncTimeoutEnd > 0 && m_dSyncTimeoutEnd < Plat_FloatTime() )
{
m_nState = STATE_SYNC;
SendSync( 0 );
}
}
// result from "/start" request
void CDemoStreamHttp::OnStart( HTTPRequestHandle hRequest )
{
m_pStreamSignup = MakeBuffer( hRequest );
m_nStreamSignupFragment = m_SyncResponse.nSignupFragment;
if ( !m_pStreamSignup )
{
DevMsg( "Broadcast failed to start: cannot retrieve startup packet data\n" );
StopStreaming();
return;
}
DevMsg( "Received signup fragment %d\n", m_SyncResponse.nSignupFragment );
if ( m_pClient )
{
m_pClient->OnDemoStreamStart( GetStreamStartReference(), 0 );
}
}
void CDemoStreamHttp::OnFragmentRequestSuccess( HTTPRequestHandle hRequest, int nFragment, FragmentTypeEnum_t nType )
{
Fragment_t &fragment = Fragment( nFragment );
fragment.ClearStreaming( nType );
if ( Buffer_t *pBuf = MakeBuffer( hRequest ) )
{
fragment.SetField( nType, pBuf );
}
else
{
DevMsg( "Broadcast playback failed to retrieve %s frame of fragment %d", AsString( nType ), nFragment ); // TODO: implement fault-tolerant recovery; we can request the next fragment's full frame
StopStreaming();
}
}
void CDemoStreamHttp::OnFragmentRequestFailure( EHTTPStatusCode nErrorCode, int nFragment, FragmentTypeEnum_t nType )
{
Fragment_t &fragment = Fragment( nFragment );
fragment.ClearStreaming( nType );
// TODO: Retry streaming gracefully, implement timeouts, skip fragment and download the next full frame if needed
}
bool CDemoStreamHttp::OnEngineGotvSyncPacket( const CEngineGotvSyncPacket *pPkt )
{
GotvHttpStreamId_t streamId = GetStreamId( m_Url );
if ( streamId.m_nMatchId != pPkt->match_id() || streamId.m_nInstanceId != pPkt->instance_id() )
{
Warning( "Ignoring unexpected sync from gc, match %llu:%d, expected %llu:%d\n", pPkt->match_id(), pPkt->instance_id(), streamId.m_nMatchId, streamId.m_nInstanceId );
return false;
}
if ( m_nState != STATE_SYNC && m_nState != STATE_RANDOM_WAIT_AND_SYNC ) // we should be waiting for a sync in some way. In case of WAIT_AND_SYNC, maybe GC will send us a packet even though we didn't ask for it, while we're waiting to re-ask for a sync..
{
Warning( "Ignoring unexpected sync from gc, match %llu:%d\n", pPkt->match_id(), pPkt->instance_id() );
return false;
}
if ( !pPkt->has_tick() )
{
// the packet is empty, which means: wait for a few seconds
m_nState = STATE_RANDOM_WAIT_AND_SYNC;
float flDelay = pPkt->rtdelay() * RandomFloat( 0.5f, 1.5f );
m_dSyncTimeoutEnd = Plat_FloatTime() + flDelay;
DevMsg( "Waiting %.2f seconds\n", flDelay );
return true; // we actually successfully processed the packet
}
m_nDemoProtocol = 4;
m_SyncResponse.flTicksPerSecond = pPkt->tickrate();
m_SyncResponse.flKeyframeInterval = pPkt->has_keyframe_interval() ? pPkt->keyframe_interval() : 3.0f;
m_SyncResponse.nStartTick = pPkt->tick();
m_SyncResponse.flRealTimeDelay = pPkt->rtdelay();
m_SyncResponse.flReceiveAge = pPkt->rcvage();
m_SyncResponse.nFragment = pPkt->currentfragment();
m_SyncResponse.nSignupFragment = pPkt->signupfragment();
m_SyncResponse.dPlatTimeReceived = Plat_FloatTime();
return OnSync( 0 );
}
// result from "/sync" request arrived
bool CDemoStreamHttp::OnSync( const char *pBuffer, int nBufferSize, int nResync )
{
if ( m_nState != STATE_SYNC )
{
Warning( "Ignoring unexpected sync, %d bytes, resync %d\n", nBufferSize, nResync );
return false;
}
KeyValuesJSONParser json( pBuffer, nBufferSize );
if ( KeyValues *pSync = json.ParseFile() )
{
m_SyncResponse.flKeyframeInterval = pSync->GetFloat( "keyframe_interval", 3.0f );
m_SyncResponse.nStartTick = pSync->GetInt( "tick", -1 );
m_SyncResponse.flRealTimeDelay = pSync->GetFloat( "rtdelay", 0 );
m_SyncResponse.flReceiveAge = pSync->GetFloat( "rcvage", 0 );
m_SyncResponse.nFragment = pSync->GetInt( "fragment", 1 );
m_SyncResponse.nSignupFragment = pSync->GetInt( "signup_fragment", 0 );
m_SyncResponse.flTicksPerSecond = pSync->GetInt( "tps", 0 );
m_SyncResponse.dPlatTimeReceived = Plat_FloatTime();
m_nDemoProtocol = pSync->GetInt( "protocol", 4 ); // DEMO_PROTOCOL == 4 is where I started writing this
delete pSync; pSync = NULL;
return OnSync( nResync );
}
else
{
DevMsg( "Broadcast sync: malformed response: %s\n", pBuffer );
StopStreaming();
return false;
}
}
bool CDemoStreamHttp::OnSync( int nResync )
{
if ( nResync && !IsDebug() && m_SyncResponse.flReceiveAge > tv_playcast_max_rcvage.GetFloat() && m_SyncResponse.flRealTimeDelay > tv_playcast_max_rtdelay.GetFloat() )
{
DevMsg( "Broadcast resync %d: the stream seems to have stopped (rcvage %.1f, rtdelay %.1f)\n", nResync, m_SyncResponse.flReceiveAge, m_SyncResponse.flRealTimeDelay );
StopStreaming();
return false;
}
else if ( /*nTick < 0 || nEndTick < nTick || flSkip < 0 ||*/ m_SyncResponse.nFragment < m_SyncResponse.nSignupFragment || m_SyncResponse.nSignupFragment < 0 )
{
DevMsg( "Broadcast m_SyncResponse: unexpected response. fragment %d must be at/after start fragment %d\n", m_SyncResponse.nFragment, m_SyncResponse.nSignupFragment );
StopStreaming();
return false;
}
else
{
DevMsg( "Broadcast: Buffering stream tick %d fragment %d signup fragment %d\n", m_SyncResponse.nStartTick, m_SyncResponse.nSignupFragment, m_SyncResponse.nSignupFragment );
m_nState = STATE_START;
m_dSyncTimeoutEnd = -1;
m_flBroadcastKeyframeInterval = m_SyncResponse.flKeyframeInterval;
if ( nResync )
{
if ( !m_pClient )
{
DevMsg( "Broadcast resync failed: Client not connected to Stream\n" );
StopStreaming();
return false;
}
if ( m_SyncResponse.nSignupFragment == m_nStreamSignupFragment )
{
Assert( m_pStreamSignup.IsValid() && m_pClient );
m_pClient->OnDemoStreamStart( GetStreamStartReference(), nResync );
}
else
{
if ( !m_pClient->OnDemoStreamRestarting() )
{
StopStreaming();
return false;
}
DevMsg( "Resync %d response requires full stream restart because signup fragment changed from %d to %d\n", nResync, m_nStreamSignupFragment, m_SyncResponse.nSignupFragment );
SendGet( CFmtStr( "/%d/start", m_SyncResponse.nSignupFragment ), new CStartRequest( ) );
}
}
else
{
Assert( !m_pStreamSignup ); // when we're restarting, we don't need the start fragment, we already initialized
SendGet( CFmtStr( "/%d/start", m_SyncResponse.nSignupFragment ), new CStartRequest( ) );
}
if ( nResync || !tv_playcast_delay_prediction.GetBool() )
{
int nFragment = m_SyncResponse.nFragment;
BeginBuffering( nFragment );
}
return true;
}
}
void CDemoStreamHttp::BeginBuffering( int nFragment )
{
RequestFragment( nFragment, FRAGMENT_FULL );
for ( int i = 0; i <= 4; ++i )
RequestFragment( nFragment + i, FRAGMENT_DELTA );
}
void CDemoStreamHttp::RequestFragment( int nFragment, FragmentTypeEnum_t nType )
{
Fragment_t &fragment = Fragment( nFragment );
if ( !fragment.GetField( nType ) && !fragment.IsStreaming(nType) )
{
fragment.SetStreaming( nType );
SendGet( CFmtStr( nType == FRAGMENT_FULL ? "/%d/full" : "/%d/delta", nFragment ), new CFragmentRequest( nFragment, nType ) );
}
}
void CDemoStreamHttp::ReleaseFragment( int nFragment )
{
UtlHashHandle_t it = m_FragmentCache.Find( nFragment );
if ( it != m_FragmentCache.InvalidHandle() )
{
m_FragmentCache[ it ].ResetBuffers();
m_FragmentCache.RemoveByHandle( it );
}
}
GotvHttpStreamId_t CDemoStreamHttp::GetStreamId( const char *pUrl )
{
GotvHttpStreamId_t out;
if ( !pUrl || !*pUrl )
return out;
const char *p = pUrl + V_strlen( pUrl ) - 1;
if ( !V_isdigit( *p ) )
return out;
out.m_nInstanceId = *p - '0';
p--;
if ( *p != 'i' )
return out;
out.m_nMatchId = 0;
uint64 digitPlace = 1;
while ( ( --p ) >= pUrl && V_isdigit( *p ) )
{
if ( out.m_nMatchId > uint64( -1ll ) / 10 )
break; // the number doesn't fit 64 bit, error out
out.m_nMatchId += ( *p - '0' ) * digitPlace;
digitPlace *= 10;
}
if ( *p != '/' )
{
// invalid matchid
out.m_nMatchId = 0;
}
return out;
}
IDemoStreamClient::DemoStreamReference_t CDemoStreamHttp::GetStreamStartReference( bool bLagCompensation /*= false */ )
{
IDemoStreamClient::DemoStreamReference_t start;
start.nTick = m_SyncResponse.nStartTick;
start.nFragment = m_SyncResponse.nFragment;
if ( bLagCompensation )
{
float flSkipSeconds = ( Plat_FloatTime() - m_SyncResponse.dPlatTimeReceived + m_SyncResponse.flReceiveAge );
if ( flSkipSeconds >= 0 && flSkipSeconds < 90 ) // if it's not too suspiciously long interval, we can try to compensate for it
{
int nTotalSkipTicks = int( flSkipSeconds * m_SyncResponse.flTicksPerSecond );
int nTicksPerFragment = int( m_SyncResponse.flKeyframeInterval * m_SyncResponse.flTicksPerSecond );
start.nSkipTicks = nTotalSkipTicks % nTicksPerFragment;
start.nTick += ( nTotalSkipTicks - start.nSkipTicks );
start.nFragment += nTotalSkipTicks / nTicksPerFragment;
}
else
start.nSkipTicks = 0;
}
else
{
start.nSkipTicks = int( m_SyncResponse.flReceiveAge * m_SyncResponse.flTicksPerSecond ); // Maybe GC should send rtdelay - desired_delay instead?
}
return start;
}
void CDemoStreamHttp::StopStreaming()
{
if ( m_nState != STATE_IDLE )
{
m_nState = STATE_IDLE;
if ( m_pClient )
{
m_pClient->OnDemoStreamStop();
}
}
m_dSyncTimeoutEnd = -1;
while ( m_PendingRequests.Count() )
m_PendingRequests.Tail()->Cancel();
m_pStreamSignup = NULL; // delete start Buffer_t
FOR_EACH_HASHTABLE( m_FragmentCache, it )
{
m_FragmentCache.Element( it ).ResetBuffers();
}
m_FragmentCache.Purge();
}
CDemoStreamHttp::Buffer_t * CDemoStreamHttp::GetFragmentBuffer( int nFragment , FragmentTypeEnum_t nFragmentType )
{
UtlHashHandle_t hFind = m_FragmentCache.Find( nFragment );
if ( hFind == m_FragmentCache.InvalidHandle() )
return NULL;
return m_FragmentCache[ hFind ].GetField( nFragmentType );
}
void CDemoStreamHttp::CSyncRequest::OnSuccess( const HTTPRequestCompleted_t * pResponse )
{
uint32 nBodySize;
if( !s_pSteamHTTP->GetHTTPResponseBodySize( pResponse->m_hRequest, &nBodySize ) || nBodySize >= 1024 )
{
DevMsg( "Broadcast sync: response buffer overflow (%d bytes)\n", nBodySize );
return;
};
char *pResponseBuffer = StackAlloc( char, nBodySize + 1 );
if ( !s_pSteamHTTP->GetHTTPResponseBodyData( pResponse->m_hRequest, ( uint8* )pResponseBuffer, nBodySize ) )
{
DevMsg( "Broadcast sync: cannot read response body\n" );
return;
}
pResponseBuffer[ nBodySize ] = '\0';
m_pParent->OnSync( pResponseBuffer, nBodySize, m_nResync );
}
void CDemoStreamHttp::CSyncRequest::OnFailure( const HTTPRequestCompleted_t * pResponse )
{
if ( !m_nResync || m_nResync > 5 )
{
CPendingRequest::OnFailure( pResponse );
}
else
{
DevMsg( "%d stream resync failed\n", m_nResync );
m_pParent->SendSync( m_nResync + 1 ); // retry a couple times
}
}
void CDemoStreamHttp::CPendingRequest::OnFailure( const HTTPRequestCompleted_t * pResponse )
{
if ( !pResponse )
{
DevMsg( "Broadcast IO error. Please try again later.\n" );
}
else
{
DevMsg( "Broadcast Streaming error %d\n", pResponse->m_eStatusCode );
}
m_pParent->StopStreaming();
}
void CDemoStreamHttp::CStartRequest::OnSuccess( const HTTPRequestCompleted_t * pResponse )
{
m_pParent->OnStart( pResponse->m_hRequest );
}
CDemoStreamHttp::Buffer_t * CDemoStreamHttp::MakeBuffer( HTTPRequestHandle hRequest )
{
uint32 nBodySize;
if ( !s_pSteamHTTP->GetHTTPResponseBodySize( hRequest, &nBodySize ) )
{
return NULL;
}
uint8 *pMemory = new uint8[ sizeof( Buffer_t ) + nBodySize + 1 ];
if ( !s_pSteamHTTP->GetHTTPResponseBodyData( hRequest, pMemory + sizeof( Buffer_t ), nBodySize ) )
{
delete[] pMemory;
return NULL;
}
pMemory[ sizeof( Buffer_t ) + nBodySize ] = '\0'; // in case we need to receive and parse some text-only packets in the future
Buffer_t* pBuffer = ( Buffer_t* )pMemory;
pBuffer->m_nRefCount = 0;
pBuffer->m_nSize = nBodySize;
return pBuffer;
}
void CDemoStreamHttp::SendGet( const char *pPath, CPendingRequest *pRequest )
{
HTTPRequestHandle hRequest = s_pSteamHTTP->CreateHTTPRequest( k_EHTTPMethodGET, m_Url + pPath );
s_pSteamHTTP->SetHTTPRequestNetworkActivityTimeout( hRequest, 30 );
const char *pOriginAuth = tv_playcast_origin_auth.GetString();
if ( pOriginAuth && *pOriginAuth )
{
if ( !s_pSteamHTTP->SetHTTPRequestHeaderValue( hRequest, "X-Origin-Auth", pOriginAuth ) )
{
Warning( "Cannot set http X-Origin-Auth for %s\n", pPath );
}
}
SteamAPICall_t hCall;
bool bSentOk = s_pSteamHTTP->SendHTTPRequest( hRequest, &hCall );
pRequest->Init( this, hRequest, hCall );
if ( bSentOk && hCall )
{
SteamAPI_RegisterCallResult( pRequest, hCall );
}
else
{
s_pSteamHTTP->ReleaseHTTPRequest( hRequest );
DevMsg( "Broadcast streaming: unexpected failure getting %s\n", pPath );
pRequest->OnFailure( NULL ); // IO failure
}
}
CDemoStreamHttp::Fragment_t & CDemoStreamHttp::Fragment( int nFragment )
{
UtlHashHandle_t it = m_FragmentCache.Insert( nFragment );
return m_FragmentCache[ it ];
}
CDemoStreamHttp::CPendingRequest::CPendingRequest() :
m_pParent( NULL ),
m_hRequest( INVALID_HTTPREQUEST_HANDLE ),
m_hCall( k_uAPICallInvalid )
{
m_iCallback = HTTPRequestCompleted_t::k_iCallback;
}
void CDemoStreamHttp::CPendingRequest::Init( CDemoStreamHttp *pParent, HTTPRequestHandle hRequest, SteamAPICall_t hCall )
{
m_pParent = pParent;
m_hRequest = hRequest;
m_hCall = hCall;
pParent->m_PendingRequests.AddToTail( this );
}
CDemoStreamHttp::CPendingRequest::~CPendingRequest()
{
}
void CDemoStreamHttp::CPendingRequest::Run( void *pvParam )
{
m_pParent->m_PendingRequests.FindAndFastRemove( this );
OnSuccess( ( HTTPRequestCompleted_t * )pvParam );
delete this;
}
void CDemoStreamHttp::CPendingRequest::Run( void *pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall )
{
m_pParent->m_PendingRequests.FindAndFastRemove( this );
if ( bIOFailure )
{
OnFailure( NULL );
}
else
{
EHTTPStatusCode nStatus = ( ( HTTPRequestCompleted_t * )pvParam )->m_eStatusCode;
Assert( ( ( HTTPRequestCompleted_t * )pvParam )->m_hRequest == m_hRequest );
if ( nStatus != k_EHTTPStatusCode200OK ) // we should always get a 200
{
OnFailure( ( HTTPRequestCompleted_t * )pvParam );
}
else
{
OnSuccess( ( HTTPRequestCompleted_t * )pvParam );
}
}
delete this;
}
void CDemoStreamHttp::CPendingRequest::Cancel()
{
SteamAPI_UnregisterCallResult( this, m_hCall );
s_pSteamHTTP->ReleaseHTTPRequest( m_hRequest );
m_pParent->m_PendingRequests.FindAndFastRemove( this );
OnFailure( NULL );
delete this;
}
void CDemoStreamHttp::CFragmentRequest::OnSuccess( const HTTPRequestCompleted_t * pResponse )
{
m_pParent->OnFragmentRequestSuccess( pResponse->m_hRequest, m_nFragment, m_nType );
}
static const char *s_pFragmentTypeName[ CDemoStreamHttp::FRAGMENT_COUNT ] =
{
"delta", "full"
};
void CDemoStreamHttp::CFragmentRequest::OnFailure( const HTTPRequestCompleted_t * pResponse )
{
if ( demo_debug.GetBool() )
DevMsg( "Failed to retrieve %s fragment %d\n", s_pFragmentTypeName[ m_nType ], m_nFragment );
m_pParent->OnFragmentRequestFailure( pResponse ? pResponse->m_eStatusCode : k_EHTTPStatusCodeInvalid, m_nFragment, m_nType );
// CPendingRequest::OnFailure( pResponse ); <-- the parent OnFail will stop streaming, and we don't want that because we'll retry this fragment or next for a few times before giving up
}
void CDemoStreamHttp::Fragment_t::ResetBuffers()
{
for ( int i = 0; i < FRAGMENT_COUNT; ++i )
{
if ( m_pField[ i ] )
{
Buffer_t::Release( m_pField[ i ] );
m_pField[ i ] = NULL;
}
}
}
void CDemoStreamHttp::Fragment_t::SetField( FragmentTypeEnum_t nFragment, Buffer_t *pBuffer )
{
if ( pBuffer )
Buffer_t::AddRef( pBuffer );
if ( m_pField[ nFragment ] )
Buffer_t::Release( m_pField[ nFragment ] );
m_pField[ nFragment ] = pBuffer;
}
void CDemoStreamHttp::SyncParams_t::PrintSyncRequest(char *buffer, int nBufferSize) const
{
if ( m_nStartFragment )
V_snprintf(buffer, nBufferSize, "/sync?fragment=%d", m_nStartFragment );
else
V_strncpy( buffer, "/sync", nBufferSize );
}