Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

657 lines
20 KiB

  1. //========= Copyright (c), Valve Corporation, All rights reserved. ============//
  2. #include "client_pch.h"
  3. #include "demostreamhttp.h"
  4. #include "cl_steamauth.h"
  5. #include "tier1/keyvaluesjson.h"
  6. #include "tier0/memalloc.h"
  7. #include "cl_demo.h"
  8. #include "sv_steamauth.h"
  9. #include "engine_gcmessages.pb.h"
  10. static ISteamHTTP *s_pSteamHTTP = NULL;
  11. ConVar demo_debug( "demo_debug", "0", 0, "Demo debug info." );
  12. ConVar tv_playcast_origin_auth( "tv_playcast_origin_auth", "", FCVAR_RELEASE | FCVAR_HIDDEN, "Get request X-Origin-Auth string" );
  13. ConVar tv_playcast_max_rcvage( "tv_playcast_max_rcvage", "15", FCVAR_RELEASE | FCVAR_HIDDEN );
  14. ConVar tv_playcast_max_rtdelay( "tv_playcast_max_rtdelay", "55", FCVAR_RELEASE | FCVAR_HIDDEN );
  15. ConVar tv_playcast_delay_prediction( "tv_playcast_delay_prediction", "1", FCVAR_RELEASE );
  16. CDemoStreamHttp::CDemoStreamHttp() :
  17. m_nState( STATE_IDLE ),
  18. m_pStreamSignup( NULL ),
  19. m_pClient( NULL ),
  20. m_bSyncFromGc( false ),
  21. m_flBroadcastKeyframeInterval( 3 )
  22. {
  23. V_memset( &m_SyncResponse, 0, sizeof( m_SyncResponse ) );
  24. m_dSyncTimeoutEnd = -1;
  25. }
  26. void CDemoStreamHttp::StartStreaming( const char *pUrl, SyncParams_t syncParams )
  27. {
  28. if ( !PrepareForStreaming( pUrl ) )
  29. return;
  30. m_SyncParams = syncParams;
  31. SendSync( );
  32. }
  33. bool CDemoStreamHttp::PrepareForStreaming( const char * pUrl )
  34. {
  35. StopStreaming();
  36. if ( pUrl[ 0 ] == 'g' && pUrl[ 1 ] == 'c' && pUrl[ 2 ] == '-' )
  37. {
  38. m_Url = pUrl + 3;
  39. m_bSyncFromGc = true;
  40. }
  41. else
  42. {
  43. m_Url = pUrl;
  44. m_bSyncFromGc = false;
  45. }
  46. m_Url.StripTrailingSlash();
  47. #ifndef DEDICATED
  48. s_pSteamHTTP = Steam3Client().SteamHTTP();
  49. #endif
  50. if ( !s_pSteamHTTP )
  51. s_pSteamHTTP = Steam3Server().SteamHTTP();
  52. if ( !s_pSteamHTTP )
  53. {
  54. DevMsg( "Cannot get Steam HTTP interface\n" );
  55. return false ;
  56. }
  57. DevMsg( "Broadcast: Synchronizing stream\n" );
  58. m_nState = STATE_SYNC;
  59. return true;
  60. }
  61. // this is only called in special cases for debugging, to play back stale contents
  62. void CDemoStreamHttp::StartStreamingCached( const char *pUrl, int nFragment)
  63. {
  64. if ( !PrepareForStreaming( pUrl ) )
  65. return;
  66. m_nState = STATE_START;
  67. // guess parameters of the stream
  68. int nStartTick = 1;
  69. m_nDemoProtocol = 4; // DEMO_PROTOCOL == 4 is where I started writing this
  70. int nSignupFragment = 0;
  71. m_SyncParams.m_nStartFragment = 0;
  72. m_SyncResponse.flTicksPerSecond = 128;
  73. m_SyncResponse.flKeyframeInterval = 3;
  74. m_SyncResponse.flRealTimeDelay = 0;
  75. m_SyncResponse.flReceiveAge = 0;
  76. m_SyncResponse.nFragment = nFragment;
  77. m_SyncResponse.nSignupFragment = nSignupFragment;
  78. m_SyncResponse.nStartTick = nStartTick;
  79. m_SyncResponse.dPlatTimeReceived = Plat_FloatTime();
  80. SendGet( CFmtStr( "/%d/start", nSignupFragment ), new CStartRequest( ) );
  81. BeginBuffering( nFragment );
  82. }
  83. void CDemoStreamHttp::SendSync( int nResync )
  84. {
  85. Assert( m_nState == STATE_SYNC );
  86. #ifndef DEDICATED
  87. if ( m_bSyncFromGc )
  88. {
  89. GotvHttpStreamId_t params = GetStreamId( m_Url );
  90. DevMsg( "Requesting sync from GC, start fragment %d match id %llu instance %d\n", m_SyncParams.m_nStartFragment, params.m_nMatchId, params.m_nInstanceId );
  91. CEngineGotvSyncPacket msg;
  92. msg.set_match_id( params.m_nMatchId );
  93. msg.set_instance_id( params.m_nInstanceId );
  94. if ( m_SyncParams.m_nStartFragment > 0 )
  95. {
  96. msg.set_currentfragment( m_SyncParams.m_nStartFragment );
  97. }
  98. g_ClientDLL->EngineGotvSyncPacket( &msg );
  99. m_dSyncTimeoutEnd = Plat_FloatTime() + 10;
  100. }
  101. else
  102. #endif
  103. {
  104. char request[ 128 ];
  105. m_SyncParams.PrintSyncRequest( request, sizeof( request ) );
  106. DevMsg( "Requesting sync from relay %s\n", request );
  107. SendGet( request, new CSyncRequest( m_SyncParams, nResync ) );
  108. }
  109. }
  110. void CDemoStreamHttp::Resync( )
  111. {
  112. m_nState = STATE_SYNC;
  113. SendSync( 1 );
  114. }
  115. void CDemoStreamHttp::Update()
  116. {
  117. if ( m_nState == STATE_SYNC && m_bSyncFromGc && m_dSyncTimeoutEnd > 0 && m_dSyncTimeoutEnd < Plat_FloatTime() )
  118. {
  119. StopStreaming();
  120. }
  121. if ( m_nState == STATE_RANDOM_WAIT_AND_SYNC && m_bSyncFromGc && m_dSyncTimeoutEnd > 0 && m_dSyncTimeoutEnd < Plat_FloatTime() )
  122. {
  123. m_nState = STATE_SYNC;
  124. SendSync( 0 );
  125. }
  126. }
  127. // result from "/start" request
  128. void CDemoStreamHttp::OnStart( HTTPRequestHandle hRequest )
  129. {
  130. m_pStreamSignup = MakeBuffer( hRequest );
  131. m_nStreamSignupFragment = m_SyncResponse.nSignupFragment;
  132. if ( !m_pStreamSignup )
  133. {
  134. DevMsg( "Broadcast failed to start: cannot retrieve startup packet data\n" );
  135. StopStreaming();
  136. return;
  137. }
  138. DevMsg( "Received signup fragment %d\n", m_SyncResponse.nSignupFragment );
  139. if ( m_pClient )
  140. {
  141. m_pClient->OnDemoStreamStart( GetStreamStartReference(), 0 );
  142. }
  143. }
  144. void CDemoStreamHttp::OnFragmentRequestSuccess( HTTPRequestHandle hRequest, int nFragment, FragmentTypeEnum_t nType )
  145. {
  146. Fragment_t &fragment = Fragment( nFragment );
  147. fragment.ClearStreaming( nType );
  148. if ( Buffer_t *pBuf = MakeBuffer( hRequest ) )
  149. {
  150. fragment.SetField( nType, pBuf );
  151. }
  152. else
  153. {
  154. 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
  155. StopStreaming();
  156. }
  157. }
  158. void CDemoStreamHttp::OnFragmentRequestFailure( EHTTPStatusCode nErrorCode, int nFragment, FragmentTypeEnum_t nType )
  159. {
  160. Fragment_t &fragment = Fragment( nFragment );
  161. fragment.ClearStreaming( nType );
  162. // TODO: Retry streaming gracefully, implement timeouts, skip fragment and download the next full frame if needed
  163. }
  164. bool CDemoStreamHttp::OnEngineGotvSyncPacket( const CEngineGotvSyncPacket *pPkt )
  165. {
  166. GotvHttpStreamId_t streamId = GetStreamId( m_Url );
  167. if ( streamId.m_nMatchId != pPkt->match_id() || streamId.m_nInstanceId != pPkt->instance_id() )
  168. {
  169. 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 );
  170. return false;
  171. }
  172. 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..
  173. {
  174. Warning( "Ignoring unexpected sync from gc, match %llu:%d\n", pPkt->match_id(), pPkt->instance_id() );
  175. return false;
  176. }
  177. if ( !pPkt->has_tick() )
  178. {
  179. // the packet is empty, which means: wait for a few seconds
  180. m_nState = STATE_RANDOM_WAIT_AND_SYNC;
  181. float flDelay = pPkt->rtdelay() * RandomFloat( 0.5f, 1.5f );
  182. m_dSyncTimeoutEnd = Plat_FloatTime() + flDelay;
  183. DevMsg( "Waiting %.2f seconds\n", flDelay );
  184. return true; // we actually successfully processed the packet
  185. }
  186. m_nDemoProtocol = 4;
  187. m_SyncResponse.flTicksPerSecond = pPkt->tickrate();
  188. m_SyncResponse.flKeyframeInterval = pPkt->has_keyframe_interval() ? pPkt->keyframe_interval() : 3.0f;
  189. m_SyncResponse.nStartTick = pPkt->tick();
  190. m_SyncResponse.flRealTimeDelay = pPkt->rtdelay();
  191. m_SyncResponse.flReceiveAge = pPkt->rcvage();
  192. m_SyncResponse.nFragment = pPkt->currentfragment();
  193. m_SyncResponse.nSignupFragment = pPkt->signupfragment();
  194. m_SyncResponse.dPlatTimeReceived = Plat_FloatTime();
  195. return OnSync( 0 );
  196. }
  197. // result from "/sync" request arrived
  198. bool CDemoStreamHttp::OnSync( const char *pBuffer, int nBufferSize, int nResync )
  199. {
  200. if ( m_nState != STATE_SYNC )
  201. {
  202. Warning( "Ignoring unexpected sync, %d bytes, resync %d\n", nBufferSize, nResync );
  203. return false;
  204. }
  205. KeyValuesJSONParser json( pBuffer, nBufferSize );
  206. if ( KeyValues *pSync = json.ParseFile() )
  207. {
  208. m_SyncResponse.flKeyframeInterval = pSync->GetFloat( "keyframe_interval", 3.0f );
  209. m_SyncResponse.nStartTick = pSync->GetInt( "tick", -1 );
  210. m_SyncResponse.flRealTimeDelay = pSync->GetFloat( "rtdelay", 0 );
  211. m_SyncResponse.flReceiveAge = pSync->GetFloat( "rcvage", 0 );
  212. m_SyncResponse.nFragment = pSync->GetInt( "fragment", 1 );
  213. m_SyncResponse.nSignupFragment = pSync->GetInt( "signup_fragment", 0 );
  214. m_SyncResponse.flTicksPerSecond = pSync->GetInt( "tps", 0 );
  215. m_SyncResponse.dPlatTimeReceived = Plat_FloatTime();
  216. m_nDemoProtocol = pSync->GetInt( "protocol", 4 ); // DEMO_PROTOCOL == 4 is where I started writing this
  217. delete pSync; pSync = NULL;
  218. return OnSync( nResync );
  219. }
  220. else
  221. {
  222. DevMsg( "Broadcast sync: malformed response: %s\n", pBuffer );
  223. StopStreaming();
  224. return false;
  225. }
  226. }
  227. bool CDemoStreamHttp::OnSync( int nResync )
  228. {
  229. if ( nResync && !IsDebug() && m_SyncResponse.flReceiveAge > tv_playcast_max_rcvage.GetFloat() && m_SyncResponse.flRealTimeDelay > tv_playcast_max_rtdelay.GetFloat() )
  230. {
  231. DevMsg( "Broadcast resync %d: the stream seems to have stopped (rcvage %.1f, rtdelay %.1f)\n", nResync, m_SyncResponse.flReceiveAge, m_SyncResponse.flRealTimeDelay );
  232. StopStreaming();
  233. return false;
  234. }
  235. else if ( /*nTick < 0 || nEndTick < nTick || flSkip < 0 ||*/ m_SyncResponse.nFragment < m_SyncResponse.nSignupFragment || m_SyncResponse.nSignupFragment < 0 )
  236. {
  237. DevMsg( "Broadcast m_SyncResponse: unexpected response. fragment %d must be at/after start fragment %d\n", m_SyncResponse.nFragment, m_SyncResponse.nSignupFragment );
  238. StopStreaming();
  239. return false;
  240. }
  241. else
  242. {
  243. DevMsg( "Broadcast: Buffering stream tick %d fragment %d signup fragment %d\n", m_SyncResponse.nStartTick, m_SyncResponse.nSignupFragment, m_SyncResponse.nSignupFragment );
  244. m_nState = STATE_START;
  245. m_dSyncTimeoutEnd = -1;
  246. m_flBroadcastKeyframeInterval = m_SyncResponse.flKeyframeInterval;
  247. if ( nResync )
  248. {
  249. if ( !m_pClient )
  250. {
  251. DevMsg( "Broadcast resync failed: Client not connected to Stream\n" );
  252. StopStreaming();
  253. return false;
  254. }
  255. if ( m_SyncResponse.nSignupFragment == m_nStreamSignupFragment )
  256. {
  257. Assert( m_pStreamSignup.IsValid() && m_pClient );
  258. m_pClient->OnDemoStreamStart( GetStreamStartReference(), nResync );
  259. }
  260. else
  261. {
  262. if ( !m_pClient->OnDemoStreamRestarting() )
  263. {
  264. StopStreaming();
  265. return false;
  266. }
  267. DevMsg( "Resync %d response requires full stream restart because signup fragment changed from %d to %d\n", nResync, m_nStreamSignupFragment, m_SyncResponse.nSignupFragment );
  268. SendGet( CFmtStr( "/%d/start", m_SyncResponse.nSignupFragment ), new CStartRequest( ) );
  269. }
  270. }
  271. else
  272. {
  273. Assert( !m_pStreamSignup ); // when we're restarting, we don't need the start fragment, we already initialized
  274. SendGet( CFmtStr( "/%d/start", m_SyncResponse.nSignupFragment ), new CStartRequest( ) );
  275. }
  276. if ( nResync || !tv_playcast_delay_prediction.GetBool() )
  277. {
  278. int nFragment = m_SyncResponse.nFragment;
  279. BeginBuffering( nFragment );
  280. }
  281. return true;
  282. }
  283. }
  284. void CDemoStreamHttp::BeginBuffering( int nFragment )
  285. {
  286. RequestFragment( nFragment, FRAGMENT_FULL );
  287. for ( int i = 0; i <= 4; ++i )
  288. RequestFragment( nFragment + i, FRAGMENT_DELTA );
  289. }
  290. void CDemoStreamHttp::RequestFragment( int nFragment, FragmentTypeEnum_t nType )
  291. {
  292. Fragment_t &fragment = Fragment( nFragment );
  293. if ( !fragment.GetField( nType ) && !fragment.IsStreaming(nType) )
  294. {
  295. fragment.SetStreaming( nType );
  296. SendGet( CFmtStr( nType == FRAGMENT_FULL ? "/%d/full" : "/%d/delta", nFragment ), new CFragmentRequest( nFragment, nType ) );
  297. }
  298. }
  299. void CDemoStreamHttp::ReleaseFragment( int nFragment )
  300. {
  301. UtlHashHandle_t it = m_FragmentCache.Find( nFragment );
  302. if ( it != m_FragmentCache.InvalidHandle() )
  303. {
  304. m_FragmentCache[ it ].ResetBuffers();
  305. m_FragmentCache.RemoveByHandle( it );
  306. }
  307. }
  308. GotvHttpStreamId_t CDemoStreamHttp::GetStreamId( const char *pUrl )
  309. {
  310. GotvHttpStreamId_t out;
  311. if ( !pUrl || !*pUrl )
  312. return out;
  313. const char *p = pUrl + V_strlen( pUrl ) - 1;
  314. if ( !V_isdigit( *p ) )
  315. return out;
  316. out.m_nInstanceId = *p - '0';
  317. p--;
  318. if ( *p != 'i' )
  319. return out;
  320. out.m_nMatchId = 0;
  321. uint64 digitPlace = 1;
  322. while ( ( --p ) >= pUrl && V_isdigit( *p ) )
  323. {
  324. if ( out.m_nMatchId > uint64( -1ll ) / 10 )
  325. break; // the number doesn't fit 64 bit, error out
  326. out.m_nMatchId += ( *p - '0' ) * digitPlace;
  327. digitPlace *= 10;
  328. }
  329. if ( *p != '/' )
  330. {
  331. // invalid matchid
  332. out.m_nMatchId = 0;
  333. }
  334. return out;
  335. }
  336. IDemoStreamClient::DemoStreamReference_t CDemoStreamHttp::GetStreamStartReference( bool bLagCompensation /*= false */ )
  337. {
  338. IDemoStreamClient::DemoStreamReference_t start;
  339. start.nTick = m_SyncResponse.nStartTick;
  340. start.nFragment = m_SyncResponse.nFragment;
  341. if ( bLagCompensation )
  342. {
  343. float flSkipSeconds = ( Plat_FloatTime() - m_SyncResponse.dPlatTimeReceived + m_SyncResponse.flReceiveAge );
  344. if ( flSkipSeconds >= 0 && flSkipSeconds < 90 ) // if it's not too suspiciously long interval, we can try to compensate for it
  345. {
  346. int nTotalSkipTicks = int( flSkipSeconds * m_SyncResponse.flTicksPerSecond );
  347. int nTicksPerFragment = int( m_SyncResponse.flKeyframeInterval * m_SyncResponse.flTicksPerSecond );
  348. start.nSkipTicks = nTotalSkipTicks % nTicksPerFragment;
  349. start.nTick += ( nTotalSkipTicks - start.nSkipTicks );
  350. start.nFragment += nTotalSkipTicks / nTicksPerFragment;
  351. }
  352. else
  353. start.nSkipTicks = 0;
  354. }
  355. else
  356. {
  357. start.nSkipTicks = int( m_SyncResponse.flReceiveAge * m_SyncResponse.flTicksPerSecond ); // Maybe GC should send rtdelay - desired_delay instead?
  358. }
  359. return start;
  360. }
  361. void CDemoStreamHttp::StopStreaming()
  362. {
  363. if ( m_nState != STATE_IDLE )
  364. {
  365. m_nState = STATE_IDLE;
  366. if ( m_pClient )
  367. {
  368. m_pClient->OnDemoStreamStop();
  369. }
  370. }
  371. m_dSyncTimeoutEnd = -1;
  372. while ( m_PendingRequests.Count() )
  373. m_PendingRequests.Tail()->Cancel();
  374. m_pStreamSignup = NULL; // delete start Buffer_t
  375. FOR_EACH_HASHTABLE( m_FragmentCache, it )
  376. {
  377. m_FragmentCache.Element( it ).ResetBuffers();
  378. }
  379. m_FragmentCache.Purge();
  380. }
  381. CDemoStreamHttp::Buffer_t * CDemoStreamHttp::GetFragmentBuffer( int nFragment , FragmentTypeEnum_t nFragmentType )
  382. {
  383. UtlHashHandle_t hFind = m_FragmentCache.Find( nFragment );
  384. if ( hFind == m_FragmentCache.InvalidHandle() )
  385. return NULL;
  386. return m_FragmentCache[ hFind ].GetField( nFragmentType );
  387. }
  388. void CDemoStreamHttp::CSyncRequest::OnSuccess( const HTTPRequestCompleted_t * pResponse )
  389. {
  390. uint32 nBodySize;
  391. if( !s_pSteamHTTP->GetHTTPResponseBodySize( pResponse->m_hRequest, &nBodySize ) || nBodySize >= 1024 )
  392. {
  393. DevMsg( "Broadcast sync: response buffer overflow (%d bytes)\n", nBodySize );
  394. return;
  395. };
  396. char *pResponseBuffer = StackAlloc( char, nBodySize + 1 );
  397. if ( !s_pSteamHTTP->GetHTTPResponseBodyData( pResponse->m_hRequest, ( uint8* )pResponseBuffer, nBodySize ) )
  398. {
  399. DevMsg( "Broadcast sync: cannot read response body\n" );
  400. return;
  401. }
  402. pResponseBuffer[ nBodySize ] = '\0';
  403. m_pParent->OnSync( pResponseBuffer, nBodySize, m_nResync );
  404. }
  405. void CDemoStreamHttp::CSyncRequest::OnFailure( const HTTPRequestCompleted_t * pResponse )
  406. {
  407. if ( !m_nResync || m_nResync > 5 )
  408. {
  409. CPendingRequest::OnFailure( pResponse );
  410. }
  411. else
  412. {
  413. DevMsg( "%d stream resync failed\n", m_nResync );
  414. m_pParent->SendSync( m_nResync + 1 ); // retry a couple times
  415. }
  416. }
  417. void CDemoStreamHttp::CPendingRequest::OnFailure( const HTTPRequestCompleted_t * pResponse )
  418. {
  419. if ( !pResponse )
  420. {
  421. DevMsg( "Broadcast IO error. Please try again later.\n" );
  422. }
  423. else
  424. {
  425. DevMsg( "Broadcast Streaming error %d\n", pResponse->m_eStatusCode );
  426. }
  427. m_pParent->StopStreaming();
  428. }
  429. void CDemoStreamHttp::CStartRequest::OnSuccess( const HTTPRequestCompleted_t * pResponse )
  430. {
  431. m_pParent->OnStart( pResponse->m_hRequest );
  432. }
  433. CDemoStreamHttp::Buffer_t * CDemoStreamHttp::MakeBuffer( HTTPRequestHandle hRequest )
  434. {
  435. uint32 nBodySize;
  436. if ( !s_pSteamHTTP->GetHTTPResponseBodySize( hRequest, &nBodySize ) )
  437. {
  438. return NULL;
  439. }
  440. uint8 *pMemory = new uint8[ sizeof( Buffer_t ) + nBodySize + 1 ];
  441. if ( !s_pSteamHTTP->GetHTTPResponseBodyData( hRequest, pMemory + sizeof( Buffer_t ), nBodySize ) )
  442. {
  443. delete[] pMemory;
  444. return NULL;
  445. }
  446. pMemory[ sizeof( Buffer_t ) + nBodySize ] = '\0'; // in case we need to receive and parse some text-only packets in the future
  447. Buffer_t* pBuffer = ( Buffer_t* )pMemory;
  448. pBuffer->m_nRefCount = 0;
  449. pBuffer->m_nSize = nBodySize;
  450. return pBuffer;
  451. }
  452. void CDemoStreamHttp::SendGet( const char *pPath, CPendingRequest *pRequest )
  453. {
  454. HTTPRequestHandle hRequest = s_pSteamHTTP->CreateHTTPRequest( k_EHTTPMethodGET, m_Url + pPath );
  455. s_pSteamHTTP->SetHTTPRequestNetworkActivityTimeout( hRequest, 30 );
  456. const char *pOriginAuth = tv_playcast_origin_auth.GetString();
  457. if ( pOriginAuth && *pOriginAuth )
  458. {
  459. if ( !s_pSteamHTTP->SetHTTPRequestHeaderValue( hRequest, "X-Origin-Auth", pOriginAuth ) )
  460. {
  461. Warning( "Cannot set http X-Origin-Auth for %s\n", pPath );
  462. }
  463. }
  464. SteamAPICall_t hCall;
  465. bool bSentOk = s_pSteamHTTP->SendHTTPRequest( hRequest, &hCall );
  466. pRequest->Init( this, hRequest, hCall );
  467. if ( bSentOk && hCall )
  468. {
  469. SteamAPI_RegisterCallResult( pRequest, hCall );
  470. }
  471. else
  472. {
  473. s_pSteamHTTP->ReleaseHTTPRequest( hRequest );
  474. DevMsg( "Broadcast streaming: unexpected failure getting %s\n", pPath );
  475. pRequest->OnFailure( NULL ); // IO failure
  476. }
  477. }
  478. CDemoStreamHttp::Fragment_t & CDemoStreamHttp::Fragment( int nFragment )
  479. {
  480. UtlHashHandle_t it = m_FragmentCache.Insert( nFragment );
  481. return m_FragmentCache[ it ];
  482. }
  483. CDemoStreamHttp::CPendingRequest::CPendingRequest() :
  484. m_pParent( NULL ),
  485. m_hRequest( INVALID_HTTPREQUEST_HANDLE ),
  486. m_hCall( k_uAPICallInvalid )
  487. {
  488. m_iCallback = HTTPRequestCompleted_t::k_iCallback;
  489. }
  490. void CDemoStreamHttp::CPendingRequest::Init( CDemoStreamHttp *pParent, HTTPRequestHandle hRequest, SteamAPICall_t hCall )
  491. {
  492. m_pParent = pParent;
  493. m_hRequest = hRequest;
  494. m_hCall = hCall;
  495. pParent->m_PendingRequests.AddToTail( this );
  496. }
  497. CDemoStreamHttp::CPendingRequest::~CPendingRequest()
  498. {
  499. }
  500. void CDemoStreamHttp::CPendingRequest::Run( void *pvParam )
  501. {
  502. m_pParent->m_PendingRequests.FindAndFastRemove( this );
  503. OnSuccess( ( HTTPRequestCompleted_t * )pvParam );
  504. delete this;
  505. }
  506. void CDemoStreamHttp::CPendingRequest::Run( void *pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall )
  507. {
  508. m_pParent->m_PendingRequests.FindAndFastRemove( this );
  509. if ( bIOFailure )
  510. {
  511. OnFailure( NULL );
  512. }
  513. else
  514. {
  515. EHTTPStatusCode nStatus = ( ( HTTPRequestCompleted_t * )pvParam )->m_eStatusCode;
  516. Assert( ( ( HTTPRequestCompleted_t * )pvParam )->m_hRequest == m_hRequest );
  517. if ( nStatus != k_EHTTPStatusCode200OK ) // we should always get a 200
  518. {
  519. OnFailure( ( HTTPRequestCompleted_t * )pvParam );
  520. }
  521. else
  522. {
  523. OnSuccess( ( HTTPRequestCompleted_t * )pvParam );
  524. }
  525. }
  526. delete this;
  527. }
  528. void CDemoStreamHttp::CPendingRequest::Cancel()
  529. {
  530. SteamAPI_UnregisterCallResult( this, m_hCall );
  531. s_pSteamHTTP->ReleaseHTTPRequest( m_hRequest );
  532. m_pParent->m_PendingRequests.FindAndFastRemove( this );
  533. OnFailure( NULL );
  534. delete this;
  535. }
  536. void CDemoStreamHttp::CFragmentRequest::OnSuccess( const HTTPRequestCompleted_t * pResponse )
  537. {
  538. m_pParent->OnFragmentRequestSuccess( pResponse->m_hRequest, m_nFragment, m_nType );
  539. }
  540. static const char *s_pFragmentTypeName[ CDemoStreamHttp::FRAGMENT_COUNT ] =
  541. {
  542. "delta", "full"
  543. };
  544. void CDemoStreamHttp::CFragmentRequest::OnFailure( const HTTPRequestCompleted_t * pResponse )
  545. {
  546. if ( demo_debug.GetBool() )
  547. DevMsg( "Failed to retrieve %s fragment %d\n", s_pFragmentTypeName[ m_nType ], m_nFragment );
  548. m_pParent->OnFragmentRequestFailure( pResponse ? pResponse->m_eStatusCode : k_EHTTPStatusCodeInvalid, m_nFragment, m_nType );
  549. // 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
  550. }
  551. void CDemoStreamHttp::Fragment_t::ResetBuffers()
  552. {
  553. for ( int i = 0; i < FRAGMENT_COUNT; ++i )
  554. {
  555. if ( m_pField[ i ] )
  556. {
  557. Buffer_t::Release( m_pField[ i ] );
  558. m_pField[ i ] = NULL;
  559. }
  560. }
  561. }
  562. void CDemoStreamHttp::Fragment_t::SetField( FragmentTypeEnum_t nFragment, Buffer_t *pBuffer )
  563. {
  564. if ( pBuffer )
  565. Buffer_t::AddRef( pBuffer );
  566. if ( m_pField[ nFragment ] )
  567. Buffer_t::Release( m_pField[ nFragment ] );
  568. m_pField[ nFragment ] = pBuffer;
  569. }
  570. void CDemoStreamHttp::SyncParams_t::PrintSyncRequest(char *buffer, int nBufferSize) const
  571. {
  572. if ( m_nStartFragment )
  573. V_snprintf(buffer, nBufferSize, "/sync?fragment=%d", m_nStartFragment );
  574. else
  575. V_strncpy( buffer, "/sync", nBufferSize );
  576. }