using System.Text.Json; using RelayShared.Rtc; using RelayServer.Services.Rtc; namespace RelayServer.Endpoints; public static class RtcEndpoints { /// /// Maps all RTC-related HTTP endpoints used for storing offers and answers, /// writing ICE candidates, checking active calls, and leaving active calls. /// /// The web application to map endpoints onto. public static void MapRtcEndpoints(this WebApplication app) { // Store or update the current SDP offer for a channel call. app.MapPost("/api/rtc/offer", async (RtcOffer request, RtcCallService rtcCallService) => { await rtcCallService.WriteOfferAsync(request.ChannelId, request.Username, request.SessionDescription); RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage { Type = SignalType.OfferUpdated, ChannelId = request.ChannelId, Username = request.Username }); return Results.Ok(); }); // List all offers. app.MapGet("/api/rtc/offers", async (RtcCallService rtcCallService) => { return Results.Ok(await rtcCallService.GetOffersAsync()); }); // Return whether the specified channel currently has an active call. app.MapGet("/api/rtc/active/{channelId}", async (string channelId, RtcCallService rtcCallService) => { return Results.Ok(await rtcCallService.HasActiveCallAsync(channelId)); }); // Return the latest stored SDP offer for the specified channel. app.MapGet("/api/rtc/offers/{channelId}", async (string channelId, RtcCallService rtcCallService) => { var offer = await rtcCallService.GetOfferAsync(channelId); return offer is null ? Results.NotFound() : Results.Ok(offer); }); // Store a new SDP answer for the specified channel call. app.MapPost("/api/rtc/answer", async (RtcOffer request, RtcCallService rtcCallService) => { Console.WriteLine($"RTC answer received for channel {request.ChannelId} from {request.Username}"); await rtcCallService.WriteAnswerAsync(request.ChannelId, request.SessionDescription); Console.WriteLine($"Broadcasting rtc_answer_updated for {request.ChannelId}"); RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage { Type = SignalType.AnswerUpdated, ChannelId = request.ChannelId }); return Results.Ok(); }); // Return all answers stored for the specified channel. app.MapGet("/api/rtc/answers/{channelId}", async (string channelId, RtcCallService rtcCallService) => { return Results.Ok(await rtcCallService.GetAnswersAsync(channelId)); }); app.MapGet("/api/rtc/participants/{channelId}", (string channelId) => { return Results.Ok(RtcChannelPresenceService.GetUsersInChannel(channelId)); }); // Return the latest answer stored for the specified channel. app.MapGet("/api/rtc/answer/{channelId}", async (string channelId, RtcCallService rtcCallService) => { var answer = await rtcCallService.GetLatestAnswerAsync(channelId); return answer is null ? Results.NotFound() : Results.Ok(answer); }); // Store a new ICE candidate for the specified channel call. app.MapPost("/api/rtc/candidate", async (RtcIceCandidate request, RtcCallService rtcCallService) => { await rtcCallService.WriteIceCandidateAsync( request.ChannelId, request.Username, request.Candidate.candidate, request.Candidate.sdpMid, request.Candidate.sdpMLineIndex ); RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage { Type = SignalType.CandidateAdded, ChannelId = request.ChannelId, Username = request.Username, Direction = JsonSerializer.Serialize(request.Candidate) }); return Results.Ok(); }); // Return all ICE candidates stored for the specified channel. app.MapGet("/api/rtc/candidates/{channelId}", async (string channelId, RtcCallService rtcCallService) => { return Results.Ok(await rtcCallService.GetIceCandidatesAsync(channelId)); }); // Return ICE candidates for the specified channel that belong to other users // and match the requested direction. app.MapGet("/api/rtc/candidates/{channelId}/{username}/{direction}", async ( string channelId, string username, string direction, RtcCallService rtcCallService) => { return Results.Ok(await rtcCallService.GetIceCandidatesForOthersAsync(channelId, username, direction)); }); // Leave the active call for the specified channel. app.MapPost("/api/rtc/leave", async (RtcLeaveRequest request, RtcCallService rtcCallService) => { await rtcCallService.LeaveCallAsync(request.ChannelId, request.Username); RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage { Type = SignalType.CallLeft, ChannelId = request.ChannelId, Username = request.Username }); return Results.Ok(); }); } }