├── .gitignore ├── README.md ├── SCPSLAudioApi.sln └── SCPSLAudioApi ├── App.config ├── AudioCore └── AudioPlayerBase.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── SCPSLAudioApi.csproj ├── Startup.cs └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | /.idea 400 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SCPSLAudioAPi 2 | 3 | This is an API Library that does not depend on any Plugin frameworks for its function. 4 | This API provides a implementation of an Audio player in a SCP:SL Server with options to extend 5 | 6 | ## Installation 7 | Download the latest release from the [Releases](https://github.com/CedModV2/SCPSLAudioApi/releases/) page and place the SCPSLAudioAPi.dll file in your plugin dependencies folder 8 | 9 | ## Usage 10 | Note: This is **NOT** an audioplayer plugin, this is a library to make creating a plugin that can play audio easier. To be able to play audio you will need to find a plugin that does that using this api, or a custom solution. 11 | 12 | ## Developer Usage 13 | The Library mainly consists of the AudioPlayerBase class, which can be inherited in combination with overriding Methods to create custom logic while still having the heavy lifting done by the Api. 14 | 15 | Note: In order for the Library to function properly, you **must** call `Startup::SetupDependencies` in a function that runs before any interaction with the library, Eg: OnLoad or OnRegistered of your plugin. 16 | 17 | ### Without a custom AudioPlayer class 18 | Simply Create a Dummyplayer (Not included in the library) and Call AudioPlayerBase.Get() on the ReferenceHub of this DummyPlayer. 19 | 20 | To play Audio, you must first, insert a Path to the audio file into the Queue, This can be done by directly modifying AudioToPlay, but it is recommend to use the Enqueue Method. \ 21 | When audio is in the queue, call the Play method and after the file has been loaded, it should start playing your audio. 22 | 23 | ### With a Custom AudioPlayer class 24 | Simply Create a Dummyplayer (Not included in the library) and Call AudioPlayerBase.Get() on the ReferenceHub of this DummyPlayer. 25 | 26 | (You will need to create a cusom NetworkConnection for a fake player) 27 | 28 | ```csharp 29 | public class FakeConnection : NetworkConnectionToClient 30 | { 31 | public FakeConnection(int connectionId) : base(connectionId, false, 0f) 32 | { 33 | 34 | } 35 | 36 | public override string address 37 | { 38 | get 39 | { 40 | return "localhost"; 41 | } 42 | } 43 | 44 | public override void Send(ArraySegment segment, int channelId = 0) 45 | { 46 | } 47 | public override void Disconnect() 48 | { 49 | } 50 | } 51 | ``` 52 | 53 | then, you can spawn a fake player like this 54 | 55 | ```csharp 56 | var newPlayer = UnityEngine.Object.Instantiate(NetworkManager.singleton.playerPrefab); 57 | var fakeConnection = new FakeConnection(id); 58 | var hubPlayer = newPlayer.GetComponent(); 59 | NetworkServer.AddPlayerForConnection(fakeConnection, newPlayer); 60 | ``` 61 | Then you can use the player like you would with any, eg spawn it, teleport it 62 | 63 | Custom AudioPlayer classes work the same way as a normal AudioPlayerBase, the developer can change whatever they feel like in the methods for playing and broadcasting. 64 | Refer to the source of AudioPlayerBase to see what each method does. 65 | 66 | You can get your AudioPlayer class in multiple ways 67 | You can try to find it in AudioPlayerBase.AudioPlayers, but it is recommended to create a helper Method. 68 | 69 | ```csharp 70 | public static CustomAudioPlayer Get(ReferenceHub hub) 71 | { 72 | if (AudioPlayers.TryGetValue(hub, out AudioPlayerBase player)) 73 | { 74 | if (player is CustomAudioPlayer cplayer1) 75 | return cplayer1; 76 | } 77 | 78 | var cplayer = hub.gameObject.AddComponent(); 79 | cplayer.Owner = hub; 80 | 81 | AudioPlayers.Add(hub, cplayer); 82 | return cplayer; 83 | } 84 | ``` 85 | 86 | To start playing audio there are 2 options you can use, using the Queue system, or directly playing. 87 | to directly play audio, set CurrentPlay to the path or url that you want to play, and call Play(-1). 88 | Additionally if you want to loop the audio, set Loop to true. 89 | 90 | If you wish to play using the Queue system. 91 | Call Enqueue with the file or url you wish to play. 92 | then call Play(0); to start playing the first file or url in queue. 93 | Additionally, setting loop to true will cause the AudioPlayer to add audio to the end of the queue when it starts to play it. 94 | 95 | **Note:** For Urls to work you must set AllowUrl to true on the AudioPlayerBase instance. 96 | 97 | ### Features: 98 | Volume control - `AudioPlayerBase::Volume` \ 99 | Queue Looping - `AudioPlayerBase::Loop` \ 100 | Queue Shuffle - `AudioPlayerBase::Shuffle` \ 101 | Autoplay - `AudioPlayerBase::Continue` \ 102 | Pausing - `AudioPlayerBase::ShouldPlay` \ 103 | Play From URL - `AudioPlayerBase::AllowUrl` \ 104 | Play to specific players only (Will play to everyone if the list is Empty) - `AudioPlayerBase::BroadcastTo` \ 105 | Debug logs (can cause spam) - `AudioPlayerBase::LogDebug` \ 106 | AudioChannel - `AudioPlayerBase::BroadcastChannel` 107 | -------------------------------------------------------------------------------- /SCPSLAudioApi.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SCPSLAudioApi", "SCPSLAudioApi\SCPSLAudioApi.csproj", "{3E68FD79-87B1-4009-AAA0-5F5C9E7A67DE}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {3E68FD79-87B1-4009-AAA0-5F5C9E7A67DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {3E68FD79-87B1-4009-AAA0-5F5C9E7A67DE}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {3E68FD79-87B1-4009-AAA0-5F5C9E7A67DE}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {3E68FD79-87B1-4009-AAA0-5F5C9E7A67DE}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /SCPSLAudioApi/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /SCPSLAudioApi/AudioCore/AudioPlayerBase.cs: -------------------------------------------------------------------------------- 1 | using CentralAuth; 2 | using MEC; 3 | using Mirror; 4 | using NVorbis; 5 | using PluginAPI.Core; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | using UnityEngine; 11 | using UnityEngine.Networking; 12 | using VoiceChat; 13 | using VoiceChat.Codec; 14 | using VoiceChat.Networking; 15 | using Random = UnityEngine.Random; 16 | 17 | namespace SCPSLAudioApi.AudioCore 18 | { 19 | public class AudioPlayerBase : MonoBehaviour 20 | { 21 | public static Dictionary AudioPlayers = new Dictionary(); 22 | 23 | #region Internal 24 | 25 | public const int HeadSamples = 1920; 26 | public OpusEncoder Encoder { get; } = new OpusEncoder(VoiceChat.Codec.Enums.OpusApplicationType.Voip); 27 | public PlaybackBuffer PlaybackBuffer { get; } = new PlaybackBuffer(); 28 | public byte[] EncodedBuffer { get; } = new byte[512]; 29 | public bool stopTrack = false; 30 | public bool ready = false; 31 | public CoroutineHandle PlaybackCoroutine; 32 | 33 | public float allowedSamples; 34 | public int samplesPerSecond; 35 | public Queue StreamBuffer { get; } = new Queue(); 36 | public VorbisReader VorbisReader { get; set; } 37 | public float[] SendBuffer { get; set; } 38 | public float[] ReadBuffer { get; set; } 39 | 40 | #endregion 41 | 42 | #region AudioPlayer Settings 43 | 44 | /// 45 | /// The ReferenceHub instance that this player sends as. 46 | /// 47 | public ReferenceHub Owner { get; set; } 48 | 49 | /// 50 | /// Volume that the player will play at. 51 | /// 52 | public float Volume { get; set; } = 100f; 53 | 54 | /// 55 | /// List of Paths/Urls that the player will play from (Urls only work if is true) 56 | /// 57 | public List AudioToPlay = new List(); 58 | 59 | /// 60 | /// Path/Url of the currently playing audio file. 61 | /// 62 | public string CurrentPlay; 63 | 64 | /// 65 | /// Stream containing the Audio data 66 | /// 67 | public MemoryStream CurrentPlayStream; 68 | 69 | /// 70 | /// Boolean indicating whether or not the Queue will loop (Audio will be added to the end of the queue after it gets removed on play) 71 | /// 72 | public bool Loop = false; 73 | 74 | /// 75 | /// If the playlist should be shuffled when an audio track start. 76 | /// 77 | public bool Shuffle = false; 78 | 79 | /// 80 | /// Whether the Player should continue playing by itself after the current Track ends. 81 | /// 82 | public bool Continue = true; 83 | 84 | /// 85 | /// Whether the Player should be sending audio to the broadcaster. 86 | /// 87 | public bool ShouldPlay = true; 88 | 89 | /// 90 | /// If URLs are allowed to be played 91 | /// 92 | public bool AllowUrl = false; 93 | 94 | /// 95 | /// Determines whether debug logs should be shown. Note: Enabling this option can generate a large amount of log output. 96 | /// 97 | public bool LogDebug = false; 98 | 99 | /// 100 | /// Determines whether informational logs should be shown throughout the code. 101 | /// 102 | public bool LogInfo = false; 103 | 104 | /// 105 | /// Gets a value indicating whether the current song has finished playing. 106 | /// 107 | public bool IsFinished = false; 108 | 109 | /// 110 | /// Determines whether the ReferenceHub will be destroyed after finishing playing all tracks. 111 | /// 112 | public bool ClearOnFinish = false; 113 | 114 | /// 115 | /// If not empty, the audio will only be sent to players with the PlayerIds in this list 116 | /// 117 | public List BroadcastTo = new List(); 118 | 119 | /// 120 | /// Gets or Sets the Channel where audio will be played in 121 | /// 122 | public VoiceChatChannel BroadcastChannel { get; set; } = VoiceChatChannel.Proximity; 123 | 124 | #endregion 125 | 126 | #region Events 127 | 128 | /// 129 | /// Fired when a track is getting selected. 130 | /// 131 | /// The AudioPlayer instance that this event fired for 132 | /// If the AudioPlayer was playing Directly (-1 index) 133 | /// Position in the Queue of the track that is going to be selected 134 | public delegate void TrackSelecting(AudioPlayerBase playerBase, bool directPlay, ref int queuePos); 135 | public static event TrackSelecting OnTrackSelecting; 136 | 137 | /// 138 | /// Fired when a track has been selected 139 | /// 140 | /// The AudioPlayer instance that this event fired for 141 | /// If the AudioPlayer was playing Directly (-1 index) 142 | /// Position in the Queue of the track that will start 143 | /// The track the AudioPlayer will play 144 | public delegate void TrackSelected(AudioPlayerBase playerBase, bool directPlay, int queuePos, ref string track); 145 | public static event TrackSelected OnTrackSelected; 146 | 147 | 148 | /// 149 | /// Fired when a track is loaded and will begin playing. 150 | /// 151 | /// The AudioPlayer instance that this event fired for 152 | /// If the AudioPlayer was playing Directly (-1 index) 153 | /// Position in the Queue that will play 154 | /// The track the AudioPlayer will play 155 | public delegate void TrackLoaded(AudioPlayerBase playerBase, bool directPlay, int queuePos, string track); 156 | public static event TrackLoaded OnTrackLoaded; 157 | 158 | /// 159 | /// Fired when a track finishes. 160 | /// 161 | /// The AudioPlayer instance that this event fired for 162 | /// The track the AudioPlayer was playing 163 | /// If the AudioPlayer was playing Directly (-1 index) 164 | /// Position in the Queue that will play next, can be set to a different value 165 | public delegate void TrackFinished(AudioPlayerBase playerBase, string track, bool directPlay, ref int nextQueuePos); 166 | public static event TrackFinished OnFinishedTrack; 167 | 168 | #endregion 169 | 170 | /// 171 | /// Add or retrieve the AudioPlayerBase instance based on a ReferenceHub instance. 172 | /// 173 | /// The ReferenceHub instance that this AudioPlayer belongs to 174 | /// 175 | public static AudioPlayerBase Get(ReferenceHub hub) 176 | { 177 | if (AudioPlayers.TryGetValue(hub, out AudioPlayerBase player)) 178 | { 179 | return player; 180 | } 181 | 182 | player = hub.gameObject.AddComponent(); 183 | player.Owner = hub; 184 | 185 | AudioPlayers.Add(hub, player); 186 | return player; 187 | } 188 | 189 | /// 190 | /// Start playing audio, if called while audio is already playing the player will skip to the next file. 191 | /// 192 | /// The position in the queue of the audio that should be played. 193 | public virtual void Play(int queuePos) 194 | { 195 | if (PlaybackCoroutine.IsRunning) 196 | Timing.KillCoroutines(PlaybackCoroutine); 197 | PlaybackCoroutine = Timing.RunCoroutine(Playback(queuePos), Segment.FixedUpdate); 198 | } 199 | 200 | /// 201 | /// Stops playing the current Track, or stops the player entirely if Clear is true. 202 | /// 203 | /// If true the player will stop and the queue will be cleared. 204 | public virtual void Stoptrack(bool Clear) 205 | { 206 | if (Clear) 207 | AudioToPlay.Clear(); 208 | stopTrack = true; 209 | } 210 | 211 | /// 212 | /// Add an audio file to the queue 213 | /// 214 | /// Path/Url to an audio file (Url only works if is true) 215 | /// Position that the audio file should be inserted at, use -1 to insert at the end of the queue. 216 | public virtual void Enqueue(string audio, int pos) 217 | { 218 | if (pos == -1) 219 | AudioToPlay.Add(audio); 220 | else 221 | AudioToPlay.Insert(pos, audio); 222 | } 223 | 224 | public virtual void OnDestroy() 225 | { 226 | if (PlaybackCoroutine.IsRunning) 227 | Timing.KillCoroutines(PlaybackCoroutine); 228 | 229 | AudioPlayers.Remove(Owner); 230 | 231 | if (ClearOnFinish) 232 | NetworkServer.RemovePlayerForConnection(Owner.connectionToClient, true); 233 | } 234 | 235 | public virtual IEnumerator Playback(int position) 236 | { 237 | stopTrack = false; 238 | IsFinished = false; 239 | int index = position; 240 | 241 | OnTrackSelecting?.Invoke(this, index == -1, ref index); 242 | if (index != -1) 243 | { 244 | if (Shuffle) 245 | AudioToPlay = AudioToPlay.OrderBy(i => Random.value).ToList(); 246 | CurrentPlay = AudioToPlay[index]; 247 | AudioToPlay.RemoveAt(index); 248 | if (Loop) 249 | { 250 | AudioToPlay.Add(CurrentPlay); 251 | } 252 | } 253 | 254 | OnTrackSelected?.Invoke(this, index == -1, index, ref CurrentPlay); 255 | 256 | if (LogInfo) 257 | Log.Info($"Loading Audio"); 258 | 259 | if (AllowUrl && Uri.TryCreate(CurrentPlay, UriKind.Absolute, out Uri result)) 260 | { 261 | UnityWebRequest www = new UnityWebRequest(CurrentPlay, "GET"); 262 | DownloadHandlerBuffer dH = new DownloadHandlerBuffer(); 263 | www.downloadHandler = dH; 264 | 265 | yield return Timing.WaitUntilDone(www.SendWebRequest()); 266 | 267 | if (www.responseCode != 200) 268 | { 269 | Log.Error($"Failed to retrieve audio {www.responseCode} {www.downloadHandler.text}"); 270 | if (Continue && AudioToPlay.Count >= 1) 271 | { 272 | yield return Timing.WaitForSeconds(1); 273 | if (AudioToPlay.Count >= 1) 274 | Timing.RunCoroutine(Playback(0)); 275 | } 276 | yield break; 277 | } 278 | 279 | CurrentPlayStream = new MemoryStream(www.downloadHandler.data); 280 | } 281 | else 282 | { 283 | if (File.Exists(CurrentPlay)) 284 | { 285 | if (!CurrentPlay.EndsWith(".ogg")) 286 | { 287 | Log.Error($"Audio file {CurrentPlay} is not valid. Audio files must be ogg files"); 288 | yield return Timing.WaitForSeconds(1); 289 | if (AudioToPlay.Count >= 1) 290 | Timing.RunCoroutine(Playback(0)); 291 | yield break; 292 | } 293 | CurrentPlayStream = new MemoryStream(File.ReadAllBytes(CurrentPlay)); 294 | } 295 | else 296 | { 297 | Log.Error($"Audio file {CurrentPlay} does not exist. skipping."); 298 | yield return Timing.WaitForSeconds(1); 299 | if (AudioToPlay.Count >= 1) 300 | Timing.RunCoroutine(Playback(0)); 301 | yield break; 302 | } 303 | } 304 | 305 | CurrentPlayStream.Seek(0, SeekOrigin.Begin); 306 | 307 | VorbisReader = new NVorbis.VorbisReader(CurrentPlayStream); 308 | 309 | if (VorbisReader.Channels >= 2) 310 | { 311 | Log.Error($"Audio file {CurrentPlay} is not valid. Audio files must be mono."); 312 | yield return Timing.WaitForSeconds(1); 313 | if (AudioToPlay.Count >= 1) 314 | Timing.RunCoroutine(Playback(0)); 315 | VorbisReader.Dispose(); 316 | CurrentPlayStream.Dispose(); 317 | yield break; 318 | } 319 | 320 | if (VorbisReader.SampleRate != 48000) 321 | { 322 | Log.Error($"Audio file {CurrentPlay} is not valid. Audio files must have a SamepleRate of 48000"); 323 | yield return Timing.WaitForSeconds(1); 324 | if (AudioToPlay.Count >= 1) 325 | Timing.RunCoroutine(Playback(0)); 326 | VorbisReader.Dispose(); 327 | CurrentPlayStream.Dispose(); 328 | yield break; 329 | } 330 | OnTrackLoaded?.Invoke(this, index == -1, index, CurrentPlay); 331 | 332 | if (LogInfo) 333 | Log.Info($"Playing {CurrentPlay} with samplerate of {VorbisReader.SampleRate}"); 334 | 335 | samplesPerSecond = VoiceChatSettings.SampleRate * VoiceChatSettings.Channels; 336 | //_samplesPerSecond = VorbisReader.Channels * VorbisReader.SampleRate / 5; 337 | SendBuffer = new float[samplesPerSecond / 5 + HeadSamples]; 338 | ReadBuffer = new float[samplesPerSecond / 5 + HeadSamples]; 339 | int cnt; 340 | while ((cnt = VorbisReader.ReadSamples(ReadBuffer, 0, ReadBuffer.Length)) > 0) 341 | { 342 | if (stopTrack) 343 | { 344 | VorbisReader.SeekTo(VorbisReader.TotalSamples - 1); 345 | stopTrack = false; 346 | } 347 | while (!ShouldPlay) 348 | { 349 | yield return Timing.WaitForOneFrame; 350 | } 351 | while (StreamBuffer.Count >= ReadBuffer.Length) 352 | { 353 | ready = true; 354 | yield return Timing.WaitForOneFrame; 355 | } 356 | for (int i = 0; i < ReadBuffer.Length; i++) 357 | { 358 | StreamBuffer.Enqueue(ReadBuffer[i]); 359 | } 360 | } 361 | 362 | if (LogInfo) 363 | Log.Info($"Track Complete."); 364 | 365 | int nextQueuepos = 0; 366 | if (Continue && Loop && index == -1) 367 | { 368 | nextQueuepos = -1; 369 | Timing.RunCoroutine(Playback(nextQueuepos)); 370 | OnFinishedTrack?.Invoke(this, CurrentPlay, index == -1, ref nextQueuepos); 371 | yield break; 372 | } 373 | 374 | if (Continue && AudioToPlay.Count >= 1) 375 | { 376 | IsFinished = true; 377 | Timing.RunCoroutine(Playback(nextQueuepos)); 378 | OnFinishedTrack?.Invoke(this, CurrentPlay, index == -1, ref nextQueuepos); 379 | yield break; 380 | } 381 | 382 | IsFinished = true; 383 | OnFinishedTrack?.Invoke(this, CurrentPlay, index == -1, ref nextQueuepos); 384 | 385 | if (ClearOnFinish) 386 | Destroy(this); 387 | } 388 | 389 | public virtual void Update() 390 | { 391 | if (Owner == null || !ready || StreamBuffer.Count == 0 || !ShouldPlay) return; 392 | 393 | allowedSamples += Time.deltaTime * samplesPerSecond; 394 | int toCopy = Mathf.Min(Mathf.FloorToInt(allowedSamples), StreamBuffer.Count); 395 | if (LogDebug) 396 | Log.Debug($"1 {toCopy} {allowedSamples} {samplesPerSecond} {StreamBuffer.Count} {PlaybackBuffer.Length} {PlaybackBuffer.WriteHead}"); 397 | if (toCopy > 0) 398 | { 399 | for (int i = 0; i < toCopy; i++) 400 | { 401 | PlaybackBuffer.Write(StreamBuffer.Dequeue() * (Volume / 100f)); 402 | } 403 | } 404 | 405 | if (LogDebug) 406 | Log.Debug($"2 {toCopy} {allowedSamples} {samplesPerSecond} {StreamBuffer.Count} {PlaybackBuffer.Length} {PlaybackBuffer.WriteHead}"); 407 | 408 | allowedSamples -= toCopy; 409 | 410 | while (PlaybackBuffer.Length >= 480) 411 | { 412 | PlaybackBuffer.ReadTo(SendBuffer, (long)480, 0L); 413 | int dataLen = Encoder.Encode(SendBuffer, EncodedBuffer, 480); 414 | 415 | foreach (var plr in ReferenceHub.AllHubs) 416 | { 417 | if (plr.connectionToClient == null || !PlayerIsConnected(plr) || (BroadcastTo.Count >= 1 && !BroadcastTo.Contains(plr.PlayerId))) continue; 418 | 419 | plr.connectionToClient.Send(new VoiceMessage(Owner, BroadcastChannel, EncodedBuffer, dataLen, false)); 420 | } 421 | } 422 | } 423 | 424 | /// 425 | /// Checks whether a player connected to the server is considered fully connected or if it is a DummyPlayer. 426 | /// 427 | /// The ReferenceHub of the player to check. 428 | /// True if the player is fully connected and not a DummyPlayer; otherwise, false. 429 | private bool PlayerIsConnected(ReferenceHub hub) 430 | { 431 | return hub.authManager.InstanceMode == ClientInstanceMode.ReadyClient && 432 | hub.nicknameSync.NickSet && 433 | !hub.isLocalPlayer && 434 | !string.IsNullOrEmpty(hub.authManager.UserId) && 435 | !hub.authManager.UserId.Contains("Dummy"); 436 | } 437 | 438 | 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /SCPSLAudioApi/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NVorbis 6 | System.Memory 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /SCPSLAudioApi/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks 13 | 14 | 15 | 16 | 17 | A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. 18 | 19 | 20 | 21 | 22 | A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks 23 | 24 | 25 | 26 | 27 | A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. 28 | 29 | 30 | 31 | 32 | A list of unmanaged 32 bit assembly names to include, delimited with line breaks. 33 | 34 | 35 | 36 | 37 | A list of unmanaged 64 bit assembly names to include, delimited with line breaks. 38 | 39 | 40 | 41 | 42 | The order of preloaded assemblies, delimited with line breaks. 43 | 44 | 45 | 46 | 47 | 48 | This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. 49 | 50 | 51 | 52 | 53 | Controls if .pdbs for reference assemblies are also embedded. 54 | 55 | 56 | 57 | 58 | Controls if runtime assemblies are also embedded. 59 | 60 | 61 | 62 | 63 | Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. 64 | 65 | 66 | 67 | 68 | Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. 69 | 70 | 71 | 72 | 73 | As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. 74 | 75 | 76 | 77 | 78 | Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. 79 | 80 | 81 | 82 | 83 | Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. 84 | 85 | 86 | 87 | 88 | A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | 89 | 90 | 91 | 92 | 93 | A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. 94 | 95 | 96 | 97 | 98 | A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | 99 | 100 | 101 | 102 | 103 | A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. 104 | 105 | 106 | 107 | 108 | A list of unmanaged 32 bit assembly names to include, delimited with |. 109 | 110 | 111 | 112 | 113 | A list of unmanaged 64 bit assembly names to include, delimited with |. 114 | 115 | 116 | 117 | 118 | The order of preloaded assemblies, delimited with |. 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 127 | 128 | 129 | 130 | 131 | A comma-separated list of error codes that can be safely ignored in assembly verification. 132 | 133 | 134 | 135 | 136 | 'false' to turn off automatic generation of the XML Schema file. 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /SCPSLAudioApi/SCPSLAudioApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {3E68FD79-87B1-4009-AAA0-5F5C9E7A67DE} 9 | Library 10 | Properties 11 | SCPSLAudioApi 12 | SCPSLAudioApi 13 | v4.8 14 | 512 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ..\packages\Costura.Fody.5.8.0-alpha0098\lib\netstandard1.0\Costura.dll 50 | True 51 | 52 | 53 | ..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll 54 | True 55 | 56 | 57 | 58 | 59 | 60 | ..\packages\NVorbis.0.10.5\lib\net45\NVorbis.dll 61 | True 62 | 63 | 64 | 65 | ..\packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll 66 | True 67 | 68 | 69 | ..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll 70 | True 71 | 72 | 73 | 74 | ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll 75 | True 76 | 77 | 78 | 79 | ..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll 80 | True 81 | 82 | 83 | ..\packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll 84 | True 85 | 86 | 87 | ..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll 88 | True 89 | 90 | 91 | ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll 92 | True 93 | 94 | 95 | ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll 96 | True 97 | 98 | 99 | 100 | ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll 101 | True 102 | 103 | 104 | ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll 105 | True 106 | 107 | 108 | ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll 109 | True 110 | 111 | 112 | ..\packages\System.Linq.4.3.0\lib\net463\System.Linq.dll 113 | True 114 | 115 | 116 | ..\packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll 117 | True 118 | 119 | 120 | ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll 121 | True 122 | 123 | 124 | ..\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll 125 | True 126 | 127 | 128 | ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll 129 | True 130 | 131 | 132 | 133 | ..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll 134 | True 135 | 136 | 137 | ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll 138 | True 139 | 140 | 141 | ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll 142 | True 143 | 144 | 145 | ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll 146 | True 147 | 148 | 149 | ..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll 150 | True 151 | 152 | 153 | ..\packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll 154 | True 155 | 156 | 157 | ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll 158 | True 159 | 160 | 161 | ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll 162 | True 163 | 164 | 165 | ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll 166 | True 167 | 168 | 169 | ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll 170 | True 171 | 172 | 173 | ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll 174 | True 175 | 176 | 177 | ..\packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll 178 | True 179 | 180 | 181 | ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll 182 | True 183 | 184 | 185 | 186 | 187 | ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll 188 | True 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /SCPSLAudioApi/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace SCPSLAudioApi 2 | { 3 | public class Startup 4 | { 5 | public static void SetupDependencies() 6 | { 7 | CosturaUtility.Initialize(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /SCPSLAudioApi/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | --------------------------------------------------------------------------------