I am using Angular 8 and singalR for image streaming from the server.
I noticed that streaming slow down my UI rendering. I want whole streaming part to move to Web Worker and only valuable content send from Web Worker to UI tread.
I do not have code example since I am not sure if my idea even possible.
Can anyone give me some tips, or post, since I do not find anything about web workers and web socket (signalR) connection in them.
After some researching I finally finished this. This is possible, and there is couple tips for it:
https://angular.io/guide/web-worker Create web worker, ng generate webWorker , is command for that and Angular will set up enviroment for you. If you do that without this command, there is additional work to do. Please check documentation for it.
Install @microsoft/signalr library, not @aspnet/signalr. I had problem to establish connection in web worker with @aspnet/signalr.
Then from my component I called worker to establish connection:
public establishConnectionToStreamingHub() {
if (!this.streamingWorker) {
this.streamingWorker = new Worker('./streaming-data.worker', { type: 'module' });
this.streamingWorker.postMessage(<StreamingWorkerRequest>{
action: StreamingWorkerAction.EstablishConnection,
baseHref: this.href
});
this.streamingWorker.onmessage = this.onWorkerMessage;
}
}
// listen for streaming response from worker.
// I created interface here for message type.
// If message is success I notified listener about it, if message is error handling should be added here.
private onWorkerMessage = (message) => {
const response = message.data as StreamingWorkerResponse;
if (response.type === ResponseMessageType.StreamingSuccessResult) {
this.streamingDataNotify(response.data);
}
}
This is my streaming worker file. The main point here is that I have one class for connection and streaming. First time I get message to the worker I check message type if message is 'StreamingWorkerAction.EstablishConnection'. If it is I create new class instance and establish connection to the hub in class constructor. I send worker instace to the class also. After it for all streaming request class object is in charge of.
import { StreamingWorkerAction,
StreamingWorkerRequest,
StreamingWorkerResponse,
ResponseMessageType
} from './streaming-worker.model';
export class StreamingClass {
private hubConnection: HubConnection;
private workerInstance;
private href = '';
private streamingSubscription: ISubscription<Data>;
constructor(data: StreamingWorkerRequest) {
this.href = data.baseHref;
this.workerInstance = data.workerInstance;
this.connectHub().subscribe(result => {
console.log('Streaming connection established.')
}, error => {
this.workerInstance.postMessage(<StreamingWorkerResponse>{
type: ResponseMessageType.ConnectingError,
error: error
});
});
this.workerInstance.onmessage = (event) => {
const requestData = event.data as StreamingWorkerRequest;
if (requestData.action === StreamingWorkerAction.StartStreaming) {
this.clearCurrentSubscription();
this.streamingSubscription = this.startStreaming(streamingId);
}
};
}
private clearCurrentSubscription() {
if (this.streamingSubscription) {
this.streamingSubscription.dispose();
}
}
private startStreaming(streamingId: string): ISubscription<StreamingData> {
return this.hubConnection.stream<StreamingData>('Stream', streamingId).subscribe({
next: data => {
this.workerInstance.postMessage(<StreamingWorkerResponse>{
type: ResponseMessageType.StreamingSuccessResult,
data: data
});
},
complete: () => {
console.log('complete');
},
error: error => {
this.workerInstance.postMessage(<StreamingWorkerResponse>{
type: ResponseMessageType.StreamingError,
error: error
});
}
});
}
private connectHub(): Observable<boolean> {
this.hubConnection = new HubConnectionBuilder()
.withUrl(this.href + '/api/hub/pathToTheHub')
.withAutomaticReconnect()
.build();
}
}
// web worker event listener
addEventListener('message', event => {
const requestData = event.data as StreamingWorkerRequest;
if (requestData.action === StreamingWorkerAction.EstablishConnection) {
requestData.workerInstance = self;
const streamingClass = new StreamingClass(requestData);
}
});
Update: 02-09-2020
On npm site for @microsoft/signalr library: https://www.npmjs.com/package/@microsoft/signalr
I noticed this:
To use the client in a webworker, copy *.js files from the dist/webworker > >folder to your script folder include on your webworker using the importScripts >function. Note that webworker SignalR hub connection supports only absolute path >to a SignalR hub.
After it i found webworker folder in my node modules: \node_modules@microsoft\signalr\dist\webworker
and copied file signalr.js to assets folder on path: assets/scripts/signalr.js.
Then in my worker file I imported script like this:
import * as signalR from '../../../../assets/scripts/signalr.js';
And in worker file replaced all old signalR import to new one from webworker script. For example those lines:
private hubConnection: HubConnection;
private streamingSubscription: ISubscription<MetaModule>;
this.hubConnection = new HubConnectionBuilder()
.withUrl(this.href + '/api/hub/pathToTheHub')
.withAutomaticReconnect()
.build();
are changed to:
private hubConnection: signalR.HubConnection;
private streamingSubscription: signalR.ISubscription<MetaModule>;
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(this.href + '/api/hub/pathToTheHub')
.withAutomaticReconnect()
.build();
After those updates our streaming process works significantly faster.