Reputation: 334
I'm using Blazor WebAssembly with .NET 5 and I have a MultipleCameraLive component which embeds 4 SingleCameraLive components. Each component represents the streaming of frames coming from a given camera. By clicking on the frame belonging to a given camera, the user will jump to a page (let's call it SingleCameraFocus) where only the streaming from that camera is showed.
Images are sent by the server by means of SignalR as soon as they are acquired from the cameras (server-initiated communication).
Now, for modularity reasons I think the best approach would be to have a single Hub class and have like "multiple instances" of it. In other words in the server I'd have something like this
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
[...]
app.UseEndpoints(endpoints => {
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
endpoints.MapHub<CameraLiveModeHub>("/cameraLiveHub_0");
endpoints.MapHub<CameraLiveModeHub>("/cameraLiveHub_1");
endpoints.MapHub<CameraLiveModeHub>("/cameraLiveHub_2");
endpoints.MapHub<CameraLiveModeHub>("/cameraLiveHub_3");
});
CameraLiveModeHub.Context = app.ApplicationServices.GetService<IHubContext<CameraLiveModeHub>>();
[...]
Client side there are no problems since I can use the Uri above to connect to the given Hub. However when I send a message from server by means of Hub Context I have no such possibility. In fact, from what I understand it's not like there are 4 Hub: there is just one, with four Uri "pointing" to that Hub. In fact here you can read
Hubs are transient: Don't store state in a property on the hub class. Every hub method call is executed on a new hub instance.
This seems to suggest that there is no way of accomplishing what I'd like to. If I were able to accomplish the above-mentioned modular design I would have had advantages such as: in order to design the SingleCameraFocus component, I'd just need to re-use SingleCameraLive component, passing-in the correct parameters (such as the Hub Uri).
So the other way would be to have a single Hub for MultipleCameraLive component, and everytime it receives a frame from the server it needs to act as "multiplexer", passing the frame to the correct child component. For what concerns the SingleCameraFocus component, I would need a separate Hub for it. Furthermore, since there are 4 cameras, I'd probably need 4 Hubs, 4 different Classes having almost exactly the same code. This is because a client should receive the frames only from a single Camera, i.e. from a single Hub, since a Hub have no means of selecting which client to send data to (if communication is server-initiated like in this case).
What is in your opinion the best way to approach this problem?
Upvotes: 1
Views: 3235
Reputation: 1229
Here is a sample:
public class MyHub : Hub
{
private new static readonly ConcurrentDictionary<string, IList<string>> Groups = new ConcurrentDictionary<string, IList<string>>(
new Dictionary<string, IList<string>>()
{
{ "group_1", new List<string>() },
{ "group_2", new List<string>() },
{ "group_3", new List<string>() },
{ "group_4", new List<string>() }
}
);
public bool Join(string groupName)
{
var connId = Context.ConnectionId;
// Check if I already belong to this group
if(Groups.TryGetValue(groupName, out var groupClients))
{
if (groupClients.Contains(connId) == false)
groupClients.Add(connId);
return true;
}
return false;
}
public async Task Send(string groupName, string message)
{
var connId = Context.ConnectionId;
// Check if I belong to this group
if(Groups.TryGetValue(groupName, out var groupClients))
{
if(groupClients.Contains(Context.ConnectionId))
{
await Clients.Users((IReadOnlyList<string>)groupClients.Where(c => c != connId)).SendAsync(message);
}
}
}
public void Disconnect()
{
var myGroups = GetMyGroups();
foreach(var groupName in myGroups)
if (Groups.TryGetValue(groupName, out var groupClients))
groupClients.Remove(Context.ConnectionId);
}
// In case you can belong to many groups at the same time
private IList<string> GetMyGroups()
{
var connId = Context.ConnectionId;
var output = new List<string>();
foreach(var item in Groups)
{
if (item.Value.Contains(connId))
output.Add(item.Key);
}
return output;
}
}
Upvotes: 2