89 lines
4.6 KiB
C#
89 lines
4.6 KiB
C#
using System.Net;
|
||
using System.Net.Http.Json;
|
||
using System.Text.Json;
|
||
|
||
namespace Calls.Api.Recording;
|
||
|
||
/// <summary>Участники комнаты из prosody (mod_muc_size: GET /room?room=…&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/<sessionId>.
|
||
/// </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
|
||
{
|
||
// stopService ждёт, пока Jibri закроет ffmpeg и Chrome — это дольше обычного таймаута клиента.
|
||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||
cts.CancelAfter(TimeSpan.FromSeconds(60));
|
||
using var r = await http.CreateClient("jibri-stop").PostAsync($"{opt.JibriUrl}/jibri/api/v1.0/stopService", null, cts.Token);
|
||
return r.IsSuccessStatusCode;
|
||
}
|
||
catch (Exception e) when (e is HttpRequestException or TaskCanceledException) { return false; }
|
||
}
|
||
}
|