SCPSL-AudioManagerAPI
1.6.0
See the version list below for details.
dotnet add package SCPSL-AudioManagerAPI --version 1.6.0
NuGet\Install-Package SCPSL-AudioManagerAPI -Version 1.6.0
<PackageReference Include="SCPSL-AudioManagerAPI" Version="1.6.0" />
<PackageVersion Include="SCPSL-AudioManagerAPI" Version="1.6.0" />
<PackageReference Include="SCPSL-AudioManagerAPI" />
paket add SCPSL-AudioManagerAPI --version 1.6.0
#r "nuget: SCPSL-AudioManagerAPI, 1.6.0"
#:package SCPSL-AudioManagerAPI@1.6.0
#addin nuget:?package=SCPSL-AudioManagerAPI&version=1.6.0
#tool nuget:?package=SCPSL-AudioManagerAPI&version=1.6.0
SCPSL-AudioManagerAPI
A lightweight, reusable C# library for managing audio playback in SCP: Secret Laboratory (SCP:SL) plugins using LabAPI. Designed to integrate seamlessly with Northwood’s LabAPI ecosystem, it provides a robust system for loading, caching, and playing audio through SpeakerToy
instances, with centralized controller ID management, advanced audio control (volume, position, range, spatialization, fading, queuing), speaker lifecycle management, and prioritization for enhanced gameplay.
⚠️ Warning
This plugin is currently in active development. New features are being added regularly, and not all functionality has been fully tested in live gameplay.
If you encounter any issues or bugs, please report them on the official GitHub repository.
Description
SCPSL-AudioManagerAPI
is a robust library for managing spatial and global audio in SCP: Secret Laboratory server-side plugins. Built on top of LabAPI’s SpeakerToy
, it provides a high-level interface for playing audio clips, managing speaker lifecycles, and applying player-specific filters. With thread-safe operations and integration with ControllerIdManager
, it ensures seamless audio playback across SCP:SL’s multi-threaded environment. The library supports both simple and advanced use cases, from ambient sounds to role-specific announcements, without requiring Exiled dependencies. Version 1.6.0 introduces enhanced filtering for global audio with the PlayGlobalAudioWithFilter
method and improved documentation distinguishing spatial and non-spatial audio.
Features
- Spatial Audio: Play audio at specific world positions with customizable volume, range, and spatialization.
- Global Audio: Broadcast audio to all players, ideal for announcements or events.
- Global Audio with Filters: Broadcast audio to specific players using custom filters via
PlayGlobalAudioWithFilter
, perfect for targeted announcements or events. - Player Filters: Restrict audio to specific players based on role, team, room, or custom conditions using
AudioFilters
. - Thread-Safe Management: Handles concurrent audio operations with a shared, thread-safe speaker registry.
- Controller ID System: Integrates with
ControllerIdManager
for unique speaker IDs (1-255) across plugins. - Audio Caching: Uses
AudioCache
with LRU eviction for efficient audio sample management. - Flexible Factories: Offers
StaticSpeakerFactory
for simple static access andDefaultSpeakerFactory
for advanced control. - Persistence: Supports persistent speaker states for recovery after interruptions.
- Priority and Queuing: Manages audio priorities (
AudioPriority
) and queues for smooth playback.
Installation
Install the SCPSL-AudioManagerAPI
package via NuGet:
dotnet add package SCPSL-AudioManagerAPI --version 1.6.0
Ensure you have the following dependencies in your SCP:SL plugin project:
LabApi
(version 1.1.0 or compatible)UnityEngine.CoreModule
MEC
(for coroutines)
Project Setup
Add the SCPSL-AudioManagerAPI
package to your SCP:SL plugin project. Ensure you reference UnityEngine.CoreModule
for Vector3
, LabApi
for SpeakerToy
and Player
, and MEC
for coroutines (used for fading and lifespan management).
Example .csproj
snippet:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SCPSL-AudioManagerAPI" Version="1.6.0" />
<Reference Include="LabApi">
<HintPath>path\to\LabApi.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>path\to\UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="MEC">
<HintPath>path\to\MEC.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
Replace path\to\
with the actual paths to the dependency DLLs in your SCP:SL server installation (e.g., SCPSL_Data\Managed
).
Usage
1. Using DefaultAudioManager (Recommended)
The DefaultAudioManager
provides a plug-and-play interface for common audio tasks, ideal for most SCP:SL plugins.
using AudioManagerAPI.Defaults;
using UnityEngine;
using LabApi.Features.Console;
public class AudioPlugin
{
public void Initialize()
{
DefaultAudioManager.RegisterDefaults(cacheSize: 50);
DefaultAudioManager.RegisterAudio("ambientSound", () => Assembly.GetExecutingAssembly().GetManifestResourceStream("MyPlugin.Audio.ambient.wav"));
}
public void PlayAudio()
{
byte id = DefaultAudioManager.Play("ambientSound");
DefaultAudioManager.Play("announcement", queue: true);
DefaultAudioManager.FadeIn(id, 2f);
DefaultAudioManager.Pause(id);
DefaultAudioManager.Resume(id);
DefaultAudioManager.Skip(id, 1);
DefaultAudioManager.SetVolume(id, 0.5f);
DefaultAudioManager.SetPosition(id, new Vector3(10f, 0f, 0f));
DefaultAudioManager.ClearQueue(id);
DefaultAudioManager.FadeOut(id, 2f);
DefaultAudioManager.Stop(id);
}
public void PlayGlobalAudioWithFilter()
{
byte id = DefaultAudioManager.Instance.PlayGlobalAudioWithFilter(
key: "ambientSound",
loop: false,
volume: 0.8f,
priority: AudioPriority.High,
configureSpeaker: speaker =>
{
if (speaker is ISpeakerWithPlayerFilter filterSpeaker)
{
filterSpeaker.SetValidPlayers(p => p.Role == RoleTypeId.Scp173);
}
},
queue: false,
fadeInDuration: 1f,
persistent: true,
lifespan: 10f,
autoCleanup: true
);
if (id != 0)
{
Logger.Info($"[AudioPlugin] Played global audio with filter for SCP-173 with controller ID {id}.");
}
else
{
Logger.Warn("[AudioPlugin] Failed to play global audio with filter.");
}
}
}
2. Using StaticSpeakerFactory (Recommended for Simple Speaker Management)
The StaticSpeakerFactory
in the AudioManagerAPI.Features.Static
namespace provides a static interface for managing DefaultSpeakerToyAdapter
instances. It leverages a shared, thread-safe speaker registry aligned with the global ControllerIdManager
, ideal for simple audio playback scenarios.
using AudioManagerAPI.Features.Static;
using AudioManagerAPI.Features.Speakers;
using UnityEngine;
using LabApi.Features.Console;
using LabApi.Player;
public class AudioPlugin
{
public void PlayZoneSound(Vector3 position, string zoneName)
{
byte controllerId = 1;
ISpeaker speaker = StaticSpeakerFactory.GetSpeaker(controllerId) ?? StaticSpeakerFactory.CreateSpeaker(position, controllerId);
if (speaker != null)
{
float[] audioSamples = GetAudioSamples(); // Assume method to get PCM samples
speaker.Play(audioSamples, loop: false);
speaker.SetVolume(0.8f);
speaker.SetSpatialization(true);
speaker.SetMaxDistance(20f);
if (speaker is ISpeakerWithPlayerFilter filterSpeaker)
{
filterSpeaker.SetValidPlayers(p => p.CurrentRoom?.Zone == zoneName);
}
Logger.Info($"[AudioPlugin] PlayZoneSound: Playing sound with controller ID {controllerId} in zone {zoneName}.");
}
else
{
Logger.Warn($"[AudioPlugin] PlayZoneSound: Failed to get or create speaker for controller ID {controllerId}.");
}
}
public void RemoveSpeaker(byte controllerId)
{
if (StaticSpeakerFactory.RemoveSpeaker(controllerId))
{
Logger.Info($"[AudioPlugin] RemoveSpeaker: Removed speaker for controller ID {controllerId}.");
}
else
{
Logger.Warn($"[AudioPlugin] RemoveSpeaker: No speaker found for controller ID {controllerId}.");
}
}
public void OnRoundEnded()
{
StaticSpeakerFactory.ClearSpeakers();
Logger.Info($"[AudioPlugin] OnRoundEnded: Cleared all speakers for round restart.");
}
}
3. Using DefaultSpeakerFactory (Advanced)
The DefaultSpeakerFactory
in the AudioManagerAPI.Defaults
namespace provides an instantiable factory for advanced use cases, compatible with AudioManager
. Use StaticSpeakerFactory
for simpler, static access.
using AudioManagerAPI.Defaults;
using AudioManagerAPI.Features.Speakers;
using UnityEngine;
using LabApi.Features.Console;
using LabApi.Player;
public class AdvancedAudioPlugin
{
private readonly ISpeakerFactory speakerFactory;
public AdvancedAudioPlugin()
{
speakerFactory = new DefaultSpeakerFactory();
}
public void PlayRoleBasedSound(Vector3 position, string roleType)
{
byte controllerId = 1;
ISpeaker speaker = speakerFactory.GetSpeaker(controllerId) ?? speakerFactory.CreateSpeaker(position, controllerId);
if (speaker != null)
{
float[] audioSamples = GetAudioSamples(); // Assume method to get PCM samples
speaker.Play(audioSamples, loop: false);
speaker.SetVolume(0.8f);
speaker.SetSpatialization(true);
speaker.SetMaxDistance(20f);
if (speaker is ISpeakerWithPlayerFilter filterSpeaker)
{
filterSpeaker.SetValidPlayers(p => p.Role.ToString() == roleType);
}
Logger.Info($"[AdvancedAudioPlugin] PlayRoleBasedSound: Playing sound with controller ID {controllerId} for role {roleType}.");
}
else
{
Logger.Warn($"[AdvancedAudioPlugin] PlayRoleBasedSound: Failed to get or create speaker for controller ID {controllerId}.");
}
}
public void RemoveSpeaker(byte controllerId)
{
if (speakerFactory.RemoveSpeaker(controllerId))
{
Logger.Info($"[AdvancedAudioPlugin] RemoveSpeaker: Removed speaker for controller ID {controllerId}.");
}
else
{
Logger.Warn($"[AdvancedAudioPlugin] RemoveSpeaker: No speaker found for controller ID {controllerId}.");
}
}
public void OnRoundEnded()
{
speakerFactory.ClearSpeakers();
Logger.Info($"[AdvancedAudioPlugin] OnRoundEnded: Cleared all speakers for round restart.");
}
}
4. Initialize AudioManager (Advanced)
Create an instance of AudioManager
for advanced control, suitable for complex plugins requiring custom audio management. Use StaticSpeakerFactory.Instance
for static access.
using AudioManagerAPI;
using AudioManagerAPI.Features.Static;
using AudioManagerAPI.Features.Management;
using System;
using System.Reflection;
using UnityEngine;
using LabApi.Features.Console;
using LabApi.Player;
public class CustomAudioManager
{
private readonly IAudioManager audioManager;
public CustomAudioManager()
{
audioManager = new AudioManager(StaticSpeakerFactory.Instance, cacheSize: 20);
RegisterAudioResources();
}
private void RegisterAudioResources()
{
var assembly = Assembly.GetExecutingAssembly();
audioManager.RegisterAudio("ambientSound", () =>
assembly.GetManifestResourceStream("MyPlugin.Audio.ambient.wav"));
}
public void PlayZoneSound(Vector3 position, string zoneName)
{
byte controllerId = audioManager.PlayAudio(
key: "ambientSound",
position: position,
loop: false,
volume: 0.8f,
minDistance: 5f,
maxDistance: 50f,
isSpatial: true,
priority: AudioPriority.High,
configureSpeaker: speaker =>
{
if (speaker is ISpeakerWithPlayerFilter filterSpeaker)
{
filterSpeaker.SetValidPlayers(p => p.CurrentRoom?.Zone == zoneName);
}
},
queue: false,
persistent: true
);
if (audioManager.IsValidController(controllerId))
{
audioManager.FadeInAudio(controllerId, 1f);
audioManager.SetSpeakerPosition(controllerId, new Vector3(position.x + 5f, position.y, position.z));
Logger.Info($"[CustomAudioManager] PlayZoneSound: Played sound with controller ID {controllerId} in zone {zoneName}.");
}
else
{
Logger.Warn($"[CustomAudioManager] PlayZoneSound: Failed to play sound in zone {zoneName}.");
}
}
public void PlayGlobalAnnouncement()
{
byte controllerId = audioManager.PlayGlobalAudio(
key: "ambientSound",
loop: false,
volume: 0.8f,
priority: AudioPriority.High,
queue: true
);
if (audioManager.IsValidController(controllerId))
{
audioManager.FadeInAudio(controllerId, 1f);
audioManager.ClearSpeakerQueue(controllerId);
Logger.Info($"[CustomAudioManager] PlayGlobalAnnouncement: Played global sound with controller ID {controllerId}.");
}
}
}
5. Using Audio Filters
The AudioFilters
class in the AudioManagerAPI.Features.Filters
namespace provides predefined filters to control which players hear audio from a speaker. These filters are accessible via StaticSpeakerFactory.AudioFilters
for convenience and can be used with ISpeakerWithPlayerFilter.SetValidPlayers
to target specific players based on role, team, position, room, or custom conditions. Filters can be combined to create complex audio playback scenarios, such as playing audio only to certain roles in specific rooms during events like blackouts.
Available Filters
ByRole(RoleTypeId roleType)
: Filters players by their role (e.g.,RoleTypeId.Scp173
).ByTeam(Team team)
: Filters players by their team (e.g.,Team.SCP
).ByDistance(Vector3 position, float maxDistance)
: Filters players within a specified distance from a position.IsAlive()
: Filters players who are alive.IsInRoomWhereLightsAre(bool lightsEnabled)
: Filters players in rooms with lights enabled (true
) or disabled (false
).IsConditionTrue(bool condition)
: Filters players based on a boolean condition (e.g., event active).IsInRoom(RoomName roomType)
: Filters players in a specific room type (e.g.,RoomName.EzIntercom
).
Example: Playing Audio for SCPs During a Blackout
This example plays audio only to SCP players who are alive and in a dark room during a blackout event using PlayGlobalAudioWithFilter
.
using AudioManagerAPI.Defaults;
using UnityEngine;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using MapGeneration;
using PlayerRoles;
public class BlackoutAudioPlugin
{
public void PlayBlackoutSound()
{
bool isBlackoutActive = true; // Example: Check blackout event state
byte controllerId = DefaultAudioManager.Instance.PlayGlobalAudioWithFilter(
key: "blackoutSound",
loop: false,
volume: 0.8f,
priority: AudioPriority.High,
configureSpeaker: speaker =>
{
if (speaker is ISpeakerWithPlayerFilter filterSpeaker)
{
filterSpeaker.SetValidPlayers(p =>
p.Team == Team.SCP &&
p.IsAlive &&
p.CurrentRoom != null &&
!p.CurrentRoom.LightsEnabled &&
isBlackoutActive
);
}
},
queue: false,
fadeInDuration: 1f,
persistent: true,
lifespan: 10f,
autoCleanup: true
);
if (controllerId != 0)
{
Logger.Info($"[BlackoutAudioPlugin] Playing blackout sound for SCPs with controller ID {controllerId}.");
}
else
{
Logger.Warn("[BlackoutAudioPlugin] Failed to play blackout sound.");
}
}
}
Example: Playing Audio in a Specific Room
This example plays audio only to players in the EZ Intercom room who are alive.
using AudioManagerAPI.Features.Static;
using UnityEngine;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using MapGeneration;
public class IntercomAudioPlugin
{
public void PlayIntercomSound(Vector3 position)
{
byte controllerId = 2;
ISpeaker speaker = StaticSpeakerFactory.GetSpeaker(controllerId) ?? StaticSpeakerFactory.CreateSpeaker(position, controllerId);
if (speaker != null && speaker is ISpeakerWithPlayerFilter filterSpeaker)
{
float[] audioSamples = GetAudioSamples(); // Assume method to get PCM samples
filterSpeaker.SetValidPlayers(new[]
{
StaticSpeakerFactory.AudioFilters.IsInRoom(RoomName.EzIntercom),
StaticSpeakerFactory.AudioFilters.IsAlive()
});
speaker.Play(audioSamples, loop: false);
speaker.SetVolume(0.9f);
speaker.SetSpatialization(true);
speaker.SetMaxDistance(15f);
Logger.Info($"[IntercomAudioPlugin] Playing sound in EZ Intercom at position {position}.");
}
else
{
Logger.Warn($"[IntercomAudioPlugin] Failed to get or create speaker for controller ID {controllerId}.");
}
}
public void OnRoundEnded()
{
StaticSpeakerFactory.ClearSpeakers();
Logger.Info("[IntercomAudioPlugin] Cleared all speakers for round restart.");
}
}
Example: Proximity-Based Audio for Scientists
This example plays audio only to Scientists within 10 units of the speaker’s position.
using AudioManagerAPI.Features.Static;
using UnityEngine;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using PlayerRoles;
public class ProximityAudioPlugin
{
public void PlayProximitySound(Vector3 position)
{
byte controllerId = 3;
ISpeaker speaker = StaticSpeakerFactory.GetSpeaker(controllerId) ?? StaticSpeakerFactory.CreateSpeaker(position, controllerId);
if (speaker != null && speaker is ISpeakerWithPlayerFilter filterSpeaker)
{
float[] audioSamples = GetAudioSamples(); // Assume method to get PCM samples
filterSpeaker.SetValidPlayers(new[]
{
StaticSpeakerFactory.AudioFilters.ByRole(RoleTypeId.Scientist),
StaticSpeakerFactory.AudioFilters.ByDistance(position, 10f)
});
speaker.Play(audioSamples, loop: false);
speaker.SetVolume(0.7f);
speaker.SetSpatialization(true);
speaker.SetMaxDistance(10f);
Logger.Info($"[ProximityAudioPlugin] Playing sound for Scientists near position {position}.");
}
else
{
Logger.Warn($"[ProximityAudioPlugin] Failed to get or create speaker for controller ID {controllerId}.");
}
}
public void OnRoundEnded()
{
StaticSpeakerFactory.ClearSpeakers();
Logger.Info("[ProximityAudioPlugin] Cleared all speakers for round restart.");
}
}
Notes on Using AudioFilters
- Accessing Filters: Use
StaticSpeakerFactory.AudioFilters
to access the predefined filters, which are defined inAudioManagerAPI.Features.Filters.AudioFilters
. - Combining Filters: Pass multiple filters to
SetValidPlayers(IEnumerable<Func<Player, bool>>)
. A player must pass all filters to hear the audio. - Performance: Filters like
ByDistance
involve calculations. Cache the position parameter if used frequently (e.g., per frame for many players). - Thread Safety: Filters are stateless and thread-safe, compatible with the thread-safe
StaticSpeakerFactory
andDefaultSpeakerFactory
. - Logging: Filters include logging for edge cases (e.g., null players or rooms) using
LabApi.Features.Console.Logger
. Check logs for debugging. - Custom Filters: For scenarios not covered by predefined filters, use
SetValidPlayers
with a customFunc<Player, bool>
(e.g.,p => p.CurrentRoom?.Zone == "HeavyContainmentZone"
).
Extension Methods
The AudioManagerAPI
provides extension methods for IAudioManager
to simplify common operations:
using AudioManagerAPI.Extensions;
public void Example()
{
IAudioManager audioManager = new AudioManager(StaticSpeakerFactory.Instance, cacheSize: 20);
audioManager.Play("ambientSound", new Vector3(10f, 0f, 0f));
audioManager.FadeIn(1, 2f);
audioManager.SetVolume(1, 0.5f);
}
Manage Speakers
Use StaticSpeakerFactory
or DefaultSpeakerFactory
to create and manage speakers directly:
using AudioManagerAPI.Features.Static;
using AudioManagerAPI.Features.Speakers;
using UnityEngine;
public void ManageSpeakers()
{
ISpeaker speaker = StaticSpeakerFactory.CreateSpeaker(new Vector3(0f, 0f, 0f), 1);
if (speaker != null)
{
speaker.Play(GetAudioSamples(), loop: false);
StaticSpeakerFactory.RemoveSpeaker(1);
}
StaticSpeakerFactory.ClearSpeakers();
}
Audio Requirements
- Format: Audio clips must be WAV files, loaded as PCM samples (e.g., via
Assembly.GetManifestResourceStream
). - Registration: Register audio with
IAudioManager.RegisterAudio
before playback. - Caching: Use
AudioCache
for efficient memory management, specifyingcacheSize
inAudioManager
orDefaultAudioManager
.
Audio Control and Prioritization
- Priority: Use
AudioPriority
(Low, Medium, High) to manage playback precedence. - Queuing: Queue audio clips with
queue: true
to play sequentially. - Fading: Use
FadeInAudio
andFadeOutAudio
for smooth transitions. - Validation: Check
IsValidController
before manipulating speakers.
API Reference
Key Classes and Interfaces
Name | Namespace | Description |
---|---|---|
IAudioManager |
AudioManagerAPI.Features.Management |
Defines the contract for audio playback and speaker lifecycle management. |
AudioManager |
AudioManagerAPI.Features.Management |
Implements audio management with caching and shared controller IDs. |
ISpeaker |
AudioManagerAPI.Features.Speakers |
Represents a speaker for playing, queuing, pausing, resuming, skipping, and fading audio. |
ISpeakerWithPlayerFilter |
AudioManagerAPI.Features.Speakers |
Extends ISpeaker to support player-specific audibility, volume, position, range, and spatialization. |
ISpeakerFactory |
AudioManagerAPI.Features.Speakers |
Defines a factory for creating and managing speaker instances. |
AudioCache |
AudioManagerAPI.Cache |
Manages audio samples with LRU eviction and lazy loading. |
ControllerIdManager |
AudioManagerAPI.Controllers |
Static class for managing unique controller IDs with priority-based eviction and queuing. |
AudioPriority |
AudioManagerAPI.Features.Enums |
Enum defining audio priority levels (Low, Medium, High). |
DefaultAudioManager |
AudioManagerAPI.Defaults |
Simplifies audio management with default settings and convenience methods. |
DefaultSpeakerToyAdapter |
AudioManagerAPI.Defaults |
Default LabAPI SpeakerToy adapter with full feature support. |
DefaultSpeakerFactory |
AudioManagerAPI.Defaults |
Instantiable factory for creating and managing DefaultSpeakerToyAdapter instances with a thread-safe registry. |
StaticSpeakerFactory |
AudioManagerAPI.Features.Static |
Static wrapper around DefaultSpeakerFactory for simplified speaker management, aligned with global ControllerIdManager . |
AudioFilters |
AudioManagerAPI.Features.Filters |
Provides predefined filters for controlling which players hear audio based on role, team, position, room, or custom conditions. |
SpeakerState |
AudioManagerAPI.Speakers.State |
Stores persistent speaker state for recovery (position, volume, queued clips). |
Key Methods
IAudioManager.PlayAudio
: Plays spatial audio at a specific position with configurable parameters.IAudioManager.PlayGlobalAudio
: Plays non-spatial audio for all players.IAudioManager.PlayGlobalAudioWithFilter
: Plays non-spatial audio with custom speaker configuration, such as player filters.ISpeakerFactory.CreateSpeaker
: Creates a new speaker at a specified position.ISpeaker.Play
: Plays audio samples with optional looping.ISpeakerWithPlayerFilter.SetValidPlayers
: Sets a filter to control which players hear the audio.StaticSpeakerFactory.ClearSpeakers
: Clears all registered speakers.StaticSpeakerFactory.AudioFilters.*
: Provides predefined filters for role, team, distance, room, and custom conditions.
Events
- OnRoundEnded: Call
ClearSpeakers
to clean up speakers and prevent memory leaks. - OnPlayerSpawned: Use player filters to target specific roles or zones.
- OnAudioPlaybackFinished: Monitor playback completion for queued audio.
Notes
- Global Audio with Filters: Use
PlayGlobalAudioWithFilter
to play non-spatial audio with custom player filters, ideal for targeted announcements or events. - Spatial vs. Non-Spatial Audio:
PlayAudio
is for spatial audio with 3D positioning, whilePlayGlobalAudio
andPlayGlobalAudioWithFilter
are for non-spatial audio heard uniformly by targeted players. - Documentation: Version 1.6.0 includes comprehensive XML documentation for all public methods, improving clarity and ease of use.
- Controller ID Synchronization:
ControllerIdManager
ensures no ID conflicts by maintaining a shared pool of IDs (1-255). High-priority audio can evict lower-priority speakers or be queued for later allocation. - Speaker Lifecycle Management: Use
StaticSpeakerFactory
orDefaultSpeakerFactory
methods (GetSpeaker
,RemoveSpeaker
,ClearSpeakers
) to manage speakers. CallClearSpeakers
on round restarts to prevent memory leaks. - Thread Safety: All operations (ID allocation, caching, speaker management, filters) are thread-safe using locks.
- Dependencies: Requires
UnityEngine.CoreModule
,LabApi
, andMEC
. Fully compatible with LabAPI’sPlayer
andSpeakerToy
classes, aligning with Northwood’s SCP:SL ecosystem. No Exiled dependencies are used. - Logging: Uses
LabApi.Features.Console.Logger
for debugging. Integrate with your plugin’s logging system for additional context. - Spatial Audio: Use
isSpatial: true
for positional effects (e.g., zone-specific sounds) andisSpatial: false
for global sounds (e.g., announcements). Non-spatial audio defaults toVector3.zero
. - Fading and Queuing: Use
FadeInAudio
/FadeOutAudio
for smooth transitions,ClearSpeakerQueue
to reset queues, andGetQueueStatus
to monitor queue state. - Persistent Speakers: Use
persistent: true
to retain speaker state (position, volume, queued clips, playback position) for recovery viaRecoverSpeaker
, validated byValidateState
. - Playback Position: For persistent speakers, playback position is approximated by trimming samples, as
AudioTransmitter
does not natively support seeking. - Player Filters: Use
StaticSpeakerFactory.AudioFilters
or customFunc<Player, bool>
withISpeakerWithPlayerFilter.SetValidPlayers
to control which players hear audio.
Contributing
Contributions are welcome! Please submit pull requests or issues to the GitHub repository. Ensure code follows SCP:SL plugin conventions and includes tests for new features.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET Framework | net48 is compatible. net481 was computed. |
-
.NETFramework 4.8
- Northwood.LabAPI (>= 1.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
1.7.0 | 89 | 7/28/2025 |
1.6.0 | 60 | 7/27/2025 |
1.5.2 | 205 | 7/26/2025 |
1.5.1 | 208 | 7/26/2025 |
1.5.0 | 435 | 7/24/2025 |
1.4.2 | 438 | 7/24/2025 |
1.4.1 | 432 | 7/24/2025 |
1.4.0 | 435 | 7/24/2025 |
1.3.0 | 437 | 7/23/2025 |
1.2.3 | 483 | 7/22/2025 |
1.2.2 | 483 | 7/22/2025 |
1.2.1 | 488 | 7/22/2025 |
1.2.0 | 487 | 7/22/2025 |
1.0.3 | 484 | 7/22/2025 |
1.0.2 | 433 | 7/21/2025 |
1.0.1 | 430 | 7/21/2025 |
1.0.0 | 434 | 7/21/2025 |