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:
- Checks
CanRecord()
: Periodically checks if yourCanRecord
delegate returnstrue
. - 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). - Stops Recording: After
RecordingDurationSeconds
, it stops recording. - 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. - 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. - 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.
- Enable Flag: Set the
isUsingExistingUnityMic
field totrue
on thePlaySafeManager
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 yourGetTelemetry
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
- 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:
- Delete their punishment
- Send a webhook request to your configured webhook URLs with the
forgive
action - Create a new
forgive
action log
- 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"
- You can forgive a player via the live feed, playground or player profile dialog.
- You have the option to either keep the strikes that a player has or reset them
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.