Files
Hospital-booking/frontend/src/app/components/call/call.component.ts
Iliyan Angelov c15a7bdbde Start
2025-11-16 02:44:17 +02:00

215 lines
7.3 KiB
TypeScript

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<HTMLVideoElement>;
@ViewChild('remoteVideo') remoteVideoRef!: ElementRef<HTMLVideoElement>;
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<void> {
// 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<void> {
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<void> {
await this.callService.answerCall();
this.isRinging = false;
this.isCallActive = true;
}
async rejectCall(): Promise<void> {
await this.callService.rejectCall();
this.isRinging = false;
this.currentCall = null;
}
async endCall(): Promise<void> {
// 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<void> {
this.isMuted = !await this.callService.toggleMute();
}
async toggleVideo(): Promise<void> {
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);
}
}