Skip to content
RustPluginGenerator

OnDemoRecordingStopped

OnDemoRecordingStopped is called after a player's server-side demo recording has been stopped by BasePlayer.StopServerDemoRecording and the recording file has been closed.

Signature
void OnDemoRecordingStopped(string recordFilename, BasePlayer player)

Parameters

Hook parameters
ParameterTypeDescription
recordFilenamestringFile name of the demo recording that was just finalized, as stored in the connection's recordFilename property (relative to the server's demo directory).
playerBasePlayerThe BasePlayer whose server-side demo recording was stopped.

Return value

The return value is ignored; the original method has already completed its stop logic and the hook fires as the last step. Returning null or any value changes nothing.

How it works

Fires inside BasePlayer.StopServerDemoRecording, after the connection's recording has been stopped (net.connection.StopRecording()) and the demo action monitor invoke has been cancelled. The call happens only when the player has a network connection, that connection is currently recording, and the earlier OnDemoRecordingStop hook does not block the stop. The recordFilename argument is the name the demo was written under, and player is the target of the recording. This hook is a notification only: there is no earlier stop method to widen into a base class, so a hook declared with a different signature than (string, BasePlayer) simply never matches. Use it to track when recordings end, log them, notify staff, or move/announce the finished file. The recording has already been closed by the time the hook fires, so the file is safe to reference. Declaring player as BasePlayer is the only correct type because the argument is always a BasePlayer.

Example plugin: Announce finished demo recordings to staff

DemoRecordingAnnouncer.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Demo Recording Announcer", "RustPluginGenerator", "1.0.0")]
    [Description("Notifies configured staff when a player's server-side demo recording finishes.")]
    public class DemoRecordingAnnouncer : RustPlugin
    {
        private const string PermView = "demorecordingannouncer.view";

        private PluginConfig config;
        private StoredData data;

        private class PluginConfig
        {
            [JsonProperty("Staff Steam IDs to notify")]
            public List<string> StaffIds = new List<string>();

            [JsonProperty("Maximum stored recordings per player")]
            public int MaxRecordsPerPlayer = 10;
        }

        private class StoredData
        {
            [JsonProperty("Recordings by player ID")]
            public Dictionary<string, List<string>> Recordings = new Dictionary<string, List<string>>();
        }

        protected override void LoadDefaultConfig() => config = new PluginConfig();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<PluginConfig>();
                if (config == null) throw new Exception();
            }
            catch
            {
                PrintWarning("Configuration file is invalid, using defaults");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["StaffNotice"] = "Demo recording finished for {0} ({1}): {2}"
            }, this);
        }

        private string Msg(string key, string userId = null, params object[] args) => string.Format(lang.GetMessage(key, this, userId), args);

        private void Init()
        {
            permission.RegisterPermission(PermView, this);
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

        private void OnServerInitialized()
        {
            if (config.StaffIds.Count == 0)
                PrintWarning("No staff Steam IDs configured; recording notices will be suppressed.");
        }

        private void OnDemoRecordingStopped(string recordFilename, BasePlayer player)
        {
            if (player == null || string.IsNullOrEmpty(recordFilename)) return;
            if (player.net == null || player.net.connection == null) return;

            Record(player.UserIDString, recordFilename);

            if (config.StaffIds.Count == 0) return;

            foreach (var id in config.StaffIds)
            {
                var staff = BasePlayer.FindByID(ulong.Parse(id));
                if (staff == null || !staff.IsConnected) continue;
                if (!permission.UserHasPermission(staff.UserIDString, PermView)) continue;
                staff.ChatMessage(Msg("StaffNotice", staff.UserIDString, player.displayName, player.UserIDString, recordFilename));
            }
        }

        private void Record(string userId, string filename)
        {
            if (!data.Recordings.TryGetValue(userId, out var list))
            {
                list = new List<string>();
                data.Recordings[userId] = list;
            }
            list.Add(filename);
            while (list.Count > config.MaxRecordsPerPlayer)
                list.RemoveAt(0);
        }

        private void Unload()
        {
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }
    }
}

The plugin keeps a list of Steam IDs that should be notified when a demo recording finishes. When OnDemoRecordingStopped fires it null-checks the player and empty filename, then sends a formatted chat message to every configured staff member who is online. It also stores a short record of the last recording per player, capped to avoid unbounded growth, and writes it to oxide/data on unload. Everything flows through the language file so messages stay configurable, and the example keeps timers and data handling minimal.

Common use cases

  • Staff notification

    Send an alert to admins when a player's recording finishes so they know a demo file is ready for review.

  • Recording audit log

    Append the player name, ID, and record filename to an in-memory list that is saved to oxide/data for later inspection.

  • Recording quotas

    Increment a per-player counter each time a recording stops and warn or flag players who exceed a configured daily limit.

Pitfalls

  • Not a blocking hook

    The stop has already happened and the file is closed. You cannot use this hook to prevent a recording from being stopped; use OnDemoRecordingStop for that.

  • Only fires for actual stops

    The hook is skipped if the player has no connection, the connection is not recording, or OnDemoRecordingStop returned a non-null value. Do not assume a stop event for every player disconnect.

  • Check the filename

    Treat recordFilename defensively, such as when the file was cleaned up by another process; guard against a null or empty string before using it in messages.

Build a plugin with OnDemoRecordingStopped

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Staff notification using the OnDemoRecordingStopped hook, with a config file and a permission
Generate this plugin

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.

Build a plugin with OnDemoRecordingStopped

Generate