Google Meet Clone in Angular 18

import { Component, OnInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef, Inject, PLATFORM_ID, inject, ApplicationRef } from '@angular/core';
import { CommonModule, isPlatformBrowser } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { Socket, io } from 'socket.io-client';
import { WhiteboardComponent } from '../whiteboard/whiteboard.component';
import { RoomCreationComponent } from '../room-creation/room-creation.component';
import { first } from 'rxjs';
import { environment } from '../../../environments/environment';
import { AuthService } from '../../auth/Auth.Service';

interface Participant {
  id: any;
  stream: MediaStream;
  cameraStream?: MediaStream;
  name: string;
  isWhiteboardActive: boolean;
  isScreenSharing: boolean;
  hasScreenSharePermission: boolean;
  hasWhiteboardPermission: boolean;
  isAudioMuted: boolean;
  isVideoOff: boolean;
  showCameraWithScreenShare: boolean;
}
interface Message {
  id: string;
  sender: string;
  content: string;
  time: Date;
  isPrivate: boolean;
  recipient?: string;
}

@Component({
  selector: 'app-video-conference-page',
  standalone: true,
  imports: [CommonModule, FormsModule, WhiteboardComponent,RoomCreationComponent],
  templateUrl: './video-conference-page.component.html',
  styleUrl: './video-conference-page.component.scss'
})

export class VideoConferenceComponent implements OnInit, OnDestroy {
  @ViewChild('localVideo') localVideo!: ElementRef<HTMLVideoElement>;
  @ViewChild('chatMessages') chatMessages!: ElementRef;

  public socket: Socket;
  private peerConnections: Map<string, RTCPeerConnection> = new Map();
  localStream: MediaStream | null = null;
  screenStream: MediaStream | null = null;
  participants: Participant[] = [];
  messages: Message[] = [];
  newMessage = '';
  roomId: string = '';
  userName: string = '';
  meetingLink: string = '';
  isConnected: boolean = false;
  isHost: boolean = false;
  isScreenSharing: boolean = false;
  localWhiteboardActive: boolean = false;
  isRecording: boolean = false;
  isAudioMuted: boolean = false;
  isVideoOff: boolean = false;
  showChat: boolean = false;
  showParticipants: boolean = false;
  showMeetingDetails: boolean = false;
  isGridView: boolean = true;
  isFullscreen: boolean = false;
  focusedParticipant: Participant | null = null;
  hasScreenSharePermission: boolean = false;
  hasWhiteboardPermission: boolean = false;
  activeView: 'camera' | 'screen' | 'whiteboard' = 'camera';

  private mediaRecorder: MediaRecorder | null = null;
  private recordedChunks: Blob[] = [];
  chatRecipient: string = 'everyone';
  constructor(
    private http: HttpClient,
    private route: ActivatedRoute,
    private router: Router,
    private cdr: ChangeDetectorRef,
    private authService: AuthService,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {
    // this.socket = io('http://localhost:3000',  { autoConnect: false });
    this.socket = io(environment.apiUrl, { autoConnect: false });
    inject(ApplicationRef).isStable.pipe(
      first((isStable) => isStable))
    .subscribe(() => { this.socket.connect() });
  }
 

  ngOnInit() {
    if (isPlatformBrowser(this.platformId)) {
      this.initializeBrowserFeatures();
    }
  }
  private initializeBrowserFeatures() {
    this.socket = io(environment.apiUrl, { transports: ['websocket'] });
    this.setupSocketListeners();
   
    // Check if we're on the /meeting route or /join/:roomId route
    this.route.url.subscribe(segments => {
      if (segments[0]?.path === 'meeting') {
        // This is the host route
        this.isHost = true;
        this.generateRoomId();
        this.promptForUserName(true);
      } else if (segments[0]?.path === 'join') {
        // This is the user join route
        this.route.params.subscribe(params => {
          if (params['roomId']) {
            this.roomId = params['roomId'];
            this.userName = localStorage.getItem('userName') || '';
            if (this.userName) {
              this.joinRoom(false);
            } else {
              this.promptForUserName(false);
            }
          }
        });
      }
    });
  }
  // private initializeBrowserFeatures() {
  //   this.socket = io(environment.apiUrl, { transports: ['websocket'] });
  //   this.setupSocketListeners();
   
  //   this.route.params.subscribe(params => {
  //     if (params['roomId']) {
  //       this.roomId = params['roomId'];
  //       this.checkAuthAndJoinRoom();
  //     }
  //   });
  // }

  // private checkAuthAndJoinRoom() {
  //   this.authService.isLoggedIn().subscribe(
  //     isLoggedIn => {
  //       if (isLoggedIn) {
  //         // Implement logic to get userName or redirect to RoomCreationComponent
  //       } else {
  //         this.authService.redirectToLogin();
  //       }
  //     }
  //   );
  // }

  // ... (keep other methods, remove promptForUserName)

  // onRoomJoined(event: { roomId: string, userName: string }) {
  //   this.roomId = event.roomId;
  //   this.userName = event.userName;
  //   this.joinRoom(false);
  // }
  ngOnDestroy() {
    this.leaveRoom();
    if (this.socket) {
      this.socket.disconnect();
    }
  }

  private setupSocketListeners() {
    this.socket.on('user-connected', this.handleUserConnected.bind(this));
    this.socket.on('user-disconnected', this.handleUserDisconnected.bind(this));
    this.socket.on('all-users', this.handleAllUsers.bind(this));
    this.socket.on('receive-message', this.handleReceiveMessage.bind(this));
    this.socket.on('offer', this.handleOffer.bind(this));
    this.socket.on('answer', this.handleAnswer.bind(this));
    this.socket.on('ice-candidate', this.handleIceCandidate.bind(this));
    this.socket.on('screen-share-started', this.handleScreenShareStarted.bind(this));
    this.socket.on('screen-share-stopped', this.handleScreenShareStopped.bind(this));
    this.socket.on('whiteboard-toggle', this.handleWhiteboardToggle.bind(this));
    this.socket.on('permission-updated', this.handlePermissionUpdated.bind(this));
    this.socket.on('host-changed', this.handleHostChanged.bind(this));
    this.socket.on('private-message', this.handlePrivateMessage.bind(this));
    this.socket.on('camera-toggle', this.handleCameraToggle.bind(this));
  }

  private handleUserConnected({ userId, userName, isHost }: { userId: string; userName: string; isHost: boolean }) {
    console.log('User connected:', userId, userName, isHost);
    this.connectToNewUser(userId, userName, isHost);
    this.cdr.detectChanges();
  }

  private handleUserDisconnected(userId: string) {
    console.log('User disconnected:', userId);
    this.removeParticipant(userId);
    this.cdr.detectChanges();
  }

  private handleAllUsers(users: { id: string; name: string; isHost: boolean }[]) {
    users.forEach(user => this.connectToNewUser(user.id, user.name, user.isHost));
    this.cdr.detectChanges();
  }

  private handleReceiveMessage(message: Message) {
    if (!this.messages.some(m => m.id === message.id)) {
      this.messages.push(message);
      this.cdr.detectChanges();
      this.scrollToBottom();
    }
  }

  private async handleOffer({ from, offer }: { from: string; offer: RTCSessionDescriptionInit }) {
    const pc = this.createPeerConnection(from);
    await pc.setRemoteDescription(new RTCSessionDescription(offer));
    const answer = await pc.createAnswer();
    await pc.setLocalDescription(answer);
    this.socket.emit('answer', { to: from, answer });
  }

  private async handleAnswer({ from, answer }: { from: string; answer: RTCSessionDescriptionInit }) {
    const pc = this.peerConnections.get(from);
    if (pc) {
      await pc.setRemoteDescription(new RTCSessionDescription(answer));
    }
  }
  private async handleIceCandidate({ from, candidate }: { from: string; candidate: RTCIceCandidateInit }) {
    const pc = this.peerConnections.get(from);
    if (pc) {
      await pc.addIceCandidate(new RTCIceCandidate(candidate));
    }
  }

  private handleScreenShareStarted({ userId }: { userId: string }) {
    const participant = this.participants.find(p => p.id === userId);
    if (participant) {
      participant.isScreenSharing = true;
      this.focusedParticipant = participant;
      this.isGridView = false;
      this.cdr.detectChanges();
    }
  }
 
  private handleScreenShareStopped({ userId }: { userId: string }) {
    const participant = this.participants.find(p => p.id === userId);
    if (participant) {
      participant.isScreenSharing = false;
      if (this.focusedParticipant && this.focusedParticipant.id === userId) {
        this.focusedParticipant = null;
        this.isGridView = true;
      }
      this.cdr.detectChanges();
    }
  }


  private handleWhiteboardToggle({ userId, isActive }: { userId: string; isActive: boolean }) {
    const participant = this.participants.find(p => p.id === userId);
    if (participant) {
      participant.isWhiteboardActive = isActive;
      this.cdr.detectChanges();
    }
  }

  private handlePermissionUpdated({ userId, permission, value }: { userId: string; permission: 'screenShare' | 'whiteboard'; value: boolean }) {
    const participant = this.participants.find(p => p.id === userId);
    if (participant) {
      if (permission === 'screenShare') {
        participant.hasScreenSharePermission = value;
      } else if (permission === 'whiteboard') {
        participant.hasWhiteboardPermission = value;
      }
      this.cdr.detectChanges();
    }
  }

  private handleHostChanged({ newHostId }: { newHostId: string }) {
    this.isHost = newHostId === this.socket.id;
    this.participants.forEach(participant => {
      participant.hasScreenSharePermission = true; // Allow screen sharing for all participants
      participant.hasWhiteboardPermission = participant.id === newHostId;
    });
    this.cdr.detectChanges();
  }
   generateRoomId() {
    this.roomId = Math.random().toString(36).substring(2, 15);
    this.generateMeetingLink();
  }

  generateMeetingLink() {
    this.meetingLink = `${window.location.origin}/join/${this.roomId}`;
  }

  copyMeetingLink() {
    navigator.clipboard.writeText(this.meetingLink).then(() => {
      alert('Meeting link copied to clipboard!');
    });
  }

   promptForUserName(isHost: boolean) {
    this.userName = prompt('Enter your name to join the meeting:') || '';
    if (this.userName) {
      localStorage.setItem('userName', this.userName);
      if (isHost) {
        this.joinRoom(true);
      } else {
        this.joinRoom(false);
      }
    } else {
      this.router.navigate(['/']);
    }
  }

  async joinRoom(isHost: boolean) {
    if (!this.roomId || !this.userName || !this.socket) {
      alert('Please enter both room ID and your name.');
      return;
    }

    try {
      this.localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
      this.updateLocalVideoStream();
      this.socket.emit('join-room', { roomId: this.roomId, userName: this.userName, isHost: isHost });
      this.isConnected = true;
      this.isHost = isHost;
      this.hasScreenSharePermission = true;
      this.hasWhiteboardPermission = isHost;
      this.generateMeetingLink();
      this.cdr.detectChanges();
    } catch (error) {
      console.error('Error joining room:', error);
      this.handleJoinRoomError(error);
    }
  }

  private updateLocalVideoStream() {
    requestAnimationFrame(() => {
      if (this.localVideo && this.localVideo.nativeElement) {
        this.localVideo.nativeElement.srcObject = this.localStream;
        this.cdr.detectChanges();
      }
    });
  }

  private handleJoinRoomError(error: any) {
    if (error instanceof DOMException) {
      switch (error.name) {
        case 'NotAllowedError':
          alert('Permission to access camera and microphone was denied.');
          break;
        case 'NotFoundError':
          alert('No camera or microphone found.');
          break;
        case 'NotReadableError':
          alert('Your camera or microphone is already in use by another application.');
          break;
        default:
          alert(`An error occurred: ${error.message}`);
      }
    } else {
      alert('An unexpected error occurred. Please try again.');
    }

    const joinWithChatOnly = confirm('Would you like to join the room with chat only?');
    if (joinWithChatOnly) {
      this.socket.emit('join-room', { roomId: this.roomId, userName: this.userName, chatOnly: true });
      this.isConnected = true;
      this.cdr.detectChanges();
    }
  }

  leaveRoom() {
    this.stopScreenSharing();
    this.stopRecording();
    if (this.localStream) {
      this.localStream.getTracks().forEach(track => track.stop());
    }
    this.peerConnections.forEach(pc => pc.close());
    this.peerConnections.clear();
    this.socket.emit('leave-room', { roomId: this.roomId, userName: this.userName });
    this.reset();
  }

  private reset() {
    this.localStream = null;
    this.participants = [];
    this.isConnected = false;
    this.roomId = '';
    this.messages = [];
    this.cdr.detectChanges();
  }

  async connectToNewUser(userId: string, userName: string, isHost: boolean) {
    const pc = this.createPeerConnection(userId);
    const offer = await pc.createOffer();
    await pc.setLocalDescription(offer);
    this.socket.emit('offer', { to: userId, offer });
    this.participants.push({
      id: userId,
      stream: new MediaStream(),
      name: userName,
      isWhiteboardActive: false,
      isScreenSharing: false,
      hasScreenSharePermission: true,
      hasWhiteboardPermission: isHost,
      isAudioMuted: false,
      isVideoOff: false,
      showCameraWithScreenShare: false
    });
  }
  createPeerConnection(userId: string): RTCPeerConnection {
    const pc = new RTCPeerConnection({
      iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
    });
 
    pc.onicecandidate = (event) => {
      if (event.candidate) {
        this.socket.emit('ice-candidate', { to: userId, candidate: event.candidate });
      }
    };
 
    pc.ontrack = (event) => {
      const participant = this.participants.find(p => p.id === userId);
      if (participant) {
        // Add the new track to the participant's stream
        participant.stream.addTrack(event.track);
        this.cdr.detectChanges();
      }
    };
 
    if (this.localStream) {
      // Add the local stream to the peer connection
      this.localStream.getTracks().forEach(track => {
        pc.addTrack(track, this.localStream!);
      });
    }
 
    this.peerConnections.set(userId, pc);
    return pc;
  }
 

  removeParticipant(userId: string) {
    const index = this.participants.findIndex(p => p.id === userId);
    if (index !== -1) {
      this.participants.splice(index, 1);
    }
    const pc = this.peerConnections.get(userId);
    if (pc) {
      pc.close();
      this.peerConnections.delete(userId);
    }
  }
  sendMessage() {
    if (this.newMessage.trim()) {
      const message: Message = {
        id: Date.now().toString(),
        sender: this.userName,
        content: this.newMessage.trim(),
        time: new Date(),
        isPrivate: this.chatRecipient !== 'everyone',
        recipient: this.chatRecipient !== 'everyone' ? this.chatRecipient : undefined
      };

      if (message.isPrivate) {
        this.socket.emit('private-message', { roomId: this.roomId, message, recipientId: this.chatRecipient });
      } else {
        this.socket.emit('send-message', { roomId: this.roomId, message });
      }

      this.messages.push(message);
      this.newMessage = '';
      this.scrollToBottom();
    }
  }
  private handlePrivateMessage(message: Message) {
    if (!this.messages.some(m => m.id === message.id)) {
      this.messages.push(message);
      this.cdr.detectChanges();
      this.scrollToBottom();
    }
  }

  toggleCameraWithScreenShare(participantId: string) {
    const participant = this.participants.find(p => p.id === participantId);
    if (participant) {
      participant.showCameraWithScreenShare = !participant.showCameraWithScreenShare;
      this.socket.emit('camera-toggle', { roomId: this.roomId, userId: participantId, showCamera: participant.showCameraWithScreenShare });
      this.cdr.detectChanges();
    }
  }

  private handleCameraToggle({ userId, showCamera }: { userId: string; showCamera: boolean }) {
    const participant = this.participants.find(p => p.id === userId);
    if (participant) {
      participant.showCameraWithScreenShare = showCamera;
      this.cdr.detectChanges();
    }
  }
  private scrollToBottom() {
    setTimeout(() => {
      const chatMessages = this.chatMessages.nativeElement;
      chatMessages.scrollTop = chatMessages.scrollHeight;
    }, 0);
  }

  toggleChat() {
    this.showChat = !this.showChat;
    this.showParticipants = false;
    this.showMeetingDetails = false;
    this.cdr.detectChanges();
  }

  toggleAudio() {
    if (this.localStream) {
      const audioTrack = this.localStream.getAudioTracks()[0];
      if (audioTrack) {
        audioTrack.enabled = !audioTrack.enabled;
        this.isAudioMuted = !audioTrack.enabled;
        this.socket.emit('audio-toggle', { roomId: this.roomId, userId: this.socket.id, isAudioMuted: this.isAudioMuted });
      }
    }
    this.cdr.detectChanges();
  }

  async toggleVideo() {
    if (this.localStream) {
      this.isVideoOff = !this.isVideoOff;
      if (this.isVideoOff) {
        await this.stopVideoTrack();
      } else {
        await this.startVideoTrack();
      }
      await this.updateLocalVideoStream();
      this.socket.emit('video-toggle', { roomId: this.roomId, userId: this.socket.id, isVideoOff: this.isVideoOff });
    }
    this.cdr.detectChanges();
  }


  private async stopVideoTrack() {
    console.log('Stopping video track...');
    const videoTrack = this.localStream!.getVideoTracks()[0];
    if (videoTrack) {
      videoTrack.stop();
      this.localStream!.removeTrack(videoTrack);
      console.log('Video track stopped and removed');
    }
  }
  private async startVideoTrack() {
    try {
      console.log('Starting new video track...');
      const newStream = await navigator.mediaDevices.getUserMedia({
        video: {
          width: { ideal: 1280 },
          height: { ideal: 720 },
          frameRate: { ideal: 30 }
        }
      });
      const newVideoTrack = newStream.getVideoTracks()[0];
      this.localStream!.addTrack(newVideoTrack);
      await this.replaceVideoTrackInPeerConnections(newVideoTrack);
      console.log('New video track started and added');
    } catch (error) {
      console.error('Error starting video track:', error);
    }
  }
  private async replaceVideoTrackInPeerConnections(newTrack: MediaStreamTrack) {
    console.log('Replacing video track in peer connections...');
    for (const [peerId, pc] of this.peerConnections) {
      const sender = pc.getSenders().find(s => s.track?.kind === 'video');
      if (sender) {
        try {
          await sender.replaceTrack(newTrack);
          console.log(`Video track replaced for peer: ${peerId}`);
        } catch (error) {
          console.error(`Error replacing track for peer ${peerId}:`, error);
        }
      }
    }
  }
  async toggleScreenSharing() {
    debugger
    if (!this.isScreenSharing) {
      await this.startScreenSharing();
    } else {
      this.stopScreenSharing();
    }
  }

  async startScreenSharing() {
    try {
      this.screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
     
      if (this.screenStream && this.screenStream.getVideoTracks().length > 0) {
        this.isScreenSharing = true;
        const screenTrack = this.screenStream.getVideoTracks()[0];
        this.replaceVideoTrack(screenTrack);
        screenTrack.onended = () => {
          this.stopScreenSharing();
        };
        this.socket.emit('screen-share-started', { roomId: this.roomId, userId: this.socket.id });
       
        // Keep the camera stream separate
        const cameraStream = await navigator.mediaDevices.getUserMedia({ video: true });
       
        this.focusedParticipant = {
          id: this.socket.id,
          stream: this.screenStream,
          cameraStream: cameraStream,
          name: this.userName + ' (Screen)',
          isWhiteboardActive: false,
          isScreenSharing: true,
          hasScreenSharePermission: true,
          hasWhiteboardPermission: true,
          isAudioMuted: this.isAudioMuted,
          isVideoOff: false,
          showCameraWithScreenShare: true
        };
        this.isGridView = false;
        this.cdr.detectChanges();
      } else {
        console.warn('No video tracks found in the screen sharing stream.');
        this.stopScreenSharing();
      }
    } catch (error) {
      console.error('Error sharing screen:', error);
      this.stopScreenSharing();
    }
  }

  async stopScreenSharing() {
    if (this.screenStream) {
      this.screenStream.getTracks().forEach(track => track.stop());
      this.screenStream = null;
    }
    this.isScreenSharing = false;
   
    // Restore the original video track
    await this.restoreVideoTrack();
   
    this.socket.emit('screen-share-stopped', { roomId: this.roomId, userId: this.socket.id });
    this.focusedParticipant = null;
    this.isGridView = true;
    this.cdr.detectChanges();
  }
 
  async restoreVideoTrack() {
    try {
      // Get a new video stream
      const newStream = await navigator.mediaDevices.getUserMedia({ video: true });
      const newVideoTrack = newStream.getVideoTracks()[0];
     
      // Replace the track in all peer connections
      this.replaceVideoTrack(newVideoTrack);
     
      // Update local video
      if (this.localVideo && this.localVideo.nativeElement) {
        this.localVideo.nativeElement.srcObject = new MediaStream([newVideoTrack]);
      }
     
      // Don't forget to stop the old video track if it exists
      if (this.localStream) {
        const oldVideoTrack = this.localStream.getVideoTracks()[0];
        if (oldVideoTrack) {
          oldVideoTrack.stop();
        }
        // Update the localStream with the new video track
        this.localStream = new MediaStream([
          ...this.localStream.getAudioTracks(),
          newVideoTrack
        ]);
      }
    } catch (error) {
      console.error('Error restoring video track:', error);
    }
  }
 
  replaceVideoTrack(newTrack: MediaStreamTrack | null) {
    this.peerConnections.forEach(pc => {
      const sender = pc.getSenders().find(s => s.track?.kind === 'video');
      if (sender) {
        sender.replaceTrack(newTrack);
      }
    });
 
    if (this.localVideo && this.localVideo.nativeElement) {
      if (newTrack) {
        const stream = new MediaStream([newTrack]);
        this.localVideo.nativeElement.srcObject = stream;
      } else {
        this.localVideo.nativeElement.srcObject = null;
      }
    }
  }

  focusParticipant(participant: Participant) {
    if (this.isGridView) {
      this.focusedParticipant = participant;
      this.isGridView = false;
    } else {
      this.focusedParticipant = null;
      this.isGridView = true;
    }
    this.cdr.detectChanges();
  }

  toggleFullscreen() {
    if (!this.isFullscreen) {
      const elem = document.documentElement;
      if (elem.requestFullscreen) {
        elem.requestFullscreen();
      }
      this.isFullscreen = true;
    } else {
      if (document.exitFullscreen) {
        document.exitFullscreen();
      }
      this.isFullscreen = false;
    }
    this.cdr.detectChanges();
  }

 
  toggleWhiteboard() {
    if (this.isHost || this.hasWhiteboardPermission) {
      this.localWhiteboardActive = !this.localWhiteboardActive;
   
      if (this.localWhiteboardActive) {
        this.activeView = 'whiteboard';
      } else {
        this.activeView = 'camera';
        this.restoreVideoTrack();
      }
   
      this.socket.emit('whiteboard-toggle', { roomId: this.roomId, userId: this.socket.id, isActive: this.localWhiteboardActive });
      this.cdr.detectChanges();
    } else {
      alert('You do not have permission to use the whiteboard.');
    }
  }

  updateParticipantPermission(participantId: string, permission: 'screenShare' | 'whiteboard', value: boolean) {
    if (this.isHost) {
      this.socket.emit('update-permission', { roomId: this.roomId, userId: participantId, permission, value });
    }
  }

  toggleRecording() {
    if (!this.isRecording) {
      this.startRecording();
    } else {
      this.stopRecording();
    }
  }

  toggleParticipantsList() {
    this.showParticipants = !this.showParticipants;
    this.showChat = false;
    this.showMeetingDetails = false;
    this.cdr.detectChanges();
  }

  startRecording() {
    this.recordedChunks = [];
    const stream = new MediaStream();
    if (this.localStream) {
      this.localStream.getTracks().forEach(track => stream.addTrack(track));
    }
    this.participants.forEach(participant => {
      participant.stream.getTracks().forEach(track => stream.addTrack(track));
    });

    this.mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
    this.mediaRecorder.ondataavailable = (event) => {
      if (event.data.size > 0) {
        this.recordedChunks.push(event.data);
      }
    };
    this.mediaRecorder.start();
    this.isRecording = true;
    this.socket.emit('recording-started', { roomId: this.roomId, userId: this.socket.id });
  }

  stopRecording() {
    if (this.mediaRecorder) {
      this.mediaRecorder.stop();
      this.isRecording = false;
      this.socket.emit('recording-stopped', { roomId: this.roomId, userId: this.socket.id });
      setTimeout(() => {
        const blob = new Blob(this.recordedChunks, { type: 'video/webm' });
        this.uploadRecording(blob);
      }, 100);
    }
  }

  uploadRecording(blob: Blob) {
    const formData = new FormData();
    formData.append('file', blob, `recording_${Date.now()}.webm`);
    formData.append('roomId', this.roomId);

    this.http.post<{ id: string; name: string; date: Date }>('http://localhost:3000/api/recordings', formData)
      .subscribe(
        (response) => {
          console.log('Recording uploaded successfully');
          // Handle successful upload (e.g., show a notification)
        },
        (error) => {
          console.error('Error uploading recording:', error);
          // Handle upload error (e.g., show an error message)
        }
      );
  }

  toggleMeetingDetails() {
    this.showMeetingDetails = !this.showMeetingDetails;
    this.showChat = false;
    this.showParticipants = false;
    this.cdr.detectChanges();
  }

  raiseHand() {
    this.socket.emit('raise-hand', { roomId: this.roomId, userId: this.socket.id, userName: this.userName });
  }

  lowerHand() {
    this.socket.emit('lower-hand', { roomId: this.roomId, userId: this.socket.id });
  }

  toggleMoreOptions() {
    // Implement the logic to show/hide more options
    console.log('Toggle more options');
  }

  changeHostTo(newHostId: string) {
    if (this.isHost) {
      this.socket.emit('change-host', { roomId: this.roomId, newHostId });
    }
  }

  kickParticipant(participantId: string) {
    if (this.isHost) {
      this.socket.emit('kick-participant', { roomId: this.roomId, participantId });
    }
  }
  trackByFn(index: number, item: any): any {
    return item.id;
  }
}



HTML


<div class="video-conference-container">
  <ng-container *ngIf="!isConnected">
    <div class="join-room">
      <h2>Video Conference</h2>
      <ng-container *ngIf="!roomId">
        <input [(ngModel)]="roomId" placeholder="Enter Room ID" />
        <input [(ngModel)]="userName" placeholder="Enter Your Name" />
        <button (click)="joinRoom(false)" [disabled]="!roomId || !userName">Join Meeting</button>
        <button (click)="generateRoomId(); promptForUserName(true)">Create New Meeting</button>
      </ng-container>
      <ng-container *ngIf="roomId">
        <p>Room ID: {{ roomId }}</p>
        <input [(ngModel)]="userName" placeholder="Enter Your Name" />
        <button (click)="joinRoom(false)" [disabled]="!userName">Join Meeting</button>
      </ng-container>
    </div>
  </ng-container>
  <!-- <ng-container *ngIf="!isConnected">
    <app-room-creation (roomJoined)="onRoomJoined($event)"></app-room-creation>
  </ng-container> -->
  <ng-container *ngIf="isConnected">
    <div class="meeting-room" [class.fullscreen]="isFullscreen">
      <ng-container >
        <div class="video-grid">
          <div class="video-container local-video"
          [class.whiteboard-active]="localWhiteboardActive"
          [class.screen-sharing]="isScreenSharing"
          (click)="focusParticipant({
            id: 'local',
            stream: localStream!,
            name: userName,
            isWhiteboardActive: localWhiteboardActive,
            isScreenSharing: isScreenSharing,
            hasScreenSharePermission: hasScreenSharePermission,
            hasWhiteboardPermission: hasWhiteboardPermission,
            isAudioMuted: isAudioMuted,
            isVideoOff: isVideoOff,
            showCameraWithScreenShare: false
          })">
            <ng-container *ngIf="!localWhiteboardActive && !isScreenSharing">
         <video #localVideo autoplay muted [class.video-off]="isVideoOff"></video>
       </ng-container>
       <ng-container *ngIf="isScreenSharing">
         <video #localVideo autoplay muted [srcObject]="screenStream"></video>
         <div class="screen-share-indicator">Screen sharing active</div>
       </ng-container>
       <ng-container *ngIf="localWhiteboardActive">
         <app-whiteboard [socket]="socket" [roomId]="roomId"></app-whiteboard>
       </ng-container>
       <div class="participant-name">
         You ({{ userName }})
         <ng-container *ngIf="isHost">(Host)</ng-container>
         <ng-container *ngIf="isScreenSharing">(Screen)</ng-container>
         <ng-container *ngIf="localWhiteboardActive">(Whiteboard)</ng-container>
       </div>
       <div class="audio-indicator" [class.muted]="isAudioMuted">
         <i class="material-icons">{{ isAudioMuted ? 'mic_off' : 'mic' }}</i>
       </div>
     </div>


     <ng-container *ngFor="let participant of participants; trackBy: trackByFn">
      <div class="video-container remote-video"
           [class.whiteboard-active]="participant.isWhiteboardActive"
           [class.screen-sharing]="participant.isScreenSharing"
           (click)="focusParticipant(participant)">
        <ng-container *ngIf="!participant.isWhiteboardActive">
          <video [srcObject]="participant.stream" autoplay [class.video-off]="participant.isVideoOff"></video>
        </ng-container>
        <ng-container *ngIf="participant.isWhiteboardActive">
          <app-whiteboard [socket]="socket" [roomId]="roomId"></app-whiteboard>
        </ng-container>
        <ng-container *ngIf="participant.isScreenSharing && isHost && participant.showCameraWithScreenShare">
          <div class="camera-overlay">
            <video [srcObject]="participant.cameraStream" autoplay></video>
          </div>
        </ng-container>
        <div class="participant-name">
          {{ participant.name }}
          <ng-container *ngIf="participant.isScreenSharing">(Screen)</ng-container>
          <ng-container *ngIf="participant.isWhiteboardActive">(Whiteboard)</ng-container>
        </div>
        <div class="audio-indicator" [class.muted]="participant.isAudioMuted">
          <i class="material-icons">{{ participant.isAudioMuted ? 'mic_off' : 'mic' }}</i>
        </div>
      </div>
    </ng-container>
        </div>
      </ng-container>

      <ng-template #focusedView>
        <div class="focused-view">
          <div class="main-video">
            <ng-container *ngIf="focusedParticipant">
              <ng-container *ngIf="focusedParticipant.isWhiteboardActive">
                <app-whiteboard [socket]="socket" [roomId]="roomId"></app-whiteboard>
              </ng-container>
              <ng-container *ngIf="!focusedParticipant.isWhiteboardActive">
                <video [srcObject]="focusedParticipant.stream" autoplay [muted]="focusedParticipant.id === 'local'" [class.video-off]="focusedParticipant.isVideoOff"></video>
              </ng-container>
              <div class="participant-name">
                {{ focusedParticipant.name }}
                <ng-container *ngIf="focusedParticipant.isScreenSharing">(Screen)</ng-container>
                <ng-container *ngIf="focusedParticipant.isWhiteboardActive">(Whiteboard)</ng-container>
              </div>
              <div class="audio-indicator" [class.muted]="focusedParticipant.isAudioMuted">
                <i class="material-icons">{{ focusedParticipant.isAudioMuted ? 'mic_off' : 'mic' }}</i>
              </div>
            </ng-container>
          </div>
          <div class="thumbnail-strip">
            <ng-container *ngIf="focusedParticipant?.id !== 'local' && localStream">
              <div class="video-thumbnail local-video"
                   (click)="focusParticipant({
                     id: 'local',
                     stream: localStream,
                     name: userName,
                     isWhiteboardActive: localWhiteboardActive,
                     isScreenSharing: isScreenSharing,
                     hasScreenSharePermission: true,
                     hasWhiteboardPermission: true,
                     isAudioMuted: isAudioMuted,
                     isVideoOff: isVideoOff,
                     showCameraWithScreenShare: false
                   })">
                <video #localVideo autoplay muted [class.video-off]="isVideoOff"></video>
                <div class="participant-name">You ({{ userName }})</div>
              </div>
            </ng-container>
            <ng-container *ngFor="let participant of participants; trackBy: trackByFn">
              <ng-container *ngIf="participant.id !== focusedParticipant?.id">
                <div class="video-thumbnail" (click)="focusParticipant(participant)">
                  <video [srcObject]="participant.stream" autoplay [class.video-off]="participant.isVideoOff"></video>
                  <div class="participant-name">{{ participant.name }}</div>
                </div>
              </ng-container>
            </ng-container>
          </div>
        </div>
      </ng-template>

      <div class="controls-bar">
        <div class="left-controls">
          <button (click)="toggleMeetingDetails()" class="control-btn">
            <i class="material-icons">info</i>
          </button>
        </div>
        <div class="center-controls">
          <button (click)="toggleAudio()" [class.active]="!isAudioMuted" class="control-btn">
            <i class="material-icons">{{ isAudioMuted ? 'mic_off' : 'mic' }}</i>
          </button>
          <button (click)="toggleVideo()" [class.active]="!isVideoOff" class="control-btn">
            <i class="material-icons">{{ isVideoOff ? 'videocam_off' : 'videocam' }}</i>
          </button>
          <button (click)="toggleRecording()" [class.active]="isRecording" class="control-btn">
            <i class="material-icons">{{ isRecording ? 'stop' : 'fiber_manual_record' }}</i>
          </button>
          <button (click)="toggleScreenSharing()" [disabled]="!hasScreenSharePermission" [class.active]="isScreenSharing" class="control-btn">
            <i class="material-icons">{{ isScreenSharing ? 'stop_screen_share' : 'present_to_all' }}</i>
          </button>
          <button (click)="toggleWhiteboard()" [disabled]="!hasWhiteboardPermission" [class.active]="localWhiteboardActive" class="control-btn">
            <i class="material-icons">dashboard</i>
          </button>
          <button (click)="raiseHand()" class="control-btn">
            <i class="material-icons">pan_tool</i>
          </button>
          <button (click)="leaveRoom()" class="leave-btn">
            <i class="material-icons">call_end</i>
          </button>
          <button (click)="toggleFullscreen()" class="control-btn">
            <i class="material-icons">{{ isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</i>
          </button>
        </div>
        <div class="right-controls">
          <button (click)="toggleChat()" [class.active]="showChat" class="control-btn">
            <i class="material-icons">chat</i>
          </button>
          <button (click)="toggleParticipantsList()" [class.active]="showParticipants" class="control-btn">
            <i class="material-icons">people</i>
          </button>
          <button (click)="toggleMoreOptions()" class="control-btn">
            <i class="material-icons">more_vert</i>
          </button>
        </div>
      </div>

      <div class="side-panel" [class.show-panel]="showChat || showParticipants || showMeetingDetails">
        <ng-container *ngIf="showChat">
          <div class="chat-container">
            <div class="panel-header">
              <h3>Meeting chat</h3>
              <button (click)="toggleChat()" class="close-panel">
                <i class="material-icons">close</i>
              </button>
            </div>
            <div class="chat-messages" #chatMessages>
              <div class="chat-messages" #chatMessages>
                <ng-container *ngFor="let message of messages; trackBy: trackByFn">
                  <div class="message" [class.own-message]="message.sender === userName" [class.private-message]="message.isPrivate">
                    <div class="message-sender">{{ message.sender }} {{ message.isPrivate ? '(Private)' : '' }}</div>
                    <div class="message-content">
                      {{ message.content }}
                      <ng-container *ngIf="message.sender === userName">
                        <span class="message-status">
                          <i class="material-icons">check</i>
                        </span>
                      </ng-container>
                    </div>
                    <div class="message-time">{{ message.time | date:'shortTime' }}</div>
                  </div>
                </ng-container>
            </div>
            <div class="chat-input">
              <select [(ngModel)]="chatRecipient">
                <option value="everyone">Everyone</option>
                <option value="host" *ngIf="!isHost">Host</option>
                <ng-container *ngIf="isHost">
                  <option *ngFor="let participant of participants" [value]="participant.id">{{ participant.name }}</option>
                </ng-container>
              </select>
              <input
                [(ngModel)]="newMessage"
                placeholder="Send a message"
                (keyup.enter)="sendMessage()"
              />
              <button (click)="sendMessage()" [disabled]="!newMessage.trim()">
                <i class="material-icons">send</i>
              </button>
            </div>
          </div>
          </div>
        </ng-container>
        <ng-container *ngIf="showParticipants">
          <div class="participants-list">
            <div class="panel-header">
              <h3>Participants ({{ participants.length + 1 }})</h3>
              <button (click)="toggleParticipantsList()" class="close-panel">
                <i class="material-icons">close</i>
              </button>
            </div>
            <div class="participant-item host">
              <span>You ({{ userName }}) {{ isHost ? '(Host)' : '' }}</span>
              <i class="material-icons">person</i>
            </div>
            <ng-container *ngFor="let participant of participants; trackBy: trackByFn">
              <div class="participant-item">
                <span>{{ participant.name }}</span>
                <i class="material-icons">person</i>
                <ng-container *ngIf="isHost">
                  <div class="participant-controls">
                    <button (click)="updateParticipantPermission(participant.id, 'screenShare', !participant.hasScreenSharePermission)">
                      {{ participant.hasScreenSharePermission ? 'Revoke Screen Share' : 'Allow Screen Share' }}
                    </button>
                    <button (click)="updateParticipantPermission(participant.id, 'whiteboard', !participant.hasWhiteboardPermission)">
                      {{ participant.hasWhiteboardPermission ? 'Revoke Whiteboard' : 'Allow Whiteboard' }}
                    </button>
                    <button (click)="toggleCameraWithScreenShare(participant.id)">
                      {{ participant.showCameraWithScreenShare ? 'Hide Camera' : 'Show Camera' }}
                    </button>
                    <button (click)="kickParticipant(participant.id)">
                      Kick Participant
                    </button>
                  </div>
                </ng-container>
              </div>
            </ng-container>
          </div>
        </ng-container>
        <ng-container *ngIf="showMeetingDetails">
          <div class="meeting-details">
            <div class="panel-header">
              <h3>Meeting Details</h3>
              <button (click)="toggleMeetingDetails()" class="close-panel">
                <i class="material-icons">close</i>
              </button>
            </div>
            <div class="details-content">
              <p><strong>Room ID:</strong> {{ roomId }}</p>
              <p><strong>Meeting Link:</strong> {{ meetingLink }}</p>
              <button (click)="copyMeetingLink()">Copy Link</button>
            </div>
          </div>
        </ng-container>
      </div>
    </div>
  </ng-container>
</div>


 

Post a Comment

0 Comments