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 { 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();