I am creating a one-on-one live streaming functionality.
- First I generate the link using Guid.
- Open the link in two different tabs and connection is being established in Initiator and Receiver both the tabs.
- There are two video tags one is for localVideo and another is for remoteVideo which should display other user's video.
- But I can only see the localVideo
Program.cs
file:
using VideoStreamingApp.Hubs;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSignalR(); // Adding SignalR
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
// **Map SignalR Hub**
app.MapHub<StreamingHub>("/streamingHub");
app.Run();
Controller:
public class AdminController : Controller
{
private static Dictionary<string, DateTime> _streamLinks = new Dictionary<string, DateTime>();
public IActionResult GenerateLink()
{
string streamId = Guid.NewGuid().ToString();
DateTime expiryTime = DateTime.UtcNow.AddMinutes(1); // Set expiry time to 1 minutes
// Store the link with expiration time
_streamLinks[streamId] = expiryTime;
string streamLink = $"{Request.Scheme}://{Request.Host}/Stream/Join?streamId={streamId}";
ViewBag.StreamLink = streamLink;
ViewBag.ExpiryTime = expiryTime.ToString("yyyy-MM-dd HH:mm:ss UTC");
return View("GenerateLink");
}
public static bool IsLinkValid(string streamId)
{
return _streamLinks.ContainsKey(streamId) && _streamLinks[streamId] > DateTime.UtcNow;
}
}
public class StreamController : Controller
{
public IActionResult Join(string streamId)
{
if (!AdminController.IsLinkValid(streamId))
{
return View("ExpiredLink"); // Show expiration message
}
ViewBag.StreamId = streamId;
return View();
}
}
Inside Hubs folder > StreamingHub
:
public class StreamingHub : Hub
{
private static ConcurrentDictionary<string, string> _initiators = new ConcurrentDictionary<string, string>();
public async Task JoinStream(string streamId)
{
bool isInitiator = !_initiators.ContainsKey(streamId);
if (isInitiator)
{
_initiators[streamId] = Context.ConnectionId;
}
await Groups.AddToGroupAsync(Context.ConnectionId, streamId);
await Clients.Caller.SendAsync("SetInitiator", isInitiator);
}
public async Task SendOffer(string streamId, string offer)
{
await Clients.OthersInGroup(streamId).SendAsync("ReceiveOffer", offer);
}
public async Task SendAnswer(string streamId, string answer)
{
await Clients.OthersInGroup(streamId).SendAsync("ReceiveAnswer", answer);
}
public async Task SendMuteStatus(string streamId, bool isMuted)
{
await Clients.OthersInGroup(streamId).SendAsync("ReceiveMuteStatus", Context.ConnectionId, isMuted);
}
public async Task SendVideoStatus(string streamId, bool isVideoOn)
{
await Clients.OthersInGroup(streamId).SendAsync("ReceiveVideoStatus", Context.ConnectionId, isVideoOn);
}
}
Join.cshtml
of StreamController
:
<h6>Live Video Stream</h6>
<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
<button id="muteBtn">Mute</button>
<button id="videoToggleBtn">Turn Off Video</button>
<script src=".11.1/simplepeer.min.js"></script>
<script src=".0.2/signalr.min.js"></script>
<script>
const hubConnection = new signalR.HubConnectionBuilder()
.withUrl("/streamingHub")
.configureLogging(signalR.LogLevel.Information)
.build();
let peer;
let isInitiator = false;
const streamId = "@ViewBag.StreamId";
hubConnection.start().then(async () => {
console.log(`✅ Connected to SignalR. with StreamId: ${streamId}`);
try {
console.log("