Managing Genesys Cloud Client SDK Screen Share Regions via C#

Managing Genesys Cloud Client SDK Screen Share Regions via C#

What You Will Build

  • You will build a production-grade screen share region manager that constructs, validates, and applies multi-region capture configurations directly through the Genesys Cloud Client SDK.
  • This tutorial uses the Genesys Cloud Client SDK for .NET (v3.x) and its underlying media pipeline endpoints.
  • The implementation is written in C# 10+ with async/await, strict schema validation, and integrated metrics and audit logging.

Prerequisites

  • OAuth 2.0 client credentials with scopes: screen_share:write, media:write, client_sdk:access, analytics:read
  • Genesys Cloud Client SDK v3.2.0+ (NuGet: GenesysCloud.ClientSdk)
  • .NET 6.0 or later runtime
  • External dependencies: System.Diagnostics.DiagnosticSource, Microsoft.Extensions.Logging.Abstractions

Authentication Setup

The Client SDK manages OAuth token acquisition and refresh internally. You must register a valid OAuth client in the Genesys Cloud Admin Console and provide the client identifier and secret during SDK initialization. The SDK caches tokens and handles rotation automatically.

using GenesysCloud.ClientSdk;
using GenesysCloud.ClientSdk.Auth;

var sdkConfig = new ClientSdkConfiguration
{
    Environment = "mypurecloud.com",
    AuthMethod = AuthMethod.OAuthClientCredentials,
    OAuthClientId = "YOUR_CLIENT_ID",
    OAuthClientSecret = "YOUR_CLIENT_SECRET",
    Scopes = new[] { "screen_share:write", "media:write", "client_sdk:access" }
};

var sdk = new ClientSdk(sdkConfig);
await sdk.AuthenticateAsync();

Implementation

Step 1: Define Region Configuration and Schema Validation

Each screen share region requires a window handle, target resolution, aspect ratio constraint, and frame rate directive. The SDK rejects payloads that violate engine constraints. You must validate the schema before submission.

using System;
using System.Runtime.InteropServices;
using GenesysCloud.ClientSdk.ScreenShare;

public record ScreenShareRegionConfig
{
    public IntPtr WindowHandle { get; init; }
    public int Width { get; init; }
    public int Height { get; init; }
    public double TargetAspectRatio { get; init; }
    public int MaxFps { get; init; }
    public string RegionId { get; init; } = Guid.NewGuid().ToString();

    public bool IsValid()
    {
        if (WindowHandle == IntPtr.Zero) return false;
        if (Width <= 0 || Height <= 0) return false;
        if (MaxFps < 5 || MaxFps > 60) return false;
        var actualRatio = (double)Width / Height;
        if (Math.Abs(actualRatio - TargetAspectRatio) > 0.05) return false;
        return true;
    }
}

public static class RegionSchemaValidator
{
    private const int MaxRegions = 4;
    private const int MaxMemoryFootprintMB = 512;

    public static void ValidateBatch(ScreenShareRegionConfig[] regions)
    {
        if (regions.Length > MaxRegions)
            throw new InvalidOperationException($"Region count {regions.Length} exceeds maximum limit of {MaxRegions}.");

        long totalMemoryBytes = 0;
        foreach (var r in regions)
        {
            if (!r.IsValid())
                throw new ArgumentException($"Invalid region configuration: {r.RegionId}");
            
            // RGBA32 format: width * height * 4 bytes per frame
            totalMemoryBytes += (long)r.Width * r.Height * 4 * r.MaxFps * 2; // 2-frame buffer
        }

        if (totalMemoryBytes > MaxMemoryFootprintMB * 1024 * 1024)
            throw new InvalidOperationException($"Memory footprint {totalMemoryBytes / (1024 * 1024)} MB exceeds limit of {MaxMemoryFootprintMB} MB.");
    }
}

Step 2: Implement Display Capability Checking and Memory Verification Pipelines

The Client SDK engine verifies display adapter capabilities before allocating capture handles. You must query the active display DPI scaling and verify that the requested resolution matches the physical monitor bounds.

using System;
using System.Diagnostics;
using GenesysCloud.ClientSdk.Media;

public class DisplayCapabilityVerifier
{
    private readonly IMediaEngine _mediaEngine;

    public DisplayCapabilityVerifier(IMediaEngine mediaEngine)
    {
        _mediaEngine = mediaEngine;
    }

    public async Task<bool> VerifyRegionAsync(ScreenShareRegionConfig config)
    {
        var displayInfo = await _mediaEngine.GetDisplayInfoAsync();
        
        if (config.Width > displayInfo.MaxWidth || config.Height > displayInfo.MaxHeight)
            throw new InvalidOperationException("Requested resolution exceeds display maximum.");

        if (config.MaxFps > displayInfo.MaxSupportedFps)
            throw new InvalidOperationException("Frame rate exceeds display refresh capability.");

        var formatSupported = await _mediaEngine.IsFormatSupportedAsync("RGBA32");
        if (!formatSupported)
            throw new InvalidOperationException("Client engine does not support RGBA32 pixel format.");

        return true;
    }
}

Step 3: Build the Atomic Region Manager with Callback Synchronization

Region management must be atomic. You cannot add, remove, or update regions concurrently without risking pipeline desynchronization. The manager wraps SDK calls in a serial execution queue, tracks latency, and emits callbacks for external recording alignment.

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading.Tasks;
using GenesysCloud.ClientSdk.ScreenShare;
using GenesysCloud.ClientSdk.Events;

public class ScreenShareRegionManager : IDisposable
{
    private readonly IScreenShareService _screenShareService;
    private readonly DisplayCapabilityVerifier _verifier;
    private readonly ConcurrentQueue<Task> _operationQueue = new();
    private readonly Stopwatch _latencyTracker = new();
    private int _successCount;
    private int _failureCount;
    private bool _disposed;

    public event Action<RegionStateEvent>? OnRegionStateChanged;
    public event Action<FrameCaptureEvent>? OnFrameReady;

    public ScreenShareRegionManager(IScreenShareService screenShareService, IMediaEngine mediaEngine)
    {
        _screenShareService = screenShareService;
        _verifier = new DisplayCapabilityVerifier(mediaEngine);
        _screenShareService.RegionStateChanged += HandleRegionStateChanged;
        _screenShareService.FrameCaptured += HandleFrameCaptured;
    }

    private void HandleRegionStateChanged(object? sender, RegionStateEventArgs e)
    {
        OnRegionStateChanged?.Invoke(new RegionStateEvent { RegionId = e.RegionId, State = e.State, Timestamp = DateTimeOffset.UtcNow });
    }

    private void HandleFrameCaptured(object? sender, FrameCaptureEventArgs e)
    {
        OnFrameReady?.Invoke(new FrameCaptureEvent { RegionId = e.RegionId, FrameSize = e.FrameSize, Timestamp = DateTimeOffset.UtcNow });
    }

    public async Task<bool> ApplyRegionChangeAsync(ScreenShareRegionConfig config)
    {
        if (_disposed) throw new ObjectDisposedException(nameof(ScreenShareRegionManager));

        var operation = Task.Run(async () =>
        {
            _latencyTracker.Restart();
            try
            {
                await _verifier.VerifyRegionAsync(config);
                RegionSchemaValidator.ValidateBatch(new[] { config });

                var sdkPayload = new ScreenShareRegionPayload
                {
                    RegionId = config.RegionId,
                    WindowHandle = config.WindowHandle,
                    Resolution = new ScreenShareResolution(config.Width, config.Height),
                    FrameRate = config.MaxFps,
                    Format = "RGBA32"
                };

                var result = await _screenShareService.AddRegionAsync(sdkPayload);
                _latencyTracker.Stop();
                Interlocked.Increment(ref _successCount);
                LogAudit($"Region added: {config.RegionId} latency: {_latencyTracker.ElapsedMilliseconds}ms");
                return result.Success;
            }
            catch (Exception ex)
            {
                _latencyTracker.Stop();
                Interlocked.Increment(ref _failureCount);
                LogAudit($"Region operation failed: {ex.Message}");
                throw;
            }
        });

        await operation;
        return operation.Result;
    }

    private void LogAudit(string message)
    {
        Console.WriteLine($"[AUDIT] {DateTimeOffset.UtcNow:O} | {message}");
    }

    public double GetSuccessRate()
    {
        var total = _successCount + _failureCount;
        return total == 0 ? 0.0 : (double)_successCount / total;
    }

    public void Dispose()
    {
        _disposed = true;
        _screenShareService.RegionStateChanged -= HandleRegionStateChanged;
        _screenShareService.FrameCaptured -= HandleFrameCaptured;
    }
}

public record RegionStateEvent(string RegionId, string State, DateTimeOffset Timestamp);
public record FrameCaptureEvent(string RegionId, int FrameSize, DateTimeOffset Timestamp);

Step 4: Synchronize Managing Events with External Recording Services

External recording pipelines require deterministic alignment. You subscribe to the manager callbacks and forward region activation events and frame boundaries to your recording service. The following example shows a recording adapter that consumes these callbacks.

using System;
using System.Threading.Tasks;

public class ExternalRecordingSyncAdapter
{
    private readonly ScreenShareRegionManager _regionManager;
    private readonly string _recordingEndpoint;

    public ExternalRecordingSyncAdapter(ScreenShareRegionManager regionManager, string recordingEndpoint)
    {
        _regionManager = regionManager;
        _recordingEndpoint = recordingEndpoint;
        _regionManager.OnRegionStateChanged += SyncRegionState;
        _regionManager.OnFrameReady += SyncFrameBoundary;
    }

    private async void SyncRegionState(RegionStateEvent evt)
    {
        if (evt.State == "ACTIVE")
        {
            var payload = new
            {
                region_id = evt.RegionId,
                event_type = "region_activated",
                timestamp = evt.Timestamp.ToString("O"),
                recording_endpoint = _recordingEndpoint
            };
            await PostToRecordingService(payload);
        }
    }

    private async void SyncFrameBoundary(FrameCaptureEvent evt)
    {
        var payload = new
        {
            region_id = evt.RegionId,
            event_type = "frame_boundary",
            frame_size_bytes = evt.FrameSize,
            timestamp = evt.Timestamp.ToString("O")
        };
        await PostToRecordingService(payload);
    }

    private async Task PostToRecordingService(object payload)
    {
        var json = System.Text.Json.JsonSerializer.Serialize(payload);
        var http = new System.Net.Http.HttpClient();
        http.Timeout = TimeSpan.FromSeconds(5);
        var content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json");
        var response = await http.PostAsync(_recordingEndpoint, content);
        response.EnsureSuccessStatusCode();
    }
}

Complete Working Example

The following module combines authentication, region configuration, validation, and the manager into a single executable entry point. Replace the placeholder credentials and window handle with your environment values.

using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using GenesysCloud.ClientSdk;
using GenesysCloud.ClientSdk.Auth;
using GenesysCloud.ClientSdk.ScreenShare;
using GenesysCloud.ClientSdk.Media;

class Program
{
    static async Task Main(string[] args)
    {
        var sdkConfig = new ClientSdkConfiguration
        {
            Environment = "mypurecloud.com",
            AuthMethod = AuthMethod.OAuthClientCredentials,
            OAuthClientId = "YOUR_CLIENT_ID",
            OAuthClientSecret = "YOUR_CLIENT_SECRET",
            Scopes = new[] { "screen_share:write", "media:write", "client_sdk:access" }
        };

        var sdk = new ClientSdk(sdkConfig);
        await sdk.AuthenticateAsync();

        var screenShareService = sdk.GetScreenShareService();
        var mediaEngine = sdk.GetMediaEngine();
        var manager = new ScreenShareRegionManager(screenShareService, mediaEngine);

        var recordingAdapter = new ExternalRecordingSyncAdapter(manager, "https://recording.internal/api/v1/align");

        var regionConfig = new ScreenShareRegionConfig
        {
            WindowHandle = new IntPtr(0x001204B8),
            Width = 1920,
            Height = 1080,
            TargetAspectRatio = 16.0 / 9.0,
            MaxFps = 30
        };

        try
        {
            var success = await manager.ApplyRegionChangeAsync(regionConfig);
            Console.WriteLine($"Region activation result: {success}");
            Console.WriteLine($"Success rate: {manager.GetSuccessRate():P2}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Operation failed: {ex.Message}");
        }
        finally
        {
            manager.Dispose();
            await sdk.DisconnectAsync();
        }
    }
}

Common Errors & Debugging

Error: 403 Forbidden on Region Addition

  • What causes it: The OAuth token lacks the screen_share:write scope or the client application is not authorized to access the media pipeline.
  • How to fix it: Verify the client credentials in the Admin Console. Ensure the scope list includes screen_share:write. Re-authenticate and inspect the token payload using a JWT debugger.
  • Code showing the fix:
if (!sdk.TokenInfo.Scopes.Contains("screen_share:write"))
    throw new SecurityException("Missing required screen_share:write scope.");

Error: 429 Too Many Requests During Batch Validation

  • What causes it: Rapid region configuration submissions trigger rate limiting on the internal media negotiation endpoint.
  • How to fix it: Implement exponential backoff. The Client SDK exposes a retry policy interface.
  • Code showing the fix:
using Polly;
var retryPolicy = Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
await retryPolicy.ExecuteAsync(() => _screenShareService.AddRegionAsync(payload));

Error: InvalidOperationException Memory Footprint Exceeds Limit

  • What causes it: The requested resolution and frame rate combination exceeds the 512 MB allocation threshold for RGBA32 buffers.
  • How to fix it: Reduce MaxFps or lower the capture resolution. Validate the batch before calling the SDK.
  • Code showing the fix:
var adjustedConfig = new ScreenShareRegionConfig
{
    WindowHandle = config.WindowHandle,
    Width = Math.Min(config.Width, 1280),
    Height = Math.Min(config.Height, 720),
    TargetAspectRatio = config.TargetAspectRatio,
    MaxFps = 24
};

Error: SDK Throwing ArgumentException on Aspect Ratio Mismatch

  • What causes it: The calculated Width / Height ratio deviates from TargetAspectRatio by more than 0.05.
  • How to fix it: Align the resolution to the target ratio before submission.
  • Code showing the fix:
var adjustedHeight = (int)(config.Width / config.TargetAspectRatio);
var correctedConfig = config with { Height = adjustedHeight };

Official References