Skip to content

GOTV & Demos

GOTV Broadcast

Get5 makes no changes to the broadcasting part of the GOTV, but will automatically adjust the mp_match_restart_delay when a map ends if GOTV is enabled to ensure that it won't be shorter than what is required for the GOTV broadcast to finish. Players will also not be kicked from the server before this delay has passed.

Don't mess too much with the TV! 📺

Changing tv_delay or tv_enable in warmup.cfg, live.cfg etc. is going to cause problems with your demos. We recommend you set tv_delay either on your server in general or only once in the cvars section of your match configuration. You should also not set tv_delaymapchange as Get5 handles this automatically.

We recommend that you do not set tv_enable in your match configuration, as it requires a map change for the GOTV bot to join the server. You should enable GOTV in your general server config and refrain from turning it on and off with Get5. Note that setting tv_enable 1 won't allow people to join your server's GOTV. You must also set tv_advertise_watchable 1, so you don't have to worry about ghosting if this is disabled.

Recording Demos

Get5 can be configured to automatically record matches. This is enabled by default based on the state of get5_demo_name_format and can be disabled by setting that parameter to an empty string.

Demo recording starts once all teams have readied up and ends following a map result. When a demo file is written to disk, the Get5_OnDemoFinished forward is called shortly after. The filename can also be found in the map-section of the KeyValue stats system.

Broadcast delay on GOTV recording

When the GOTV recording stops, the server will flush its framebuffer to disk. This may cause a lag spike or a complete freeze of the GOTV broadcast if you have a substantial tv_delay, so Get5 will wait until the entire match has been broadcast before it stops recording the demo.

Automatic Upload

In addition to recording demos, Get5 can also upload them to a URL when the recording stops. To enable this feature, you must have the SteamWorks extension installed and define the URL with get5_demo_upload_url. The HTTP body will be the raw demo file, and you can read the headers for file metadata.

Headers

Get5 will add these HTTP headers to its demo upload request:

  1. Get5-FileName is the name of the file as defined by get5_demo_name_format, i.e. 2022-09-11_20-49-49_1564_map1_de_vertigo.dem.
  2. Get5-MapNumber is the zero-indexed map number in the series.
  3. Get5-MatchId if the match ID is not an empty string.
  4. Get5-ServerId if get5_server_id is not an empty string.
  5. Get5-Version is the version of Get5, i.e. 0.12.0.

Authorization

If you wish to authenticate your upload request, you can define both get5_demo_upload_header_key and get5_demo_upload_header_value as a header key-value pair which Get5 will add to the request.

Cleanup

If you set get5_demo_delete_after_upload, the demo file will be removed from the game server after successful upload.

Example

This is an example of how a Node.js web server using Express might read the demo upload request sent by Get5.

Proof of concept only

This is a simple proof-of-concept and should not be blindly copied to a production system. It has no HTTPS support and is only meant to demonstrate the key aspects of reading a potentially large POST request.

Node.js example
const express = require('express');
const fs = require('fs');
const path = require("path");
const app = express();

// Accept POST requests at http://domain.tld/upload-file
app.post('/upload-file', function (req, res) {

   // Check that the authorization header configured in Get5 matches.
   // Note that header names are not case-sensitive.
   const authorization = req.header('Authorization');

   if (authorization !== 'super_secret_key') {
       res.status(403);
       res.end('Invalid authorization header.');
       return;
   }

   // Read the Get5 headers to know what to do with the file and potentially identify the server.
   const filename = req.header('Get5-FileName');
   const matchId = req.header('Get5-MatchId');
   const mapNumber = req.header('Get5-MapNumber');
   const serverId = req.header('Get5-ServerId');

   // Put all demos for the same match in a folder.
   const folder = path.join(__dirname, 'demos', matchId);
   if (!fs.existsSync(folder)) {
      fs.mkdirSync(folder, {recursive: true});
   }
   // Create a stream and point it to a file, using the filename from the header.
   let writeStream = fs.createWriteStream(path.join(folder, filename));

   // Pipe the request body into the stream.
   req.pipe(writeStream);

   // Wait for the request to end and reply with 200.
   req.on('end', () => {
      writeStream.end();
      res.status(200);
      res.end('Success');
   });

   // If there is a problem writing the file, reply with 500.
   writeStream.on('error', function (err) {
      res.status(500);
      res.end('Error writing demo file: ' + err.message);
   });

})
app.listen(8080);