Start
This commit is contained in:
214
frontend/src/app/components/call/call.component.ts
Normal file
214
frontend/src/app/components/call/call.component.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user