Files
crm-calls/calls/backend/Calls.Api/Recording/JitsiClients.cs

86 lines
4.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
namespace Calls.Api.Recording;
/// <summary>Участники комнаты из prosody (mod_muc_size: GET /room?room=…&amp;domain=…), сеть crm-calls-bridge.</summary>
public sealed class ProsodyRooms(IHttpClientFactory http, RecordingOptions opt)
{
/// <summary>Имена участников (без focus и без скрытого рекордера — у них пустой ник). null — prosody недоступен.</summary>
public async Task<List<string>?> ParticipantsAsync(string room, CancellationToken ct)
{
try
{
using var req = new HttpRequestMessage(HttpMethod.Get,
$"{opt.ProsodyUrl}/room?room={Uri.EscapeDataString(room)}&domain={Uri.EscapeDataString(opt.XmppDomain)}");
// Маршрут mod_muc_size висит на виртуальном хосте XMPP-домена.
req.Headers.Host = opt.XmppDomain;
using var r = await http.CreateClient("jitsi").SendAsync(req, ct);
if (r.StatusCode == HttpStatusCode.NotFound) return []; // комнаты нет — пусто
if (!r.IsSuccessStatusCode) return null;
using var doc = JsonDocument.Parse(await r.Content.ReadAsStringAsync(ct));
if (doc.RootElement.ValueKind != JsonValueKind.Array) return null;
var names = new List<string>();
foreach (var o in doc.RootElement.EnumerateArray())
{
var name = o.TryGetProperty("display_name", out var n) && n.ValueKind == JsonValueKind.String ? n.GetString()!.Trim() : "";
if (name.Length > 0) names.Add(name);
}
return names;
}
catch (Exception e) when (e is HttpRequestException or TaskCanceledException or JsonException) { return null; }
}
}
/// <summary>
/// Jibri HTTP API (http://jitsi-jibri:2222/jibri/api/v1.0). Запись в файл под скрытым пользователем recorder,
/// каталог записи — RECORDINGS_DIR/&lt;sessionId&gt;.
/// </summary>
public sealed class JibriClient(IHttpClientFactory http, RecordingOptions opt)
{
public enum Busy { Idle, Busy, Unknown }
public async Task<Busy> StatusAsync(CancellationToken ct)
{
try
{
using var r = await http.CreateClient("jitsi").GetAsync($"{opt.JibriUrl}/jibri/api/v1.0/health", ct);
if (!r.IsSuccessStatusCode) return Busy.Unknown;
using var doc = JsonDocument.Parse(await r.Content.ReadAsStringAsync(ct));
var s = doc.RootElement.GetProperty("status").GetProperty("busyStatus").GetString();
return s == "IDLE" ? Busy.Idle : s == "BUSY" ? Busy.Busy : Busy.Unknown;
}
catch (Exception e) when (e is HttpRequestException or TaskCanceledException or JsonException or KeyNotFoundException or InvalidOperationException)
{ return Busy.Unknown; }
}
/// <summary>true — Jibri принял запрос (дальше он асинхронно заходит в комнату).</summary>
public async Task<(bool Ok, string Detail)> StartAsync(string sessionId, string room, CancellationToken ct)
{
var body = new
{
sessionId,
callParams = new { callUrlInfo = new { baseUrl = opt.MeetBaseUrl, callName = room } },
callLoginParams = new { domain = opt.RecorderDomain, username = opt.RecorderUser, password = opt.RecorderPassword },
sinkType = "file",
};
try
{
using var r = await http.CreateClient("jitsi").PostAsJsonAsync($"{opt.JibriUrl}/jibri/api/v1.0/startService", body, ct);
return (r.IsSuccessStatusCode, $"{(int)r.StatusCode} {await r.Content.ReadAsStringAsync(ct)}".Trim());
}
catch (Exception e) when (e is HttpRequestException or TaskCanceledException) { return (false, e.Message); }
}
public async Task<bool> StopAsync(CancellationToken ct)
{
try
{
using var r = await http.CreateClient("jitsi").PostAsync($"{opt.JibriUrl}/jibri/api/v1.0/stopService", null, ct);
return r.IsSuccessStatusCode;
}
catch (Exception e) when (e is HttpRequestException or TaskCanceledException) { return false; }
}
}