screencastR - Simple screen sharing app using SignalR streaming
Posted onEdited onDisqus:
Introduction
In this article, we will see how to create simple screen sharing app using signalR streaming. SignalR supports both server to client and client to server streaming. In my previous article , I have done server to client streaming with ChannelReader and ChannelWriter for streaming support. This may look very complex to implement asynchronous streaming just like writing the asynchronous method without async and await keyword. IAsyncEnumerable is the latest addition to .Net Core 3.0 and C# 8 feature for asynchronous streaming. It is now super easy to implement asynchronous streaming with few lines of clean code. In this example, we will use client to server streaming to stream the desktop content to all the connected remote client viewers using signalR stream with the support of IAsyncEnumerable API.
Disclaimer
The sample code for this article is just an experimental project for testing signalR streaming with IAsyncEnumerable. In Real world scenarios, You may consider using peer to peer connection using WebRTC or other socket libraries for building effective screen sharing tool.
Architecture
How it Works
ScreencastR Agent
Steps
ScreencastR agent is a Electron based desktop application. Electron is a framework for creating native applications with web technologies like JavaScript, HTML, and CSS. It allows you to create desktop applications with pure JavaScript by providing a runtime with rich native (operating system) APIs. In our example, I have used desktopCapturer API to capture the desktop content. if you are new to electron, you can follow this official docs to create your first electron application.
A simple electron application will have following files which is similar to nodejs application.
The starting point is the package.json which will have entry point javascript (main.js) and main.js will create a basic electron shell with default menu option and load the main html page. (index.html). In this package.json, i have added the dependency of latest SignalR client.
When we run the npm build, which will bring all the dependencies under node_modules folder including signalR client. Copy the signalr.js file from node_modules\@microsoft\signalr\dist\browserfolder into the root folder.
In the renderer.js javascript, initializeSignalR method would initialize the signalR connection when the application gets loaded and listens to NewViewer and NoViewer hub methods. The NewViewer method gets called whenever the new remote viewer joining to view the stream. The agent will not stream the content until atleast one viewer exists. When NoViewer method called, it will stop the stream.
CaptureScreen method will use the desktopCapturer API to get the list of available screen and window sources and filter to get the “Entire screen“ source only. After the source is identified, screen thumbnail data can be generated from source based on the thumbnail size is defined. CaptureScreen method is based on the promise API and will returns the image data in string as part of the resolve method. We will call the CaptureScreen method in timer (setInterval method) based on the frame per second defined and the output will be streamed via signalR subject class.
ScreenCastR Remote Viewer is a server side blazor app with signalR hub hosted in it. This app also has the interface for signalR client to receive the stream data from hub. Whenever the New Agent joined, it will show the details of agent in the dashboard page with the name of agent and View and Stop Cast button. When the user clicks on the View Cast button, it will start receiving the streaming from hub and render the output on the screen. In the above video, the left side is the agent streaming data to signalR hub and the right side is the viewer rendering the streaming data from the signalR hub.
ScreenCastHub class is the streaming hub with all methods to communicate between agent and remote viewer.
StreamCastData is the main streaming method which will take IAsyncEnumerable Items and stream the chunk of data that it receives to all the connected remote viewers.
AddScreenCastAgent method will send the notification to all the connected remote viewer whenever the new agent join the hub.
RemoveScreenCastAgent method will send the notification to all the connected remote viewer whenever the agent disconnects from the hub.
AddScreenCastViewer method will send the notification to agent if the new viewer joined to view the screen cast.
RemoveScreenCastViewer method will send the notification to agent if the all the viewer disconnected from viewing the screen cast.
ScreenCastManager
publicclassScreenCastManager { private List<Viewer> viewers = new List<Viewer>();
This class will holds number of viewers connected per agent. This class is injected to hub via dependency injection in singleton scope.
services.AddSingleton<ScreenCastManager>();
Startup.cs
In startup.cs, increase the default message size from 32KB to bigger range based on the quality of stream output. Otherwise hub will fail to transmit the data.
In this component, as part of OnInitializedAsync method, initialize the signalR client connection with hub and subscribe to streaming method. When the stream data is arrived from hub, it update the image source DOM element and render the screen with the changes.
Demo
Summary
IAsyncEnumerable is a very nice feature added to .Net Core 3.0 and C# 8 for asynchronous streaming with cleaner and readable code. With this new feature and SignalR streaming, we can do many cool projects like Real Time App health monitor dashboard, Real Time multiplayer games etc… I have uploaded the entire source code for this article in the github repository.