153 lines
5.1 KiB
TypeScript
153 lines
5.1 KiB
TypeScript
import { WebSocketClient, WebSocketClientStatusMapping } from './services/WebSocketClient.ts';
|
|
import { UIController } from './services/UIController.ts';
|
|
import { SubscriberRTCManager } from './services/SubscriberRTCManager.ts';
|
|
import type { ISignalingMessage } from './interfaces/IWebRTCClient.ts';
|
|
import { DisposableList } from '@techniker-me/tools';
|
|
|
|
class Subscriber {
|
|
private readonly _disposables: DisposableList = new DisposableList();
|
|
private wsClient: WebSocketClient;
|
|
private uiController: UIController;
|
|
private rtcManager: SubscriberRTCManager;
|
|
private isConnected = false;
|
|
private videoPlaceholder: HTMLElement | null;
|
|
private remoteVideo: HTMLVideoElement | null;
|
|
|
|
constructor() {
|
|
this.wsClient = new WebSocketClient('subscriber');
|
|
this.uiController = new UIController('status', undefined, 'connectBtn', 'disconnectBtn');
|
|
this.videoPlaceholder = document.getElementById('videoPlaceholder');
|
|
this.remoteVideo = document.getElementById('remoteVideo') as HTMLVideoElement;
|
|
|
|
this.rtcManager = new SubscriberRTCManager(
|
|
'remoteVideo',
|
|
(stream) => this.onStreamReceived(stream),
|
|
(state) => this.onConnectionStateChange(state)
|
|
);
|
|
|
|
this.setupEventHandlers();
|
|
this.setupWebSocketHandlers();
|
|
}
|
|
|
|
private setupEventHandlers(): void {
|
|
this.uiController.onButtonClick('connectBtn', () => this.connect());
|
|
this.uiController.onButtonClick('disconnectBtn', () => this.disconnect());
|
|
}
|
|
|
|
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 - Waiting for publisher', 'waiting');
|
|
this.isConnected = true;
|
|
this.uiController.setButtonStates(false, true);
|
|
});
|
|
|
|
this.wsClient.onMessage('publisher-joined', (message) => {
|
|
this.uiController.updateStatus('Publisher available - Requesting stream', 'waiting');
|
|
});
|
|
|
|
this.wsClient.onMessage('publisher-left', (message) => {
|
|
this.uiController.updateStatus('Publisher disconnected', 'disconnected');
|
|
this.showVideoPlaceholder();
|
|
});
|
|
|
|
this.wsClient.onMessage('offer', async (message) => {
|
|
try {
|
|
const answer = await this.rtcManager.handleOffer(message.data);
|
|
const answerMessage: ISignalingMessage = {
|
|
type: 'answer',
|
|
data: answer,
|
|
targetId: message.senderId
|
|
};
|
|
this.wsClient.sendMessage(answerMessage);
|
|
|
|
this.uiController.updateStatus('Connecting to stream...', 'waiting');
|
|
} catch (error) {
|
|
console.error('Error handling offer:', error);
|
|
this.uiController.updateStatus('Failed to connect to stream', 'disconnected');
|
|
}
|
|
});
|
|
|
|
this.wsClient.onMessage('ice-candidate', async (message) => {
|
|
if (message.data) {
|
|
await this.rtcManager.handleIceCandidate(message.data);
|
|
}
|
|
});
|
|
|
|
this.rtcManager.setOnIceCandidate((candidate) => {
|
|
const message: ISignalingMessage = {
|
|
type: 'ice-candidate',
|
|
data: candidate
|
|
};
|
|
this.wsClient.sendMessage(message);
|
|
});
|
|
}
|
|
|
|
private async connect(): Promise<void> {
|
|
try {
|
|
this.uiController.updateStatus('Connecting to server...', 'waiting');
|
|
this.uiController.setButtonStates(false, false);
|
|
|
|
await this.wsClient.connect();
|
|
} catch (error) {
|
|
console.error('Error connecting:', error);
|
|
this.uiController.updateStatus('Failed to connect to server', 'disconnected');
|
|
this.uiController.setButtonStates(true, false);
|
|
}
|
|
}
|
|
|
|
private disconnect(): void {
|
|
this.isConnected = false;
|
|
this.rtcManager.disconnect();
|
|
this.wsClient.disconnect();
|
|
|
|
this.uiController.updateStatus('Disconnected', 'disconnected');
|
|
this.uiController.setButtonStates(true, false);
|
|
this.showVideoPlaceholder();
|
|
}
|
|
|
|
private onStreamReceived(stream: MediaStream): void {
|
|
console.log('Stream received, showing video');
|
|
this.uiController.updateStatus('Connected - Receiving stream', 'connected');
|
|
this.hideVideoPlaceholder();
|
|
}
|
|
|
|
private onConnectionStateChange(state: string): void {
|
|
switch (state) {
|
|
case 'connected':
|
|
this.uiController.updateStatus('Connected - Receiving stream', 'connected');
|
|
break;
|
|
case 'connecting':
|
|
this.uiController.updateStatus('Connecting to stream...', 'waiting');
|
|
break;
|
|
case 'disconnected':
|
|
case 'failed':
|
|
this.uiController.updateStatus('Connection lost', 'disconnected');
|
|
this.showVideoPlaceholder();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private hideVideoPlaceholder(): void {
|
|
if (this.videoPlaceholder) {
|
|
this.videoPlaceholder.style.display = 'none';
|
|
}
|
|
if (this.remoteVideo) {
|
|
this.remoteVideo.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
private showVideoPlaceholder(): void {
|
|
if (this.videoPlaceholder) {
|
|
this.videoPlaceholder.style.display = 'flex';
|
|
}
|
|
if (this.remoteVideo) {
|
|
this.remoteVideo.style.display = 'none';
|
|
}
|
|
}
|
|
}
|
|
|
|
new Subscriber(); |