Google meet Clone-2

 <div class="video-conference-container">

  <ng-container *ngIf="!isConnected">
    <div class="join-room">
      <h2>Video Conference</h2>
      <ng-container *ngIf="!roomId">
        <select [(ngModel)]="meetingType">
          <option value="instant">Instant Meeting</option>
          <option value="scheduled">Scheduled Meeting</option>
        </select>
        <input [(ngModel)]="roomId" placeholder="Enter Room ID" *ngIf="meetingType === 'instant'" />
        <input [(ngModel)]="scheduledMeetingId" placeholder="Enter Scheduled Meeting ID" *ngIf="meetingType === 'scheduled'" />
        <input [(ngModel)]="userName" placeholder="Enter Your Name" *ngIf="!isAuthenticated" />
        <button (click)="joinRoom(false)" [disabled]="!canJoin()">Join Meeting</button>
        <button (click)="createNewMeeting()" *ngIf="meetingType === 'instant'">Create New Instant Meeting</button>
        <button (click)="scheduleMeeting()" *ngIf="meetingType === 'scheduled'">Schedule New Meeting</button>
      </ng-container>
      <ng-container *ngIf="roomId">
        <p>Room ID: {{ roomId }}</p>
        <input [(ngModel)]="userName" placeholder="Enter Your Name" *ngIf="!isAuthenticated" />
        <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,
            isHandRaised: false,
            isHost: isHost
          })">
            <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="isHandRaised">
          <i class="material-icons hand-raised-icon">pan_tool</i>
        </ng-container>
         <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.isHandRaised">
            <i class="material-icons hand-raised-icon">pan_tool</i>
          </ng-container>
          <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.isHost">(Host)</ng-container>
                <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,
                     isHandRaised: false,
                     isHost: isHost
                   })">
                <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-container remote-video"
                [class.whiteboard-active]="participant.isWhiteboardActive"
                [class.screen-sharing]="participant.isScreenSharing"
                (click)="focusParticipant(participant)">
                  <div class="participant-name">{{ participant.name }}
                  <ng-container *ngIf="participant.isHost">(Host)</ng-container></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>
            <span class="notification-badge" *ngIf="unreadMessagesCount > 0">
              {{ unreadMessagesCount }}
            </span>
          </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>
          <button (click)="toggleHandRaise()" class="control-btn">
            <i class="material-icons">{{ isHandRaised ? 'pan_tool' : 'pan_tool_outlined' }}</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 }}
                  <ng-container *ngIf="participant.isHost">(Host)</ng-container>
                  <ng-container *ngIf="participant.isHandRaised">
                    <i class="material-icons hand-raised-icon">pan_tool</i>
                  </ng-container>
                </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>
                    <div class="participant-controls">
                      <button (click)="changeHost(participant.id)">Make Host</button>
                      <button (click)="kickParticipant(participant.id)">Remove</button>
                    </div>
                  </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>

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 { 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;
  isHandRaised: boolean;
  showCameraWithScreenShare: boolean;
  isHost: 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],
  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';
  isHandRaised: boolean = false;
  private mediaRecorder: MediaRecorder | null = null;
  private recordedChunks: Blob[] = [];
  chatRecipient: string = 'everyone';
  userId:any

  meetingType: 'instant' | 'scheduled' = 'instant';
  scheduledMeetingId: string = '';
  isAuthenticated: boolean = false;
  private hostId: string = '';
  unreadMessagesCount: number = 0;
  private messageSound: HTMLAudioElement;
  isScreenSharingAllowed: boolean = true;
  isWhiteboardAllowed: boolean = true;
  private userNames: { [key: string]: string } = {};

 

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

    this.messageSound = new Audio('assets/sounds/message-notification.mp3');
  }
 

  ngOnInit() {
    if (isPlatformBrowser(this.platformId)) {
      this.checkAuthentication();
    }
  }

  private checkAuthentication() {
    this.authService.isLoggedIn().subscribe(
      isLoggedIn => {
        this.isAuthenticated = isLoggedIn;
        if (isLoggedIn) {
          const user = this.authService.currentUserValue;
          if (user) {
            this.userName = user.user.name;
            this.userId = user.user.id;
          }
          this.initializeBrowserFeatures();
        } else {
          this.router.navigate(['/login'], { queryParams: { returnUrl: this.router.url } });
        }
      }
    );
  }
  private initializeBrowserFeatures() {
    this.setupSocketListeners();
   
    this.route.params.subscribe(params => {
      if (params['roomId']) {
        this.roomId = params['roomId'];
        this.joinRoom(false);
      }
    });
  }

  canJoin(): boolean {
    if (this.meetingType === 'instant') {
      return !!this.roomId && !!this.userName;
    } else {
      return !!this.scheduledMeetingId && !!this.userName;
    }
  }

  createNewMeeting() {
    this.generateRoomId();
    this.joinRoom(true);
  }

  scheduleMeeting() {
    // Implement logic to schedule a meeting
    // This might involve sending a request to your backend to create a scheduled meeting
    this.http.post<{ meetingId: string }>(`${environment.apiUrls}/meeting/schedule-meeting`, {
      hostId: this.userId,
      scheduledTime: new Date() // You might want to add a date picker in the UI
    }).subscribe(
      response => {
        this.scheduledMeetingId = response.meetingId;
        alert(`Meeting scheduled. ID: ${this.scheduledMeetingId}`);
      },
      error => {
        console.error('Error scheduling meeting:', error);
        alert('Failed to schedule meeting. Please try again.');
      }
    );
  }

 
  ngOnDestroy() {
    this.leaveRoom();
    if (this.socket) {
      this.socket.disconnect();
    }
  }

  private setupSocketListeners() {
    debugger
   
    this.socket.on('room-state', this.handleRoomState.bind(this));
    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));
    this.socket.on('video-toggle', this.handleVideoToggle.bind(this));

    this.socket.on('user-raised-hand', this.handleHandRaised.bind(this));
    this.socket.on('user-lowered-hand', this.handleHandLowered.bind(this));
    this.socket.on('host-assigned', ({ hostId }) => {
      this.hostId = hostId;
      this.isHost = this.socket.id === hostId;
      this.participants.forEach(p => p.isHost = p.id === hostId);
      this.cdr.detectChanges();
    });
    this.socket.on('participant-kicked', ({ participantId }) => {
      if (participantId === this.socket.id) {
        alert('You have been removed from the meeting by the host.');
        this.leaveRoom();
      } else {
        this.removeParticipant(participantId);
      }
    });
 
    this.socket.on('host-changed', ({ newHostId, newHostName }) => {
      this.hostId = newHostId;
      this.isHost = this.socket.id === newHostId;
      this.participants.forEach(p => p.isHost = p.id === newHostId);
      // Update the participant's name to show (Host)
      const newHost = this.participants.find(p => p.id === newHostId);
      if (newHost) {
        newHost.name = `${newHost.name} (Host)`;
      }
      this.cdr.detectChanges();
    });
    this.socket.on('host-media-change', ({ mediaType, isActive }) => {
      if (mediaType === 'screen') {
        this.handleHostScreenShare(isActive);
      } else if (mediaType === 'video') {
        this.handleHostVideoChange(isActive);
      }
    });
 

    this.socket.on('screen-share-denied', () => {
      alert('Screen sharing is currently in use by another participant.');
    });
    this.socket.on('whiteboard-denied', () => {
      alert('Whiteboard is currently in use by another participant.');
    });
  }
  changeHost(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 });
    }
  }
  private handleHostScreenShare(isActive: boolean) {
    const hostParticipant = this.participants.find(p => p.id === this.hostId);
    if (hostParticipant) {
      hostParticipant.isScreenSharing = isActive;
      if (isActive) {
        this.focusedParticipant = hostParticipant;
        this.isGridView = false;
      } else {
        this.focusedParticipant = null;
        this.isGridView = true;
      }
      this.cdr.detectChanges();
    }
  }
  private handleHostVideoChange(isActive: boolean) {
    const hostParticipant = this.participants.find(p => p.id === this.hostId);
    if (hostParticipant) {
      hostParticipant.isVideoOff = !isActive;
      this.cdr.detectChanges();
    }
  }
  private handleRoomState({ participants, hostId }: { participants: any[]; hostId: string }) {
    this.hostId = hostId; // Store the host's socket ID
    participants.forEach(participant => {
      if (participant.id !== this.socket.id) {
        this.connectToNewUser(participant.id, participant.userName, participant.isHost);
      }
    });
    this.cdr.detectChanges();
  }
 
  private handleUserConnected({ userId, userName, isHost }: { userId: string; userName: string; isHost: boolean }) {
    if (!userId || userId === this.socket.id) {
      console.error('No userId received!');
      return;  // Early exit if no userId is received
    }
 
    // Add the new participant to the list
    this.participants.push({
      id: userId,
      stream: new MediaStream(),
      name: userName,
      isWhiteboardActive: false,
      isScreenSharing: false,
      hasScreenSharePermission: true,
      hasWhiteboardPermission: isHost,
      isAudioMuted: false,
      isVideoOff: false,
      showCameraWithScreenShare: false,
      isHandRaised: false,
      isHost: false,
    });
    this.cdr.detectChanges();
  }

  private handleUserDisconnected(userId: string) {
    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 handleVideoToggle({ userId, isVideoOff }: { userId: string; isVideoOff: boolean }) {
    const participant = this.participants.find(p => p.id === userId);
    if (participant) {
      participant.isVideoOff = isVideoOff;
      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();

      // Increment unread messages count if chat is not open
      if (!this.showChat) {
        this.unreadMessagesCount++;
        this.messageSound.play();
      }
    }
  }
  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, cameraTrack }: { userId: string; cameraTrack?: MediaStreamTrack }) {
    this.isScreenSharingAllowed = false;
   
    const participant = this.participants.find(p => p.id === userId);
   
    if (participant) {
      participant.isScreenSharing = true;
 
      // Only create a new MediaStream if cameraTrack is valid
      if (cameraTrack && cameraTrack instanceof MediaStreamTrack) {
        participant.cameraStream = new MediaStream([cameraTrack]); // Properly convert cameraTrack to MediaStream
      }
 
      this.focusedParticipant = participant; // Focus on the participant who started screen sharing
      this.isGridView = false;
      this.cdr.detectChanges();
    }
  }
 
 
  private handleScreenShareStopped({ userId }: { userId: string }) {
    this.isScreenSharingAllowed = true;
    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 }) {
    if (isActive) {
      this.isWhiteboardAllowed = false;
    } else {
      this.isWhiteboardAllowed = true;
    }
    const participant = this.participants.find(p => p.id === userId);
    if (participant) {
      participant.isWhiteboardActive = isActive;
      if (isActive) {
        this.focusedParticipant = participant;
        this.isGridView = false;
      } else {
        this.focusedParticipant = null;
        this.isGridView = true;
      }
      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.hostId = newHostId;
    this.isHost = newHostId === this.socket.id;
    this.hasWhiteboardPermission = this.isHost;

    this.participants.forEach(participant => {
      participant.hasWhiteboardPermission = participant.id === newHostId;
    });
    this.cdr.detectChanges();
  }

  private handleHandRaised({ userId }: { userId: string }) {
    const participant = this.participants.find(p => p.id === userId);
    if (participant) {
      participant.isHandRaised = true;
      this.cdr.detectChanges();
    }
  }
  toggleHandRaise() {
    this.isHandRaised = !this.isHandRaised;
    if (this.isHandRaised) {
      this.socket.emit('raise-hand', { roomId: this.roomId, userId: this.socket.id });
    } else {
      this.socket.emit('lower-hand', { roomId: this.roomId, userId: this.socket.id });
    }
  }
 
  private handleHandLowered({ userId }: { userId: string }) {
    const participant = this.participants.find(p => p.id === userId);
    if (participant) {
      participant.isHandRaised = false;
      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.isAuthenticated) {
      this.router.navigate(['/login'], { queryParams: { returnUrl: this.router.url } });
      return;
    }

    if (!this.canJoin()) {
      alert('Please enter all required information.');
      return;
    }

    try {
      this.localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
      this.updateLocalVideoStream();

      if (this.meetingType === 'scheduled') {
        // Verify scheduled meeting
        this.http.get<{ isValid: boolean }>(`${environment.apiUrls}/meeting/verify-scheduled-meeting/${this.scheduledMeetingId}`).subscribe(
          response => {
            if (response.isValid) {
              this.roomId = this.scheduledMeetingId;
              this.connectToRoom(isHost);
            } else {
              alert('Invalid or expired scheduled meeting ID.');
            }
          },
          error => {
            console.error('Error verifying scheduled meeting:', error);
            alert('Failed to verify scheduled meeting. Please try again.');
          }
        );
      } else {
        this.connectToRoom(isHost);
      }
    } catch (error) {
      console.error('Error joining room:', error);
      this.handleJoinRoomError(error);
    }
  }
  private connectToRoom(isHost: boolean) {
    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();
  }

  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 });
   
    // Add participant to the list
    this.participants.push({
      id: userId,
      stream: new MediaStream(),
      name: userName,
      isWhiteboardActive: false,
      isScreenSharing: false,
      hasScreenSharePermission: true,
      hasWhiteboardPermission: isHost,
      isAudioMuted: false,
      isVideoOff: false,
      showCameraWithScreenShare: false,
      isHandRaised: false,
      isHost: false,
    });
  }
 
  createPeerConnection(userId: string): RTCPeerConnection {
    console.log('Creating PeerConnection for user:', userId);
    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) {
        // If the recipient is 'host', set the recipientId to host's socket ID
        if (this.chatRecipient === 'host') {
          message.recipient = this.hostId; // You'll need to store hostId
        }
        this.socket.emit('private-message', { roomId: this.roomId, message, recipientId: message.recipient });
      } else {
        this.socket.emit('send-message', { roomId: this.roomId, message });
      }
 
      this.messages.push(message);
      this.newMessage = '';
      this.scrollToBottom();
    }
  }
 
  private handlePrivateMessage({ message }: { message: Message }) {
    if (!this.messages.some(m => m.id === message.id)) {
      this.messages.push(message);
      this.cdr.detectChanges();
      this.scrollToBottom();
 
      // Increment unread messages count if chat is not open
      if (!this.showChat) {
        this.unreadMessagesCount++;
        this.messageSound.play();
      }
    }
  }

  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;
 
    // Reset unread messages count when chat is opened
    if (this.showChat) {
      this.unreadMessagesCount = 0;
    }
 
    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();
      if (this.isHost) {
        this.socket.emit('host-media-change', {
          roomId: this.roomId,
          mediaType: 'video',
          isActive: !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();
    }
    if (this.isHost) {
      this.socket.emit('host-media-change', {
        roomId: this.roomId,
        mediaType: 'screen',
        isActive: this.isScreenSharing
      });
    }
  }

 
  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();
        };
       
        // Keep the camera stream separate
        const cameraStream = await navigator.mediaDevices.getUserMedia({ video: true });
       
        this.socket.emit('screen-share-started', {
          roomId: this.roomId,
          userId: this.socket.id,
          cameraTrack: cameraStream.getVideoTracks()[0]
        });
       
        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,
          isHandRaised: false,
          isHost: false,
        };
        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);
  //   }
  // }
  async restoreVideoTrack() {
    if (isPlatformBrowser(this.platformId)) {
      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]);
        }
       
        // 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);
      }
    } else {
      console.warn('restoreVideoTrack called on the server; skipping.');
    }
  }
 
  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 });
    }
  }

 
  trackByFn(index: number, item: any): any {
    return item.id;
  }
}

Post a Comment

0 Comments