import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; import { CommonModule } from '@angular/common'; import { CallService, CallInfo } from '../../services/call.service'; import { UserService } from '../../services/user.service'; import { LoggerService } from '../../services/logger.service'; import { Subscription } from 'rxjs'; @Component({ selector: 'app-call', standalone: true, imports: [CommonModule], templateUrl: './call.component.html', styleUrl: './call.component.scss' }) export class CallComponent implements OnInit, OnDestroy { @ViewChild('localVideo') localVideoRef!: ElementRef; @ViewChild('remoteVideo') remoteVideoRef!: ElementRef; currentCall: CallInfo | null = null; isRinging: boolean = false; isCallActive: boolean = false; isMuted: boolean = false; isVideoEnabled: boolean = true; isIncoming: boolean = false; private subscriptions: Subscription[] = []; private currentUser: any = null; // Cache current user to avoid repeated API calls constructor( public callService: CallService, public userService: UserService, private logger: LoggerService ) {} async ngOnInit(): Promise { // Cache current user on init to avoid repeated calls this.currentUser = await this.userService.getCurrentUser(); const callSub = this.callService.call$.subscribe(callInfo => { this.handleCallUpdate(callInfo); }); const signalSub = this.callService.signal$.subscribe(signal => { if (signal.signalData?.remoteStreamReady && this.remoteVideoRef) { const remoteStream = this.callService.getRemoteStream(); if (remoteStream && this.remoteVideoRef.nativeElement) { this.remoteVideoRef.nativeElement.srcObject = remoteStream; } } }); this.subscriptions.push(callSub, signalSub); // Update video streams periodically const interval = setInterval(() => { this.updateVideoStreams(); }, 1000); this.subscriptions.push(new Subscription(() => clearInterval(interval))); } ngOnDestroy(): void { this.subscriptions.forEach(sub => sub.unsubscribe()); } private async handleCallUpdate(callInfo: CallInfo | null): Promise { this.logger.debug('Call component received call update:', callInfo); // Handle null callInfo (when call is cleared) if (!callInfo) { this.logger.debug('Call info is null, clearing call state'); this.currentCall = null; this.isRinging = false; this.isCallActive = false; if (this.localVideoRef?.nativeElement) { this.localVideoRef.nativeElement.srcObject = null; } if (this.remoteVideoRef?.nativeElement) { this.remoteVideoRef.nativeElement.srcObject = null; } return; } this.currentCall = callInfo; // Ensure we have current user (use cache if available, fetch if not) if (!this.currentUser) { this.currentUser = await this.userService.getCurrentUser(); } // Determine if this is an incoming call (we are the receiver) or outgoing call (we are the sender) if (this.currentUser) { const isSender = callInfo.senderId === this.currentUser.id; const isReceiver = callInfo.receiverId === this.currentUser.id; // It's incoming if we are the receiver (not the sender) and the call is ringing // It's outgoing if we are the sender (not the receiver) and the call is ringing this.isIncoming = isReceiver && !isSender && callInfo.callStatus === 'ringing'; this.logger.debug('Current user ID:', this.currentUser.id, 'Sender ID:', callInfo.senderId, 'Receiver ID:', callInfo.receiverId, 'Is sender:', isSender, 'Is receiver:', isReceiver, 'Is incoming:', this.isIncoming); } if (callInfo.callStatus === 'ringing') { this.logger.debug('Call is ringing - showing UI'); this.isRinging = true; this.isCallActive = false; // Update UI immediately this.updateVideoStreams(); } else if (callInfo.callStatus === 'accepted') { this.logger.debug('Call accepted - starting active call'); this.isRinging = false; this.isCallActive = true; setTimeout(() => this.updateVideoStreams(), 500); } else { this.isRinging = false; this.isCallActive = false; if (callInfo.callStatus === 'ended' || callInfo.callStatus === 'rejected' || callInfo.callStatus === 'cancelled') { this.logger.debug('Call terminated:', callInfo.callStatus); this.currentCall = null; } } } private updateVideoStreams(): void { if (this.localVideoRef?.nativeElement) { const localStream = this.callService.getLocalStream(); if (localStream) { this.localVideoRef.nativeElement.srcObject = localStream; } } if (this.remoteVideoRef?.nativeElement && this.isCallActive) { const remoteStream = this.callService.getRemoteStream(); if (remoteStream) { this.remoteVideoRef.nativeElement.srcObject = remoteStream; } } } async answerCall(): Promise { await this.callService.answerCall(); this.isRinging = false; this.isCallActive = true; } async rejectCall(): Promise { await this.callService.rejectCall(); this.isRinging = false; this.currentCall = null; } async endCall(): Promise { // If call is still ringing and we're the caller, cancel the call if (this.isRinging && !this.isIncoming && this.currentCall) { await this.callService.cancelCall(); } else { await this.callService.endCall(); } this.isCallActive = false; this.currentCall = null; this.isRinging = false; if (this.localVideoRef?.nativeElement) { this.localVideoRef.nativeElement.srcObject = null; } if (this.remoteVideoRef?.nativeElement) { this.remoteVideoRef.nativeElement.srcObject = null; } } async toggleMute(): Promise { this.isMuted = !await this.callService.toggleMute(); } async toggleVideo(): Promise { this.isVideoEnabled = !await this.callService.toggleVideo(); } getOtherUserName(): string { if (!this.currentCall) return ''; // Use cached currentUser if available, otherwise return based on call info // If we're the sender (incoming=false), show receiver name // If we're the receiver (incoming=true), show sender name if (this.isIncoming && this.currentCall.senderName) { return this.currentCall.senderName; } else if (!this.isIncoming && this.currentCall.receiverName) { return this.currentCall.receiverName; } return this.currentCall.senderName || this.currentCall.receiverName || 'Unknown'; } getOtherUserAvatarUrl(): string | undefined { if (!this.currentCall) return undefined; // Return sender's avatar if we're the receiver (incoming call) // Return receiver's avatar if we're the sender (outgoing call) if (this.isIncoming) { return this.currentCall.senderAvatarUrl; } // For outgoing calls, return the receiver's avatar return this.currentCall.receiverAvatarUrl; } isVideoCall(): boolean { return this.currentCall?.callType === 'video'; } shouldShowCall(): boolean { return this.currentCall !== null && (this.isRinging || this.isCallActive); } }