Callum.Voice 0.0.2

dotnet add package Callum.Voice --version 0.0.2
                    
NuGet\Install-Package Callum.Voice -Version 0.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Callum.Voice" Version="0.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Callum.Voice" Version="0.0.2" />
                    
Directory.Packages.props
<PackageReference Include="Callum.Voice" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Callum.Voice --version 0.0.2
                    
#r "nuget: Callum.Voice, 0.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Callum.Voice@0.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Callum.Voice&version=0.0.2
                    
Install as a Cake Addin
#tool nuget:?package=Callum.Voice&version=0.0.2
                    
Install as a Cake Tool

Callum.Voice — Real-Time Voice Chat Library

A .NET library for cross-platform real-time voice communication over a Go server (phonil-opus).
All microphone capture, audio playback, WebSocket connection, multi-peer audio mixing, speaking detection, and room management logic is implemented in this library.


Table of Contents


Architecture

┌──────────────────────────────────────────────────────────┐
│                    Callum.Voice                         │
│                                                          │
│  ┌─────────────┐     ┌──────────────┐     ┌──────────┐ │
│  │ VoiceClient  │────▶│ IAudioEngine │────▶│ Platform │ │
│  │              │◀────│  (interface) │◀────│  Audio   │ │
│  │ • WebSocket  │     │              │     │  Engine  │ │
│  │ • Room Mgmt │     │ • Capture    │     │          │ │
│  │ • Peer Track│     │ • Playback   │     │ • WASAPI │ │
│  │ • Speaking  │     │ • Volume     │     │ • AVAudio│ │
│  │ • Reconnect │     │ • Power Mgmt │     │ • AudioR.│ │
│  └─────────────┘     └──────────────┘     └──────────┘ │
│         │                                      ▲         │
│         │ WebSocket (PCM 16-bit mono)          │         │
│         ▼                                      │         │
│  ┌─────────────┐                              │         │
│  │  Go Server   │────── PCM Audio ────────────┘         │
│  │ (phonil-opus)│                                        │
│  └─────────────┘                                        │
└──────────────────────────────────────────────────────────┘

Layers

Layer Responsibility
VoiceClient Network logic (WebSocket), room management, peer tracking, speaking detection, auto-reconnect
IAudioEngine Abstract interface for audio capture and playback — each platform has its own implementation
AudioEngine (Platform) Platform-specific implementation: Android (AudioRecord/AudioTrack), iOS (AVAudioEngine), Windows (WASAPI)

File Structure

Callum.Voice/
├── VoiceClient.cs              # Core logic: WebSocket, room, mixing, speaking detection
├── VoiceConfig.cs              # Connection settings (server, API key, sample rate)
├── VoiceClientState.cs         # Connection states (Disconnected → Connected → InRoom)
├── IAudioEngine.cs             # Abstract audio engine interface
├── Platforms/
│   ├── Android/
│   │   ├── AudioEngine.cs      # Capture/playback with AudioRecord/AudioTrack + Ring Buffer
│   │   └── VoiceForegroundService.cs  # Android foreground service
│   ├── iOS/
│   │   └── AudioEngine.cs      # Capture/playback with AVAudioEngine + AVAudioPlayerNode
│   ├── MacCatalyst/
│   │   └── (uses the iOS engine)
│   └── Windows/
│       └── AudioEngine.cs      # Capture/playback with WASAPI (COM Interop)
└── Callum.Voice.csproj

Communication Protocol

WebSocket Connection

ws(s)://{server}/ws?room=__lobby__&peer={peerId}&api_key={apiKey}

Text Message (JSON) — Join Room

{
  "type": "join",
  "room": "room-id",
  "peer": "user-123",
  "sampleRate": 48000
}

Binary Message (Audio) — Packet Format

[1B RoomLen][RoomID][1B PeerLen][PeerID][PCM Audio Data]
  • PCM Format: 16-bit signed integer, mono, 48 kHz
  • Each packet contains the room ID, sender ID, and raw audio data

Supported Platforms

1. .NET MAUI

Status: Fully supported — Production ready

The library is distributed as a standalone NuGet package (Callum.Voice) with no MAUI dependency. It only uses native platform APIs.

Target Frameworks:

<TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst;net10.0-windows10.0.19041.0</TargetFrameworks>

Dependencies:

  • Android: Xamarin.AndroidX.Core (for ContextCompat and NotificationCompat)
  • iOS/MacCatalyst/Windows: No additional dependencies

Minimum supported versions: | Platform | Minimum Version | |----------|----------------| | Android | API 23 (6.0) | | iOS | 15.0 | | MacCatalyst | 15.0 | | Windows | 10.0.17763.0 |

NuGet Installation
dotnet add package Callum.Voice
MAUI Example — Main Page
using Callum.Voice;

public partial class VoicePage : ContentPage
{
    private VoiceClient? _client;

    public VoicePage()
    {
        InitializeComponent();
    }

    protected async override void OnAppearing()
    {
        base.OnAppearing();

        // 1. Create audio engine (auto-selects platform implementation)
        var audio = new AudioEngine(sampleRate: 48_000);

        // 2. Create client
        var config = new VoiceConfig
        {
            Server = "callem.cloudfort.ir",
            ApiKey = "vc_live_YOUR_API_KEY",
            PeerId = $"user-{Guid.NewGuid():N}",
            AutoReconnect = true,
        };

        _client = new VoiceClient(config, audio);

        // 3. Register events
        _client.Connected += () =>
            MainThread.BeginInvokeOnMainThread(() =>
                lblStatus.Text = "Connected");

        _client.PeerJoined += peerId =>
            MainThread.BeginInvokeOnMainThread(() =>
                lblPeers.Text += $"\n{peerId}");

        _client.PeerSpeaking += peerId =>
            MainThread.BeginInvokeOnMainThread(() =>
                lblSpeaking.Text = $"{peerId} is speaking...");

        _client.Error += ex =>
            MainThread.BeginInvokeOnMainThread(() =>
                lblStatus.Text = $"Error: {ex.Message}");

        // 4. Connect and join room
        await _client.ConnectAsync();
        await _client.JoinRoomAsync("general");

        // 5. Enable microphone
        await _client.EnableMicAsync();
    }

    private async void OnToggleMic(object sender, EventArgs e)
    {
        bool isOn = await _client!.ToggleMicAsync();
        btnMic.Text = isOn ? "🎤 Mute" : "🎤 Unmute";
    }

    protected override void OnDisappearing()
    {
        _client?.Dispose();
        base.OnDisappearing();
    }
}

2. WPF (Windows)

Status: Fully supported — No changes needed

The library has no MAUI dependency and can be used directly in WPF projects.

WPF Example
using System.Windows;
using Callum.Voice;

namespace MyWpfApp;

public partial class MainWindow : Window
{
    private VoiceClient? _client;

    public MainWindow()
    {
        InitializeComponent();
    }

    private async void BtnConnect_Click(object sender, RoutedEventArgs e)
    {
        var audio = new AudioEngine(sampleRate: 48_000);

        var config = new VoiceConfig
        {
            Server = "callem.cloudfort.ir",
            ApiKey = "vc_live_YOUR_API_KEY",
            PeerId = $"wpf-user-{Guid.NewGuid():N}",
        };

        _client = new VoiceClient(config, audio);

        _client.Connected += () =>
            Dispatcher.Invoke(() => txtStatus.Text = "Connected");

        _client.PeerSpeaking += peerId =>
            Dispatcher.Invoke(() => txtSpeaking.Text = $"{peerId} speaking");

        await _client.ConnectAsync();
        await _client.JoinRoomAsync("general");
        await _client.EnableMicAsync();
    }

    private async void BtnToggleMic_Click(object sender, RoutedEventArgs e)
    {
        bool isOn = await _client!.ToggleMicAsync();
        btnMic.Content = isOn ? "Mute" : "Unmute";
    }

    protected override void OnClosed(EventArgs e)
    {
        _client?.Dispose();
        base.OnClosed(e);
    }
}

Note: The Windows AudioEngine uses WASAPI COM Interop and is fully standalone.

Custom IAudioEngine for WPF
using Callum.Voice;
using NAudio.Wave; // NuGet: NAudio

public class WpfAudioEngine : IAudioEngine
{
    private WaveInEvent? _waveIn;
    private WaveOutEvent? _waveOut;
    // ... NAudio implementation

    public int SampleRate => 48_000;
    public event Action<short[]>? AudioCaptured;
    public event Action<string>? ErrorOccurred;

    public void StartCapture()
    {
        _waveIn = new WaveInEvent { WaveFormat = new WaveFormat(48000, 16, 1) };
        _waveIn.DataAvailable += (s, e) =>
        {
            var samples = new short[e.BytesRecorded / 2];
            Buffer.BlockCopy(e.Buffer, 0, samples, 0, e.BytesRecorded);
            AudioCaptured?.Invoke(samples);
        };
        _waveIn.StartRecording();
    }

    public void StopCapture() => _waveIn?.StopRecording();
    public void StartPlayback() { /* ... */ }
    public void StopPlayback() { /* ... */ }
    public void EnqueuePeerAudio(string peerId, short[] samples) { /* ... */ }
    public void RemovePeer(string peerId) { /* ... */ }
    public void ClearPeers() { /* ... */ }
    public void SetPlaybackVolume(float volume) { /* ... */ }
    public void KeepScreenAwake() { /* no-op on WPF */ }
    public void ReleaseScreenAwake() { /* no-op on WPF */ }
    public Task<bool> RequestMicrophonePermission() => Task.FromResult(true);
    public void Dispose() { StopCapture(); StopPlayback(); }
}

3. ASP.NET Core MVC

Status: Server-side only — No mic/speaker access

ASP.NET Core is a server-side framework and does not have direct access to microphones or speakers. However, it can:

  • Act as a relay server for audio routing
  • Serve the JavaScript SDK (voice-chat-sdk.js) to browsers
  • Use VoiceClient for server-side room connections (e.g., recording or monitoring)
ASP.NET Core Example — Serving SDK to Browser
// Controllers/VoiceController.cs
using Microsoft.AspNetCore.Mvc;

namespace MyWebApp.Controllers;

public class VoiceController : Controller
{
    public IActionResult Chat()
    {
        // HTML page with JavaScript SDK
        return View();
    }
}

<!DOCTYPE html>
<html>
<head>
    <title>Voice Chat</title>
    
    <script src="~/js/voice-chat-sdk.js"></script>
</head>
<body>
    <h1>Voice Chat Room</h1>
    <button onclick="join()">Join</button>
    <button onclick="toggleMic()">Toggle Mic</button>

    <script>
        const client = new VoiceChatSDK({
            server: 'callem.cloudfort.ir',
            apiKey: 'vc_live_YOUR_API_KEY',
            peerId: 'web-user-@ViewBag.UserId',
        });

        client.on('connected', () => client.joinRoom('general'));
        client.on('peer-speaking', (id) => console.log(`${id} speaking`));
        client.connect();

        async function join() {
            await client.joinRoom('general');
            await client.enableMic();
        }
        async function toggleMic() { await client.toggleMic(); }
    </script>
</body>
</html>
ASP.NET Core Example — Server-Side VoiceClient (Monitoring/Recording)
// Services/VoiceMonitor.cs
using Callum.Voice;

public class VoiceMonitorService : BackgroundService
{
    private VoiceClient? _client;

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        // Server without mic — for listening/monitoring only
        var audio = new HeadlessAudioEngine(); // No-audio implementation
        var config = new VoiceConfig
        {
            Server = "callem.cloudfort.ir",
            ApiKey = "vc_live_YOUR_API_KEY",
            PeerId = "server-monitor-01",
        };

        _client = new VoiceClient(config, audio);
        _client.PeerSpeaking += peerId =>
            Console.WriteLine($"[Monitor] {peerId} is speaking");

        await _client.ConnectAsync(ct);
        await _client.JoinRoomAsync("general");
    }
}

// HeadlessAudioEngine implementation (no audio hardware)
public class HeadlessAudioEngine : IAudioEngine
{
    public int SampleRate => 48_000;
    public event Action<short[]>? AudioCaptured;
    public event Action<string>? ErrorOccurred;
    public Task<bool> RequestMicrophonePermission() => Task.FromResult(true);
    public void StartCapture() { }
    public void StopCapture() { }
    public void StartPlayback() { }
    public void StopPlayback() { }
    public void EnqueuePeerAudio(string peerId, short[] samples)
    {
        // Receive audio but don't play it (monitoring only)
    }
    public void RemovePeer(string peerId) { }
    public void ClearPeers() { }
    public void SetPlaybackVolume(float volume) { }
    public void KeepScreenAwake() { }
    public void ReleaseScreenAwake() { }
    public void Dispose() { }
}

4. Blazor WebAssembly

Status: Usable — Web Audio API via JS Interop

Blazor WASM runs in the browser, so it must use voice-chat-sdk.js (JavaScript SDK) via IJSRuntime.

Blazor WASM Example
@page "/voice"
@inject IJSRuntime JS
@implements IAsyncDisposable

<h3>Voice Chat</h3>
<p>Status: @_status</p>
<p>Speaking: @_speaking</p>

<button @onclick="JoinRoom">Join</button>
<button @onclick="ToggleMic">Toggle Mic</button>

@code {
    private string _status = "Disconnected";
    private string _speaking = "";
    private IJSObjectReference? _jsModule;

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _jsModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./js/voice-interop.js");
        }
    }

    public async Task JoinRoom()
    {
        if (_jsModule is not null)
            await _jsModule.InvokeVoidAsync("joinVoiceRoom", "general");
    }

    public async Task ToggleMic()
    {
        if (_jsModule is not null)
            await _jsModule.InvokeVoidAsync("toggleMic");
    }

    public async ValueTask DisposeAsync()
    {
        if (_jsModule is not null)
            await _jsModule.DisposeAsync();
    }
}
// wwwroot/js/voice-interop.js
import VoiceChatSDK from './voice-chat-sdk.js';

const client = new VoiceChatSDK({
    server: 'callem.cloudfort.ir',
    apiKey: 'vc_live_YOUR_API_KEY',
    peerId: 'blazor-user-' + Date.now(),
});

client.on('connected', () => {
    DotNet.invokeMethodAsync('MyBlazorApp', 'OnVoiceStatus', 'Connected');
});

client.on('peer-speaking', (id) => {
    DotNet.invokeMethodAsync('MyBlazorApp', 'OnPeerSpeaking', id);
});

client.connect();

export function joinVoiceRoom(roomId) {
    client.joinRoom(roomId).then(() => client.enableMic());
}

export function toggleMic() {
    client.toggleMic();
}

5. Avalonia UI

Status: Usable — Similar to WPF

Avalonia is a cross-platform UI framework. To use Callum.Voice:

  • Windows: Use the Windows AudioEngine (WASAPI)
  • Linux/macOS: Implement a custom IAudioEngine with PulseAudio/CoreAudio
Avalonia Example
// MainWindowViewModel.cs
using Callum.Voice;

public class MainWindowViewModel : ViewModelBase
{
    private VoiceClient? _client;
    private string _status = "Disconnected";

    public string Status
    {
        get => _status;
        set => this.RaiseAndSetIfChanged(ref _status, value);
    }

    public async Task ConnectAsync()
    {
        var audio = new AudioEngine(sampleRate: 48_000); // Windows WASAPI
        var config = new VoiceConfig
        {
            Server = "callem.cloudfort.ir",
            ApiKey = "vc_live_YOUR_API_KEY",
            PeerId = $"avalonia-{Guid.NewGuid():N}",
        };

        _client = new VoiceClient(config, audio);
        _client.Connected += () => Status = "Connected";
        _client.PeerSpeaking += id => Status = $"{id} speaking";

        await _client.ConnectAsync();
        await _client.JoinRoomAsync("general");
        await _client.EnableMicAsync();
    }
}

6. Console App (.NET)

Status: Usable — Headless (no playback/capture)

For command-line tools, bots, or background services:

Console Example — Monitoring Bot
using Callum.Voice;

var audio = new HeadlessAudioEngine();
var config = new VoiceConfig
{
    Server = "callem.cloudfort.ir",
    ApiKey = "vc_live_YOUR_API_KEY",
    PeerId = $"bot-{Guid.NewGuid():N}",
};

using var client = new VoiceClient(config, audio);

client.Connected += () => Console.WriteLine("[+] Connected");
client.PeerJoined += id => Console.WriteLine($"[+] Peer joined: {id}");
client.PeerSpeaking += id => Console.WriteLine($"[!] {id} is speaking");
client.PeerLeft += id => Console.WriteLine($"[-] Peer left: {id}");
client.Disconnected += () => Console.WriteLine("[-] Disconnected");

await client.ConnectAsync();
await client.JoinRoomAsync("general");

Console.WriteLine("Monitoring room... Press Enter to exit.");
Console.ReadLine();

7. Unity

Status: Supported — Requires custom IAudioEngine implementation

The library ships a netstandard2.1 build that can be consumed by Unity (2021.2+). Since Unity has its own audio system, you need to implement IAudioEngine using Unity APIs (Microphone, AudioClip, OnAudioFilterRead).

Setup
  1. Install the NuGet package via NuGetForUnity or copy the DLLs manually:
    dotnet add package Callum.Voice
    
  2. Create a UnityAudioEngine class (see below)
  3. Use VoiceClient as you would on any other platform
Unity Example — UnityAudioEngine
using Callum.Voice;
using System.Collections.Concurrent;
using UnityEngine;

public class UnityAudioEngine : IAudioEngine
{
    private AudioClip? _micClip;
    private int _sampleRate = 48000;
    private float _volume = 1f;

    // Peer playback
    private readonly ConcurrentDictionary<string, ConcurrentQueue<short>> _peerBuffers = new();
    private float[] _mixBuffer = new float[480]; // 10ms at 48kHz

    public int SampleRate => _sampleRate;
    public event Action<short[]>? AudioCaptured;
    public event Action<string>? ErrorOccurred;

    public Task<bool> RequestMicrophonePermission()
    {
        // Unity handles mic permission via Application.RequestUserAuthorization
        return Task.FromResult(true);
    }

    public void StartCapture()
    {
        _sampleRate = AudioSettings.outputSampleRate;
        var mic = Microphone.devices.Length > 0 ? Microphone.devices[0] : null;
        _micClip = Microphone.Start(mic, true, 1, _sampleRate);
    }

    public void StopCapture()
    {
        if (_micClip != null)
        {
            Microphone.End(null);
            _micClip = null;
        }
    }

    public void StartPlayback() { }
    public void StopPlayback()
    {
        _peerBuffers.Clear();
    }

    public void EnqueuePeerAudio(string peerId, short[] samples)
    {
        var queue = _peerBuffers.GetOrAdd(peerId, _ => new ConcurrentQueue<short>());
        foreach (var s in samples) queue.Enqueue(s);
    }

    public void RemovePeer(string peerId)
    {
        _peerBuffers.TryRemove(peerId, out _);
    }

    public void ClearPeers() => _peerBuffers.Clear();

    public void SetPlaybackVolume(float volume) => _volume = volume;
    public void KeepScreenAwake() => Screen.sleepTimeout = SleepTimeout.NeverSleep;
    public void ReleaseScreenAwake() => Screen.sleepTimeout = SleepTimeout.SystemSetting;

    /// <summary>
    /// Called by Unity on the audio thread.
    /// Reads mic data and mixes peer audio into the output.
    /// Attach this component to a GameObject with an AudioSource.
    /// </summary>
    public void OnAudioFilterRead(float[] data, int channels)
    {
        // 1) Capture mic samples and fire event
        if (_micClip != null)
        {
            var pos = Microphone.GetPosition(null);
            if (pos > 0)
            {
                var micData = new float[pos];
                _micClip.GetData(micData, 0);
                var pcm = new short[micData.Length];
                for (int i = 0; i < micData.Length; i++)
                    pcm[i] = (short)(Mathf.Clamp(micData[i], -1f, 1f) * 32767f);
                AudioCaptured?.Invoke(pcm);
            }
        }

        // 2) Mix peer audio into output
        for (int i = 0; i < data.Length; i++) data[i] = 0f;

        foreach (var kvp in _peerBuffers)
        {
            for (int i = 0; i < data.Length; i += channels)
            {
                if (kvp.Value.TryDequeue(out short sample))
                {
                    float f = (sample / 32768f) * _volume;
                    for (int c = 0; c < channels; c++)
                        data[i + c] += f;
                }
            }
        }
    }

    public void Dispose()
    {
        StopCapture();
        StopPlayback();
    }
}
Unity Example — Usage in a MonoBehaviour
using Callum.Voice;
using UnityEngine;

public class VoiceChatManager : MonoBehaviour
{
    private VoiceClient? _client;
    private UnityAudioEngine? _audio;

    async void Start()
    {
        _audio = new UnityAudioEngine();

        var config = new VoiceConfig
        {
            Server = "callem.cloudfort.ir",
            ApiKey = "vc_live_YOUR_API_KEY",
            PeerId = $"unity-{System.Guid.NewGuid():N}",
            AutoReconnect = true,
        };

        _client = new VoiceClient(config, _audio);
        _client.Connected += () => Debug.Log("[Voice] Connected");
        _client.PeerJoined += id => Debug.Log($"[Voice] Peer joined: {id}");
        _client.PeerSpeaking += id => Debug.Log($"[Voice] {id} speaking");
        _client.Error += ex => Debug.LogError($"[Voice] Error: {ex.Message}");

        await _client.ConnectAsync();
        await _client.JoinRoomAsync("general");
        await _client.EnableMicAsync();
    }

    void OnAudioFilterRead(float[] data, int channels)
    {
        _audio?.OnAudioFilterRead(data, channels);
    }

    void OnDestroy()
    {
        _client?.Dispose();
    }
}

Note: Attach VoiceChatManager to a GameObject in your scene. The OnAudioFilterRead callback is used by Unity to process audio on the audio thread — it must be on a MonoBehaviour.


API Reference

VoiceClient

Method Return Type Description
ConnectAsync(ct) Task Connect to server
Disconnect() void Disconnect from server
JoinRoomAsync(roomId) Task Join a room
LeaveRoomAsync() Task Leave current room
EnableMicAsync() Task Enable microphone (requests permission)
DisableMic() void Disable microphone
ToggleMicAsync() Task<bool> Toggle microphone state
EnableSpeaker() void Enable speaker (Android)
DisableSpeaker() void Disable speaker
ToggleSpeaker() bool Toggle speaker state
IsPeerSpeaking(peerId) bool Check if peer is speaking
IsPeerSpeaking(peerId, out level) bool Also returns RMS level
Dispose() void Release resources

Properties

Property Type Description
State VoiceClientState Current connection state
IsConnected bool Whether connected to server
IsInRoom bool Whether in a room
IsMicEnabled bool Whether microphone is enabled
CurrentRoom string? Current room ID
Peers IReadOnlyCollection<string> List of peers in room

VoiceConfig

Property Type Default Description
Server string Server address (e.g., callem.cloudfort.ir)
ApiKey string API key (vc_live_...)
PeerId string Unique peer identifier
UseTls bool? null Use WSS (null = auto-detect)
SampleRate int 48000 Audio sample rate (Hz)
AutoReconnect bool true Auto reconnect on disconnect
MaxReconnectAttempts int 5 Maximum reconnect attempts
ReconnectDelay TimeSpan 2s Delay between attempts
EchoCancellation bool true Echo cancellation
NoiseSuppression bool true Noise suppression
AutoGainControl bool true Automatic gain control

Events

Event Type Description
Connected Action Connection established
Disconnected Action Connection lost
Reconnecting Action<int> Reconnection attempt (with attempt number)
AuthFailed Action<string> Authentication failed
RoomJoined Action<string> Joined a room
RoomLeft Action<string> Left a room
PeerJoined Action<string> New peer joined
PeerLeft Action<string> Peer left
PeerSpeaking Action<string> Peer started speaking
PeerStopped Action<string> Peer stopped speaking
MicEnabled Action Microphone enabled
MicDisabled Action Microphone disabled
Error Action<Exception> Error occurred
StateChanged Action<VoiceClientState> Connection state changed

Important Notes

Platform Support Summary

Platform Capture/Playback VoiceClient Custom IAudioEngine Needed
MAUI Android ✅ Ready ✅ Ready ❌ No
MAUI iOS ✅ Ready ✅ Ready ❌ No
MAUI Windows ✅ Ready ✅ Ready ❌ No
WPF ✅ Ready ✅ Ready ❌ No (WASAPI built-in)
Avalonia ✅ Ready ✅ Ready ⚠️ For Linux/macOS
ASP.NET Core ❌ Server-side ✅ Ready ✅ HeadlessAudioEngine
Blazor WASM ✅ Browser ✅ Via JS Interop ✅ JavaScript SDK
Console ❌ No hardware ✅ Ready ✅ HeadlessAudioEngine
Unity ✅ Via Unity Audio ✅ Ready ✅ UnityAudioEngine (provided)

Required Permissions

Platform Permission
Android RECORD_AUDIO (runtime)
iOS NSMicrophoneUsageDescription (Info.plist)
Windows Microphone (via OS settings)

Audio Format

  • PCM 16-bit signed integer
  • Mono (single channel)
  • 48,000 Hz sample rate (default)
  • Bitrate: ~768 kbps (48000 × 16 × 1)

Server

The Go server (phonil-opus) is available at callem.cloudfort.ir.
For self-hosting, refer to the server's README-SDK.md.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-android36.0 is compatible.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.1

  • net10.0-android36.0

  • net10.0-ios26.0

    • No dependencies.
  • net10.0-maccatalyst26.0

    • No dependencies.
  • net10.0-windows10.0.19041

    • No dependencies.

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
0.0.2 86 8/26/2026
0.0.1 85 8/25/2026