using RelayServer.Models.Rtc; using SurrealDb.Net; namespace RelayServer.Services.Rtc; public sealed class RtcCallService { private readonly SurrealDbClient _db; public RtcCallService(SurrealDbClient db) { _db = db; } public async Task JoinCallAsync(string channelId, string username) { var activeCalls = await _db.Select("rtc_active_calls"); var activeCall = activeCalls.FirstOrDefault(x => x.ChannelId == channelId && x.IsActive); if (activeCall is null) { await _db.Create("rtc_active_calls", new RtcActiveCall { ChannelId = channelId, OfferUser = username, IsActive = true, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }); return new RtcJoinResponse { ChannelId = channelId, HasActiveCall = false, IsOfferer = true, OfferUser = username, OfferSdp = null }; } var offers = await _db.Select("rtc_offers"); var offer = offers .Where(x => x.ChannelId == channelId) .OrderByDescending(x => x.CreatedAt) .FirstOrDefault(); return new RtcJoinResponse { ChannelId = channelId, HasActiveCall = true, IsOfferer = false, OfferUser = activeCall.OfferUser, OfferSdp = offer?.Sdp }; } public async Task WriteOfferAsync(string channelId, string username, string sdp) { var offers = await _db.Select("rtc_offers"); var existing = offers.FirstOrDefault(x => x.ChannelId == channelId && x.Username == username); if (existing is null) { await _db.Create("rtc_offers", new RtcOffer { ChannelId = channelId, Username = username, Sdp = sdp, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }); return; } existing.Sdp = sdp; existing.UpdatedAt = DateTime.UtcNow; await _db.Merge(existing); } public async Task GetOfferAsync(string channelId) { var offers = await _db.Select("rtc_offers"); return offers .Where(x => x.ChannelId == channelId) .OrderByDescending(x => x.CreatedAt) .FirstOrDefault(); } public async Task WriteAnswerAsync(string channelId, string offerUser, string answerUser, string sdp) { await _db.Create("rtc_answers", new RtcAnswer { ChannelId = channelId, OfferUser = offerUser, AnswerUser = answerUser, Sdp = sdp, CreatedAt = DateTime.UtcNow }); } public async Task> GetAnswersAsync(string channelId) { var answers = await _db.Select("rtc_answers"); return answers .Where(x => x.ChannelId == channelId) .OrderBy(x => x.CreatedAt) .ToList(); } public async Task WriteIceCandidateAsync(string channelId, string username, string candidate, string? sdpMid, int? sdpMLineIndex, string direction) { await _db.Create("rtc_ice_candidates", new RtcIceCandidate { ChannelId = channelId, Username = username, Candidate = candidate, SdpMid = sdpMid, SdpMLineIndex = sdpMLineIndex, Direction = direction, CreatedAt = DateTime.UtcNow }); } public async Task> GetIceCandidatesAsync(string channelId) { var candidates = await _db.Select("rtc_ice_candidates"); return candidates .Where(x => x.ChannelId == channelId) .OrderBy(x => x.CreatedAt) .ToList(); } public async Task LeaveCallAsync(string channelId, string username) { var activeCalls = await _db.Select("rtc_active_calls"); var activeCall = activeCalls.FirstOrDefault(x => x.ChannelId == channelId && x.IsActive); if (activeCall is null) return; if (activeCall.OfferUser == username) { activeCall.IsActive = false; activeCall.UpdatedAt = DateTime.UtcNow; await _db.Merge(activeCall); } } }