✨ The call ended by webhook now sends end message
This commit is contained in:
parent
b913682866
commit
c21cdeba74
@ -15,6 +15,7 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
private readonly AppDatabase _db;
|
private readonly AppDatabase _db;
|
||||||
private readonly ICacheService _cache;
|
private readonly ICacheService _cache;
|
||||||
private readonly WebSocketService _ws;
|
private readonly WebSocketService _ws;
|
||||||
|
private readonly ChatService _cs;
|
||||||
|
|
||||||
private readonly ILogger<LivekitRealtimeService> _logger;
|
private readonly ILogger<LivekitRealtimeService> _logger;
|
||||||
private readonly RoomServiceClient _roomService;
|
private readonly RoomServiceClient _roomService;
|
||||||
@ -26,7 +27,8 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
ILogger<LivekitRealtimeService> logger,
|
ILogger<LivekitRealtimeService> logger,
|
||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
ICacheService cache,
|
ICacheService cache,
|
||||||
WebSocketService ws
|
WebSocketService ws,
|
||||||
|
ChatService cs
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -46,6 +48,7 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
_db = db;
|
_db = db;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_ws = ws;
|
_ws = ws;
|
||||||
|
_cs = cs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -125,7 +128,8 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
RoomAdmin = isAdmin,
|
RoomAdmin = isAdmin,
|
||||||
Room = sessionId
|
Room = sessionId
|
||||||
})
|
})
|
||||||
.WithMetadata(JsonSerializer.Serialize(new Dictionary<string, string> { { "account_id", account.Id.ToString() } }))
|
.WithMetadata(JsonSerializer.Serialize(new Dictionary<string, string>
|
||||||
|
{ { "account_id", account.Id.ToString() } }))
|
||||||
.WithTtl(TimeSpan.FromHours(1));
|
.WithTtl(TimeSpan.FromHours(1));
|
||||||
return token.ToJwt();
|
return token.ToJwt();
|
||||||
}
|
}
|
||||||
@ -139,10 +143,29 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
{
|
{
|
||||||
case "room_finished":
|
case "room_finished":
|
||||||
var now = SystemClock.Instance.GetCurrentInstant();
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
await _db.ChatRealtimeCall
|
var call = await _db.ChatRealtimeCall
|
||||||
.Where(c => c.SessionId == evt.Room.Name)
|
.Where(c => c.SessionId == evt.Room.Name)
|
||||||
.ExecuteUpdateAsync(s => s.SetProperty(p => p.EndedAt, now)
|
.Include(c => c.Room)
|
||||||
);
|
.Include(c => c.Sender)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (call is not null)
|
||||||
|
{
|
||||||
|
await _cs.SendMessageAsync(new Message
|
||||||
|
{
|
||||||
|
Type = "call.ended",
|
||||||
|
ChatRoomId = call.RoomId,
|
||||||
|
SenderId = call.SenderId,
|
||||||
|
Meta = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "call_id", call.Id },
|
||||||
|
{ "duration", (call.EndedAt!.Value - call.CreatedAt).TotalSeconds }
|
||||||
|
}
|
||||||
|
}, call.Sender, call.Room);
|
||||||
|
await _db.ChatRealtimeCall
|
||||||
|
.Where(c => c.SessionId == evt.Room.Name)
|
||||||
|
.ExecuteUpdateAsync(s => s.SetProperty(p => p.EndedAt, now)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Also clean up participants list when the room is finished
|
// Also clean up participants list when the room is finished
|
||||||
await _cache.RemoveAsync(_GetParticipantsKey(evt.Room.Name));
|
await _cache.RemoveAsync(_GetParticipantsKey(evt.Room.Name));
|
||||||
@ -160,6 +183,7 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
// Broadcast participant list update to all participants
|
// Broadcast participant list update to all participants
|
||||||
await _BroadcastParticipantUpdate(evt.Room.Name);
|
await _BroadcastParticipantUpdate(evt.Room.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "participant_left":
|
case "participant_left":
|
||||||
@ -174,6 +198,7 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
// Broadcast participant list update to all participants
|
// Broadcast participant list update to all participants
|
||||||
await _BroadcastParticipantUpdate(evt.Room.Name);
|
await _BroadcastParticipantUpdate(evt.Room.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,14 +297,14 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
// Try to parse account ID from metadata
|
// Try to parse account ID from metadata
|
||||||
Guid? accountId = null;
|
Guid? accountId = null;
|
||||||
var metadata = new Dictionary<string, string>();
|
var metadata = new Dictionary<string, string>();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(participant.Metadata))
|
if (!string.IsNullOrEmpty(participant.Metadata))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
metadata = JsonSerializer.Deserialize<Dictionary<string, string>>(participant.Metadata) ??
|
metadata = JsonSerializer.Deserialize<Dictionary<string, string>>(participant.Metadata) ??
|
||||||
new Dictionary<string, string>();
|
new Dictionary<string, string>();
|
||||||
|
|
||||||
if (metadata.TryGetValue("account_id", out var accountIdStr))
|
if (metadata.TryGetValue("account_id", out var accountIdStr))
|
||||||
{
|
{
|
||||||
if (Guid.TryParse(accountIdStr, out var parsedId))
|
if (Guid.TryParse(accountIdStr, out var parsedId))
|
||||||
@ -304,7 +329,7 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
JoinedAt = DateTime.UtcNow
|
JoinedAt = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast participant update to all participants in a room
|
// Broadcast participant update to all participants in a room
|
||||||
private async Task _BroadcastParticipantUpdate(string roomName)
|
private async Task _BroadcastParticipantUpdate(string roomName)
|
||||||
{
|
{
|
||||||
@ -315,28 +340,28 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
.Where(c => c.SessionId == roomName && c.EndedAt == null)
|
.Where(c => c.SessionId == roomName && c.EndedAt == null)
|
||||||
.Select(c => new { c.RoomId, c.Id })
|
.Select(c => new { c.RoomId, c.Id })
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
if (roomInfo == null)
|
if (roomInfo == null)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Could not find room info for session: {SessionName}", roomName);
|
_logger.LogWarning("Could not find room info for session: {SessionName}", roomName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current participants
|
// Get current participants
|
||||||
var livekitParticipants = await GetRoomParticipantsAsync(roomName);
|
var livekitParticipants = await GetRoomParticipantsAsync(roomName);
|
||||||
|
|
||||||
// Get all room members who should receive this update
|
// Get all room members who should receive this update
|
||||||
var roomMembers = await _db.ChatMembers
|
var roomMembers = await _db.ChatMembers
|
||||||
.Where(m => m.ChatRoomId == roomInfo.RoomId && m.LeaveAt == null)
|
.Where(m => m.ChatRoomId == roomInfo.RoomId && m.LeaveAt == null)
|
||||||
.Select(m => m.AccountId)
|
.Select(m => m.AccountId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
// Get member profiles for participants who have account IDs
|
// Get member profiles for participants who have account IDs
|
||||||
var accountIds = livekitParticipants
|
var accountIds = livekitParticipants
|
||||||
.Where(p => p.AccountId.HasValue)
|
.Where(p => p.AccountId.HasValue)
|
||||||
.Select(p => p.AccountId!.Value)
|
.Select(p => p.AccountId!.Value)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var memberProfiles = new Dictionary<Guid, ChatMember>();
|
var memberProfiles = new Dictionary<Guid, ChatMember>();
|
||||||
if (accountIds.Any())
|
if (accountIds.Any())
|
||||||
{
|
{
|
||||||
@ -344,7 +369,7 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
.Where(m => m.ChatRoomId == roomInfo.RoomId && accountIds.Contains(m.AccountId))
|
.Where(m => m.ChatRoomId == roomInfo.RoomId && accountIds.Contains(m.AccountId))
|
||||||
.ToDictionaryAsync(m => m.AccountId, m => m);
|
.ToDictionaryAsync(m => m.AccountId, m => m);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to CallParticipant objects
|
// Convert to CallParticipant objects
|
||||||
var participants = livekitParticipants.Select(p => new CallParticipant
|
var participants = livekitParticipants.Select(p => new CallParticipant
|
||||||
{
|
{
|
||||||
@ -352,11 +377,11 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
Name = p.Name,
|
Name = p.Name,
|
||||||
AccountId = p.AccountId,
|
AccountId = p.AccountId,
|
||||||
JoinedAt = p.JoinedAt,
|
JoinedAt = p.JoinedAt,
|
||||||
Profile = p.AccountId.HasValue && memberProfiles.TryGetValue(p.AccountId.Value, out var profile)
|
Profile = p.AccountId.HasValue && memberProfiles.TryGetValue(p.AccountId.Value, out var profile)
|
||||||
? profile
|
? profile
|
||||||
: null
|
: null
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
// Create the update packet with CallParticipant objects
|
// Create the update packet with CallParticipant objects
|
||||||
var updatePacket = new WebSocketPacket
|
var updatePacket = new WebSocketPacket
|
||||||
{
|
{
|
||||||
@ -368,7 +393,7 @@ public class LivekitRealtimeService : IRealtimeService
|
|||||||
{ "participants", participants }
|
{ "participants", participants }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send the update to all members
|
// Send the update to all members
|
||||||
foreach (var accountId in roomMembers)
|
foreach (var accountId in roomMembers)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAccessToken_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fb370f448e9f5fca62da785172d83a214319335e27ac4d51840349c6dce15d68_003FAccessToken_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003Fc5_003F2a1973a9_003FApnSender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003Fc5_003F2a1973a9_003FApnSender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003Ff0_003F595b6eda_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003Ff0_003F595b6eda_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user