141 lines
5.6 KiB
C#
141 lines
5.6 KiB
C#
using System.Text.Json;
|
|
using RelayShared.Rtc;
|
|
using RelayServer.Services.Rtc;
|
|
using RelayShared.Services;
|
|
|
|
namespace RelayServer.Endpoints;
|
|
|
|
public static class RtcEndpoints
|
|
{
|
|
/// <summary>
|
|
/// Maps all RTC-related HTTP endpoints used for storing offers and answers,
|
|
/// writing ICE candidates, checking active calls, and leaving active calls.
|
|
/// </summary>
|
|
/// <param name="app">The web application to map endpoints onto.</param>
|
|
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();
|
|
});
|
|
}
|
|
} |