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>
0 Comments