Skip to main content

Moderation

PlaySafe Moderation provides a suite of tools designed to help you create a safer and more positive environment in your Unity games by addressing in-game toxicity. These tools allow you to automatically analyze voice chat for harmful behavior, enforce moderation policies, and enable players to report or mute others.

This guide explains how to integrate and use the core moderation features of the PlaySafe SDK.

Core Concepts & Setup

Before using moderation features, ensure you have followed the Quickstart Guide to install the SDK and set up the PlaySafeManager component in your scene.

The PlaySafeManager relies on several delegates (callbacks) that you must implement in your own script and assign before calling PlaySafeManager.Instance.Initialize(). These delegates connect the SDK to your game's specific logic.

Here's a typical setup pattern, often placed in a script attached to the same GameObject as the PlaySafeManager or another central manager script:

using System;
using _DL.PlaySafe;
using UnityEngine;

public class MyGameModeration : MonoBehaviour
{
[SerializeField] PlaySafeManager playSafeManager; // Assign in Inspector

void Start()
{
// --- Assign Delegates BEFORE Initializing ---
playSafeManager.CanRecord = ShouldRecordAudio;
playSafeManager.GetTelemetry = ProvideTelemetryData;
playSafeManager.OnActionEvent = HandleModerationAction;
// ------------------------------------------

// Initialize PlaySafeManager
playSafeManager.Initialize();
}

// --- Delegate Implementations (Examples Below) ---

private bool ShouldRecordAudio()
{
// Your logic here...
return true; // Placeholder
}

private PlaySafeManager.AudioEventRequestData ProvideTelemetryData()
{
// Your logic here...
return new PlaySafeManager.AudioEventRequestData { /* ... */ }; // Placeholder
}

private void HandleModerationAction(ActionItem action, DateTime serverTime)
{
// Your logic here...
}
}

Let's look at each delegate:

1. CanRecord Delegate

This delegate (Func<bool>) tells the PlaySafeManager whether it's currently appropriate to record the local player's microphone audio for analysis. You should return true only when conditions for recording are met according to your game's logic and privacy considerations.

Example Implementation:

private bool ShouldRecordAudio()
{
// Your game's logic determines when to record. Examples:
bool isPlayerMutedLocally = MyGameManager.Instance.IsPlayerMuted; // e.g., user setting
bool isInMultiplayerScene = MyGameManager.Instance.CurrentScene == "MultiplayerLobby";
int otherPlayersInRoom = MyGameManager.Instance.GetPlayerCount() - 1;

// Only record if the player is not muted, is in a multiplayer area,
// and there's at least one other player present.
return !isPlayerMutedLocally && isInMultiplayerScene && otherPlayersInRoom > 0;
}

2. GetTelemetry Delegate

This delegate (Func<AudioEventRequestData>) provides essential context whenever audio is sent for analysis or a report is made. The AudioEventRequestData object requires:

  • UserId: A unique, persistent identifier for the local player.
  • RoomId: An identifier for the current game session, room, or server the player is in.
  • Language: (Optional) The player's language code (e.g., "en", "es") if known. Helps improve analysis accuracy.

Example Implementation:

private PlaySafeManager.AudioEventRequestData ProvideTelemetryData()
{
string currentUserId = MyGameManager.Instance.LocalPlayerUserId; // Get from your player management system
string currentRoomId = MyGameManager.Instance.CurrentRoomName; // Get from your room/session system
string playerLanguage = Application.systemLanguage.ToString(); // Example: Use system language

return new PlaySafeManager.AudioEventRequestData()
{
UserId = currentUserId,
RoomId = currentRoomId,
Language = playerLanguage,
};
}

3. OnActionEvent Delegate

This delegate (Action<ActionItem, DateTime>) is called when the PlaySafe backend completes an audio analysis and recommends a moderation action (like a timeout or mute) based on detected violations.

  • ActionItem: Contains details about the recommended action:
    • Action (string): The type of action (e.g., "TIMEOUT", "MUTE").
    • Reason (string): The reason for the action (e.g., "HATE_SPEECH", "EXCESSIVE_SWEARING").
    • DurationInMinutes (int): The recommended duration for the action in minutes.
  • serverTime (DateTime): The server's timestamp (UTC) when the analysis was completed. This is crucial for accurately calculating when a temporary action (like a timeout) should expire.

Example Implementation:

private void HandleModerationAction(ActionItem action, DateTime serverTime)
{
Debug.Log($"[PlaySafe Action] Received: {action.Action}, Reason: {action.Reason}, Duration: {action.DurationInMinutes} mins. Server Time: {serverTime}");

if (action.Action == "TIMEOUT")
{
// Calculate when the timeout expires using the server's time
DateTime timeoutExpiresUtc = serverTime + TimeSpan.FromMinutes(action.DurationInMinutes);

// Store the expiration time and disable voice chat for the player
MyGameManager.Instance.SetVoiceChatTimeout(timeoutExpiresUtc);

// Inform the player
string durationText = action.DurationInMinutes >= 60 ?
$"{(action.DurationInMinutes / 60f):F1} hours" :
$"{action.DurationInMinutes} minutes";
UIManager.Instance.ShowNotification($"Voice chat disabled for {durationText} due to: {action.Reason}");

Debug.Log($"[PlaySafe Action] Player voice timed out until: {timeoutExpiresUtc} UTC");
}
else if (action.Action == "MUTE")
{
// Potentially apply a persistent mute based on game rules
MyGameManager.Instance.ApplyPersistentMute();
UIManager.Instance.ShowNotification($"You have been muted due to: {action.Reason}");
}
// Handle other potential actions as needed
}

Audio Processing & Analysis

Once initialized with the required delegates, PlaySafeManager automatically handles the audio recording lifecycle:

  1. Checks CanRecord(): Periodically checks if your CanRecord delegate returns true.
  2. Starts Recording: If CanRecord() is true and the configured intermission (_recordingIntermissionSeconds, default 60s, potentially updated from server) has passed since the last recording stopped, it starts recording the microphone for a fixed duration (RecordingDurationSeconds, default 10s).
  3. Stops Recording: After RecordingDurationSeconds, it stops recording.
  4. Processes Audio: Converts the recorded AudioClip into WAV format. It also performs a basic silence check (_silenceThreshold) – silent clips are discarded to save bandwidth and processing.
  5. Sends for Analysis: If the clip is not silent, it calls your GetTelemetry() delegate to get context (UserID, RoomID) and sends the WAV audio data and telemetry to the PlaySafe /products/moderation endpoint for analysis.
  6. Handles Response: When the analysis is complete, the response is processed. If moderation actions are recommended, your OnActionEvent delegate is invoked.

Using Existing Audio Streams (e.g., Photon Voice)

If your game already captures microphone audio using another system like Photon Voice, you can configure PlaySafeManager to use that audio stream instead of directly accessing the microphone.

  1. Enable Flag: Set the isUsingExistingUnityMic field to true on the PlaySafeManager component in the Inspector.

Manual Player Reporting & Muting

Beyond automatic voice analysis, PlaySafe allows players to manually report or mute others. This is done via the ReportUser coroutine.

Function Signature:

public IEnumerator ReportUser(string reporterUserId, string targetUserId, string eventType)
  • reporterUserId: The unique ID of the player initiating the report/mute action. Get this from your GetTelemetry delegate or player management system.
  • targetUserId: The unique ID of the player being reported or muted.
  • eventType: A string indicating the type of action. Common values are:
    • "report": Standard player report for misconduct.
    • "mute": Player mutes another player (often client-side preference, but logged).

Usage:

You need to call this function using StartCoroutine.

Example: Reporting a Player

using _DL.PlaySafe; // Make sure namespace is included
using UnityEngine;

// Or whatever game class you have that handles reports/mutes
public class PlayerInteractionManager : MonoBehaviour
{
// This is an example implementation - in reality, you'd use your game's system to get the current playerId and target playerId
public void ReportPlayerButtonClicked(string targetPlayerId)
{
string currentPlayerId = MyGameManager.Instance.LocalPlayerUserId; // Get local player's ID

if (string.IsNullOrEmpty(currentPlayerId) || string.IsNullOrEmpty(targetPlayerId))
{
Debug.LogError("Cannot report player: Missing user ID.");
return;
}

// Prevent spamming reports if needed
if (CanSendReport())
{
StartCoroutine(PlaySafeManager.Instance.ReportUser(currentPlayerId, targetPlayerId, "report"));
Debug.Log($"Reporting player {targetPlayerId}...");
// Potentially disable report button temporarily
}
else
{
UIManager.Instance.ShowNotification("You must wait before sending another report.");
}
}
}

Example: Toggling Mute on a Player

using _DL.PlaySafe; // Make sure namespace is included
using UnityEngine;

public class PlayerInteractionManager : MonoBehaviour
{
public void MutePlayerButtonClicked(string targetPlayerId, bool isCurrentlyMuted)
{
string localPlayerId = MyGameManager.Instance.LocalPlayerUserId; // Get local player's ID

if (string.IsNullOrEmpty(localPlayerId) || string.IsNullOrEmpty(targetPlayerId))
{
Debug.LogError("Cannot mute/unmute player: Missing user ID.");
return;
}

string eventType = isCurrentlyMuted ? "unmute" : "mute";

// Call PlaySafe to log the action
StartCoroutine(PlaySafeManager.Instance.ReportUser(localPlayerId, targetPlayerId, eventType));

// Apply the mute/unmute state in your game's audio system
MyGameAudioManager.Instance.SetPlayerMuteState(targetPlayerId, !isCurrentlyMuted);

Debug.Log($"Player {targetPlayerId} {eventType} action sent.");
}
}

These reports and mute actions are sent to the PlaySafe backend and associated with the players involved, providing valuable data for review and context alongside automated voice analysis on the PlaySafe Dashboard.

Unbanning / Forgiving Players

In some scenarios, you may want to unban players that have been either automatically banned by PlaySafe or by a moderator. In such cases, you would "forgive" those players.

Understanding how forgiveness works

  1. Forgiving a player clears their active action log
  • This means that if a player was banned for 24 hours and they are still banned, once you forgive them, PlaySafe will:
  1. To ensure that a player is unbanned in your game, you MUST handle the unbanning on your backend.

Where you can forgive a player from in PlaySafe

Here's what you can do with the forgive feature. In any audio events table, click on "Take Action" Take action button screenshot

  1. You can forgive a player via the live feed, playground or player profile dialog.
  2. You have the option to either keep the strikes that a player has or reset them

Forgive popover screenshot

Handling forgiveness on your backend

In the webhook URL endpoint you have specified, you will receive a POST request that has a body that looks something like this.

{
"endDate": "2025-07-10T06:43:53.573Z",
"trigger": "manual_forgiveness",
"audioUrl": "",
"isActive": false,
"productId": "eb70b6f8-5a25-49c7-9be2-a15cd8fa1d19",
"transcript": "",
"actionValue": "forgive",
"description": "Player was manually forgiven by a moderator",
"playerUserId": "playground-player-user-id",
"delayInSeconds": 0,
"triggersStrike": false,
"durationInMinutes": 0,
"actionFriendlyName": "Forgive Player"
}

You can also view the request body sent in your PlaySafe dashboard webhooks section. If the actionValue is forgive, then you can proceed to unban the player using your game's unban business logic.