120 lines
4.0 KiB
TypeScript
120 lines
4.0 KiB
TypeScript
import { WebSocketClient, WebSocketClientStatusMapping } from './services/WebSocketClient.ts';
|
|
import { MediaHandler } from './services/MediaHandler.ts';
|
|
import { UIController } from './services/UIController.ts';
|
|
import { PublisherRTCManager } from './services/PublisherRTCManager.ts';
|
|
import type { ISignalingMessage } from './interfaces/IWebRTCClient.ts';
|
|
import { DisposableList } from '@techniker-me/tools';
|
|
|
|
class Publisher {
|
|
private readonly _disposables: DisposableList = new DisposableList();
|
|
private wsClient: WebSocketClient;
|
|
private mediaHandler: MediaHandler;
|
|
private uiController: UIController;
|
|
private rtcManager: PublisherRTCManager;
|
|
private isStreaming = false;
|
|
|
|
constructor() {
|
|
this.wsClient = new WebSocketClient('publisher');
|
|
this.mediaHandler = new MediaHandler('localVideo');
|
|
this.uiController = new UIController('status', 'subscribersCount', 'startBtn', 'stopBtn');
|
|
this.rtcManager = new PublisherRTCManager((count) => {
|
|
this.uiController.updateSubscribersCount(count);
|
|
});
|
|
|
|
this.setupEventHandlers();
|
|
this.setupWebSocketHandlers();
|
|
}
|
|
|
|
private setupEventHandlers(): void {
|
|
this.uiController.onButtonClick('startBtn', () => this.startBroadcasting());
|
|
this.uiController.onButtonClick('stopBtn', () => this.stopBroadcasting());
|
|
}
|
|
|
|
private setupWebSocketHandlers(): void {
|
|
this._disposables.add(this.wsClient.status.subscribe((status) => {
|
|
this.uiController.updateStatus(WebSocketClientStatusMapping.convertWebSocketClientStatusToWebSocketClientStatusType(status), status);
|
|
}));
|
|
|
|
this.wsClient.onMessage('join', (message) => {
|
|
this.uiController.updateStatus('Connected to server', 'connected');
|
|
});
|
|
|
|
this.wsClient.onMessage('answer', async (message) => {
|
|
if (message.senderId) {
|
|
await this.rtcManager.handleAnswer(message.senderId, message.data);
|
|
}
|
|
});
|
|
|
|
this.wsClient.onMessage('ice-candidate', async (message) => {
|
|
if (message.senderId && message.data) {
|
|
await this.rtcManager.handleIceCandidate(message.senderId, message.data);
|
|
}
|
|
});
|
|
|
|
this.rtcManager.setOnIceCandidate((subscriberId, candidate) => {
|
|
const message: ISignalingMessage = {
|
|
type: 'ice-candidate',
|
|
data: candidate,
|
|
targetId: subscriberId
|
|
};
|
|
this.wsClient.sendMessage(message);
|
|
});
|
|
}
|
|
|
|
private async startBroadcasting(): Promise<void> {
|
|
try {
|
|
this.uiController.updateStatus('Starting broadcast...', 'waiting');
|
|
this.uiController.setButtonStates(false, false);
|
|
|
|
await this.wsClient.connect();
|
|
const stream = await this.mediaHandler.getLocalStream();
|
|
this.rtcManager.setLocalStream(stream);
|
|
|
|
this.isStreaming = true;
|
|
this.uiController.updateStatus('Broadcasting - Ready for subscribers', 'connected');
|
|
this.uiController.setButtonStates(false, true);
|
|
|
|
this.startOfferLoop();
|
|
} catch (error) {
|
|
console.error('Error starting broadcast:', error);
|
|
this.uiController.updateStatus('Failed to start broadcast', 'disconnected');
|
|
this.uiController.setButtonStates(true, false);
|
|
}
|
|
}
|
|
|
|
private stopBroadcasting(): void {
|
|
this.isStreaming = false;
|
|
this.mediaHandler.stopLocalStream();
|
|
this.rtcManager.closeAllConnections();
|
|
this.wsClient.disconnect();
|
|
|
|
this.uiController.updateStatus('Broadcast stopped', 'disconnected');
|
|
this.uiController.setButtonStates(true, false);
|
|
this.uiController.updateSubscribersCount(0);
|
|
}
|
|
|
|
private async startOfferLoop(): Promise<void> {
|
|
const offerToNewSubscribers = async () => {
|
|
if (!this.isStreaming) return;
|
|
|
|
try {
|
|
const offer = await this.rtcManager.createOfferForSubscriber('broadcast');
|
|
const message: ISignalingMessage = {
|
|
type: 'offer',
|
|
data: offer
|
|
};
|
|
this.wsClient.sendMessage(message);
|
|
} catch (error) {
|
|
console.error('Error creating offer:', error);
|
|
}
|
|
|
|
if (this.isStreaming) {
|
|
setTimeout(offerToNewSubscribers, 2000);
|
|
}
|
|
};
|
|
|
|
setTimeout(offerToNewSubscribers, 1000);
|
|
}
|
|
}
|
|
|
|
new Publisher(); |