From b511daf85257d2e673938124c56646c97329419a Mon Sep 17 00:00:00 2001 From: mateen777 Date: Sat, 2 Mar 2024 18:49:43 +0530 Subject: [PATCH] all added lost files --- src/app/core/services/call.service.ts | 43 +++ src/app/core/services/http.service.ts | 10 +- .../core/services/navigator.service.spec.ts | 16 + src/app/core/services/navigator.service.ts | 45 +++ src/app/core/services/peerjs.service.ts | 141 +++++++ src/app/core/services/socket.service.ts | 66 +++- src/app/core/utils/helper.ts | 17 + .../register/register.component.html | 2 +- .../home/components/home/home.component.html | 91 ++++- .../home/components/home/home.component.scss | 83 ++++ .../home/components/home/home.component.ts | 84 +++- .../consumer/consumer.component.html | 4 +- .../components/consumer/consumer.component.ts | 78 +++- .../components/join/join.component.html | 177 ++++++++- .../components/join/join.component.ts | 359 +++++++++++++++--- .../modules/join-meeting/joinmeet.routes.ts | 4 +- src/assets/svgs/permissions_flow_allow.svg | 1 + .../svgs/permissions_flow_meet_blocked.svg | 1 + src/styles.scss | 19 + 19 files changed, 1125 insertions(+), 116 deletions(-) create mode 100644 src/app/core/services/call.service.ts create mode 100644 src/app/core/services/navigator.service.spec.ts create mode 100644 src/app/core/services/navigator.service.ts create mode 100644 src/app/core/services/peerjs.service.ts create mode 100644 src/app/core/utils/helper.ts create mode 100644 src/assets/svgs/permissions_flow_allow.svg create mode 100644 src/assets/svgs/permissions_flow_meet_blocked.svg diff --git a/src/app/core/services/call.service.ts b/src/app/core/services/call.service.ts new file mode 100644 index 0000000..4298820 --- /dev/null +++ b/src/app/core/services/call.service.ts @@ -0,0 +1,43 @@ +import { Injectable, inject } from '@angular/core'; +// import { RestService } from './rest.service'; +import { TokenModel } from 'openvidu-angular'; +import { Observable } from 'rxjs'; +import { HttpService } from './http.service'; +import { ApiMethod } from '../constants/apiRestRequest'; + +@Injectable({ + providedIn: 'root' +}) +export class CallService { + private privateAccess: boolean = true; + private initialized: boolean = false; + tokens!: TokenModel + + //services + http = inject(HttpService); + + constructor() {} + + async initialize(): Promise { + if (this.initialized) { + return; + } + // const config = await this.restService.getConfig(); + // this.privateAccess = config.isPrivate; + // this.initialized = true; + } + + isPrivateAccess(): boolean { + return this.privateAccess; + } + + getSessionId(): Observable { + return this.http.requestCall('/sessions', ApiMethod.POST); + } + + getTokens(sessionId:any,nickname:any):any { + return this.http.requestCall(`/:${sessionId}/connections`, ApiMethod.POST,{sessionId,nickname}); + } + + +} \ No newline at end of file diff --git a/src/app/core/services/http.service.ts b/src/app/core/services/http.service.ts index 7f29c9c..5c7a3f5 100644 --- a/src/app/core/services/http.service.ts +++ b/src/app/core/services/http.service.ts @@ -11,27 +11,27 @@ export class HttpService { constructor(private http:HttpClient) { } - requestCall(api:any,method:ApiMethod,data?:any){ + requestCall(apiEndpoint:any,method:ApiMethod,data?:any){ let response:any; switch (method) { case ApiMethod.GET: - response = this.http.get(`${environment.url}${api}`).pipe( + response = this.http.get(`${environment.url}${apiEndpoint}`).pipe( catchError((err)=> this.handleError(err))) break; case ApiMethod.POST: - response = this.http.post(`${environment.url}${api}`,data).pipe( + response = this.http.post(`${environment.url}${apiEndpoint}`,data).pipe( catchError((err)=> this.handleError(err))) break; case ApiMethod.PUT: - response = this.http.put(`${environment.url}${api}`,data).pipe( + response = this.http.put(`${environment.url}${apiEndpoint}`,data).pipe( catchError((err)=> this.handleError(err))) break; case ApiMethod.DELETE: - response = this.http.delete(`${environment.url}${api}`).pipe( + response = this.http.delete(`${environment.url}${apiEndpoint}`).pipe( catchError((err)=> this.handleError(err))) break; default: diff --git a/src/app/core/services/navigator.service.spec.ts b/src/app/core/services/navigator.service.spec.ts new file mode 100644 index 0000000..866fe6b --- /dev/null +++ b/src/app/core/services/navigator.service.spec.ts @@ -0,0 +1,16 @@ +/* tslint:disable:no-unused-variable */ + +import { TestBed, async, inject } from '@angular/core/testing'; +import { NavigatorService } from './navigator.service'; + +describe('Service: Navigator', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [NavigatorService] + }); + }); + + it('should ...', inject([NavigatorService], (service: NavigatorService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/core/services/navigator.service.ts b/src/app/core/services/navigator.service.ts new file mode 100644 index 0000000..337d2fa --- /dev/null +++ b/src/app/core/services/navigator.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@angular/core'; +import { Observable, from } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class NavigatorService { + + getVideoDevices(): Observable { + const allVideoDevices = this.getDevices('videoinput'); + return allVideoDevices; + + } + + getAudioDevices(): Observable { + const allAudioDevices = this.getDevices('audioinput'); + return allAudioDevices; + } + + getSpeakers(): Observable { + const allSpeakerDevices = this.getDevices('audiooutput'); + return allSpeakerDevices; + } + + private getDevices(kind: MediaDeviceKind): Observable { + return from(navigator.mediaDevices.enumerateDevices()).pipe( + map((devices:any) => { + return devices.filter((device:any) => + device.kind === kind && + device.deviceId !== 'default' && + !device.label.toLowerCase().includes('communications')); + }) + ); + } + + // private filterDevices(devices: MediaDeviceInfo[]): MediaDeviceInfo[] { + // // Filter out default and communications audio devices + // return devices.filter( + // (device) => + // device.deviceId !== 'default' && + // !device.label.toLowerCase().includes('communications') + // ); + // } +} \ No newline at end of file diff --git a/src/app/core/services/peerjs.service.ts b/src/app/core/services/peerjs.service.ts new file mode 100644 index 0000000..699fe4e --- /dev/null +++ b/src/app/core/services/peerjs.service.ts @@ -0,0 +1,141 @@ +import { Injectable, OnInit, inject } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { HttpService } from './http.service'; +import { ApiMethod, Webrtc } from '../constants/apiRestRequest'; +import { Peer } from "peerjs"; +import { Router } from '@angular/router'; + +@Injectable({ + providedIn: 'root' +}) +export class PeerjsService implements OnInit{ + + http = inject(HttpService); + router = inject(Router); + public message$: BehaviorSubject = new BehaviorSubject(''); + public userJoined$: BehaviorSubject = new BehaviorSubject(''); + peer:any; + peerId: any; + conn: any; + remoteCall: any; + + constructor() {} + + ngOnInit(): void { + + } + + initPeer(){ + this.peer = new Peer('',{ + host: "localhost", + port: 9000, + path: "/myapp", + }); + let lastPeerId:any; + this.peer.on('open', (id: any) => { + this.peerId = id; + console.log('My peer ID is: ' + id); + + if (this.peer.id === null) { + console.log('Received null id from peer open'); + this.peer.id = lastPeerId; + } else { + lastPeerId = this.peer.id; + } + }); + + this.peer.on('connection',(c:any)=> { + // Allow only a single connection + // if (this.conn && this.conn.open) { + // c.on('open', function () { + // c.send("Already connected to another client"); + // setTimeout(function () { c.close(); }, 500); + // }); + // return; + // } + + this.conn = c; + console.log("Connected to: " + this.conn.peer); + + this.conn.send('Hello'); + // Handle incoming data (messages only since this is the signal sender) + this.conn.on('data', function (data: any) { + console.log(data); + }); + this.conn.on('close', function () { + console.log("Connection closed"); + }); + }); + + this.peer.on('call',(call:any)=>{ + this.remoteCall = call; + this.router.navigate(['join/consumer']); + }) + + this.peer.on('disconnected', ()=> { + console.log('Connection lost. Please reconnect'); + + // Workaround for peer.reconnect deleting previous id + this.peer.id = lastPeerId; + this.peer._lastServerId = lastPeerId; + this.peer.reconnect(); + }); + + this.peer.on('close',()=> { + this.conn = null; + console.log('Connection destroyed'); + }); + this.peer.on('error',(err:any)=> { + console.log(err); + alert('' + err); + }); + } + + getpeer(){ + return this.peer; + } + + join(othersPeerId:any) { + // Close old connection + if (this.conn) { + this.conn.close(); + } + + // Create connection to destination peer specified in the input field + this.conn = this.peer.connect(othersPeerId, { + reliable: true + }); + + this.conn.on('open', () => { + console.log("Connected to: " + this.conn.peer); + + // Check URL params for comamnds that should be sent immediately + // var command = getUrlParam("command"); + // if (command) + // conn.send(command); + this.conn.send('Hello'); + this.router.navigate(['join']) + }); + // Handle incoming data (messages only since this is the signal sender) + this.conn.on('data', function (data: any) { + console.log(data); + }); + this.conn.on('close', function () { + console.log("Connection closed"); + }); + }; + + async getMedia() { + try { + const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true}); + + console.log(stream,'stream') + // this.vid = stream; + return stream; + } catch (error) { + console.log(error); + return null; + } +} + +} diff --git a/src/app/core/services/socket.service.ts b/src/app/core/services/socket.service.ts index 58b9f66..8e5c414 100644 --- a/src/app/core/services/socket.service.ts +++ b/src/app/core/services/socket.service.ts @@ -1,6 +1,7 @@ import { Injectable, OnInit, inject } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; -import { io } from 'socket.io-client'; +// import { io } from 'socket.io-client'; +import { Socket } from 'ngx-socket-io'; import { HttpService } from './http.service'; import { ApiMethod, Webrtc } from '../constants/apiRestRequest'; @@ -13,14 +14,69 @@ export class SocketService implements OnInit{ public message$: BehaviorSubject = new BehaviorSubject(''); public userJoined$: BehaviorSubject = new BehaviorSubject(''); - constructor() {} + private socket!:Socket; + constructor() { + this.socket = new Socket({ + url: "http://localhost:8000", + options: {}, + }); + // this.initSocketConnection(); + } + // private socket:any; + ngOnInit(): void { - + console.log('initSocket started'); + // this.initSocketConnection(); + } + + initSocketConnection(){ + // this.socket = io('http://localhost:8000'); + this.socket.on("connect", () => { + console.log('abcdefgh'); + // console.log(this.socket.id,'connected'); + }); + this.socket.on("disconnect", () => { + // console.log(this.socket.id,'disconnected'); + }); + + this.socket.on("connect_error", () => { + console.log('connect_error'); + }); + this.socket.on("from", (res:any) => { + console.log(res,'pp[p'); + }); + } + + get getSocket(){ + return this.socket; + } + + // this method is used to start connection/handhshake of socket with server + connectSocket(message:any) { + this.socket.emit('connect', message); + } + + // this method is used to get response from server + connectEvent() { + return this.socket.fromEvent('connect'); + } + disconnectEvent() { + return this.socket.fromEvent('disconnect'); + } + + + // this method is used to end web socket connection + disconnectSocket() { + this.socket.disconnect(); + } + + + sendMessage(){ + this.socket.emit('check','dwadaw') } - // socket = io('http://localhost:8000'); - socket!:any; + // socket!:any; public joinRoom(payload:any) { this.socket.emit('room:join', payload); diff --git a/src/app/core/utils/helper.ts b/src/app/core/utils/helper.ts new file mode 100644 index 0000000..78ac3ec --- /dev/null +++ b/src/app/core/utils/helper.ts @@ -0,0 +1,17 @@ +function generateUUID4() { + return ( + (1e7.toString() + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c:any) => + ( + c ^ + (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4))) + ).toString(16), + ) + ); +} + +function isValidUUID(uuid: string): boolean { + const uuidRegex = /^[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}$/; + return uuidRegex.test(uuid); + } + +export { generateUUID4, isValidUUID }; diff --git a/src/app/modules/authentication/components/register/register.component.html b/src/app/modules/authentication/components/register/register.component.html index 23de29c..d5a7950 100644 --- a/src/app/modules/authentication/components/register/register.component.html +++ b/src/app/modules/authentication/components/register/register.component.html @@ -15,7 +15,7 @@

Sign Up

*this field is required - *Enter correct email (abc@xyz.com). + *Enter correct email.
diff --git a/src/app/modules/home/components/home/home.component.html b/src/app/modules/home/components/home/home.component.html index b0baa49..52e699f 100644 --- a/src/app/modules/home/components/home/home.component.html +++ b/src/app/modules/home/components/home/home.component.html @@ -7,21 +7,24 @@

Meet People

+
+ +
{{ today | date:"h:mm"}}{{ today | date:"EEE, d MMM"}} - +
settings - account_circle - + -->
@@ -32,29 +35,49 @@

Meet People

Premium video meetings. Now free for - everyone.
+ everyone. +
We re-engineered the service that we built - for secure business meetings, Meet People, to make it free and available for all.
-
- - video_call - New meeting - - - keyboard_keys - - - + for secure business meetings, Meet People, to make it free and available for all. +
+
+
+
+ + video_call + New meeting + + + + keyboard_keys + + + + +
+ + this field is required. + + + invalid Id + +
+
+
- +
- images + images

{{product.name}}

{{product.description}}

@@ -77,7 +100,33 @@

{{product.name}}

- +
- \ No newline at end of file + + + + + + Here's the RoomId to your meeting + + + + content_copy + + +
+ + +
+
+ +
+
+ \ No newline at end of file diff --git a/src/app/modules/home/components/home/home.component.scss b/src/app/modules/home/components/home/home.component.scss index 2217abe..50fd8bf 100644 --- a/src/app/modules/home/components/home/home.component.scss +++ b/src/app/modules/home/components/home/home.component.scss @@ -12,4 +12,87 @@ // background-clip: text; // -webkit-background-clip: text; // color: transparent; +} + +.checkbox-wrapper-15 .cbx { + -webkit-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; + cursor: pointer; +} +.checkbox-wrapper-15 .cbx span { + display: inline-block; + vertical-align: middle; + transform: translate3d(0, 0, 0); +} +.checkbox-wrapper-15 .cbx span:first-child { + position: relative; + width: 24px; + height: 24px; + border-radius: 50%; + transform: scale(1); + vertical-align: middle; + border: 1px solid #B9B8C3; + transition: all 0.2s ease; +} +.checkbox-wrapper-15 .cbx span:first-child svg { + position: absolute; + z-index: 1; + top: 7px; + left: 6px; + fill: none; + stroke: white; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + stroke-dasharray: 16px; + stroke-dashoffset: 16px; + transition: all 0.3s ease; + transition-delay: 0.1s; + transform: translate3d(0, 0, 0); +} +.checkbox-wrapper-15 .cbx span:first-child:before { + content: ""; + width: 100%; + height: 100%; + background: #506EEC; + display: block; + transform: scale(0); + opacity: 1; + border-radius: 50%; + transition-delay: 0.2s; +} +.checkbox-wrapper-15 .cbx span:last-child { + margin-left: 8px; +} +.checkbox-wrapper-15 .cbx:hover span:first-child { + border-color: #3c53c7; +} + +.checkbox-wrapper-15 .inp-cbx:checked + .cbx span:first-child { + border-color: #3c53c7; + background: #3c53c7; + animation: check-15 0.6s ease; +} +.checkbox-wrapper-15 .inp-cbx:checked + .cbx span:first-child svg { + stroke-dashoffset: 0; +} +.checkbox-wrapper-15 .inp-cbx:checked + .cbx span:first-child:before { + transform: scale(2.2); + opacity: 0; + transition: all 0.6s ease; +} +.checkbox-wrapper-15 .inp-cbx:checked + .cbx span:last-child { + color: #B9B8C3; + transition: all 0.3s ease; +} +.checkbox-wrapper-15 .inp-cbx:checked + .cbx span:last-child:after { + transform: scaleX(1); + transition: all 0.3s ease; +} + +@keyframes check-15 { + 50% { + transform: scale(1.2); + } } \ No newline at end of file diff --git a/src/app/modules/home/components/home/home.component.ts b/src/app/modules/home/components/home/home.component.ts index 1fb5460..6463bf2 100644 --- a/src/app/modules/home/components/home/home.component.ts +++ b/src/app/modules/home/components/home/home.component.ts @@ -1,30 +1,57 @@ +//Angular imports import { Component, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators, AbstractControl } from '@angular/forms'; import { Router, RouterModule } from '@angular/router'; + +// primeNg imports import { ButtonModule } from 'primeng/button'; import { CardModule } from 'primeng/card'; import { InputTextModule } from 'primeng/inputtext'; -import { SocketService } from 'src/app/core/services/socket.service'; import { OverlayPanelModule } from 'primeng/overlaypanel'; import { CarouselModule } from 'primeng/carousel'; +import { DialogModule } from 'primeng/dialog'; + +// Service imports +import { CallService } from 'src/app/core/services/call.service'; +import { generateUUID4,isValidUUID } from "src/app/core/utils/helper"; + +const angulaModules:any[] = [ + CommonModule, FormsModule, RouterModule, ReactiveFormsModule +]; +const primeNgModules:any[] = [ + DialogModule, CarouselModule, CardModule, ButtonModule, InputTextModule, OverlayPanelModule +]; @Component({ selector: 'app-home', standalone: true, - imports: [CommonModule, FormsModule, RouterModule, CarouselModule, CardModule, ButtonModule, InputTextModule, OverlayPanelModule], + imports: [...angulaModules, ...primeNgModules], templateUrl: './home.component.html', styleUrls: ['./home.component.scss'] }) export class HomeComponent implements OnInit { + form!:FormGroup; today: number = Date.now(); email: any; - roomid: any; + gen_roomid: any; value: any; + name: any; + + copyIconShow:boolean = true; + copyCheckbox:boolean = false; + + //openvidu + visible:boolean = false; + loading: boolean = false; + //end openvidu + // services - socketService = inject(SocketService); + callService = inject(CallService); router = inject(Router); + private fb:FormBuilder = inject(FormBuilder); + products: any[] = [ { @@ -64,20 +91,45 @@ export class HomeComponent implements OnInit { } ]; - ngOnInit() { - // this.socketService.confirmationFromRoomJoin().subscribe((message: any) => { - // // this.messageList.push(message); - // console.log(message); - // if (message) { - // this.router.navigate(['/join/page']) - // } - // }); +ngOnInit() { + this.form = this.fb.group({ + userName: ['', Validators.required], + roomId: ['', [Validators.required, this.ValidateRoomId]] + }); +} +ValidateRoomId(control: AbstractControl) { + if (!isValidUUID(control.value)) { + return { invalidId: true }; } + return null; +} - joinRoom() { - // this.socketService.joinRoom({ email: this.email, roomid: this.roomid }); +getRoomId(){ + this.visible = true; + this.gen_roomid = generateUUID4(); +} - this.router.navigate(['join']) +GoToJoinRoom() { + const { userName,roomId } = this.form.getRawValue(); + this.router.navigate(['join',roomId],{ + queryParams:{userName} + }); +} + +async copyRoomId(){ + try { + await navigator.clipboard.writeText(this.gen_roomid); + this.copyIconShow = false; + setTimeout(() => { + this.copyCheckbox = true; + }, 10); + setTimeout(() => { + this.copyIconShow = true; + this.copyCheckbox = false; + }, 1500); + } catch (err) { + console.error('Failed to copy: ', err); } } +} diff --git a/src/app/modules/join-meeting/components/consumer/consumer.component.html b/src/app/modules/join-meeting/components/consumer/consumer.component.html index 576ba7e..f8f5f65 100644 --- a/src/app/modules/join-meeting/components/consumer/consumer.component.html +++ b/src/app/modules/join-meeting/components/consumer/consumer.component.html @@ -1 +1,3 @@ - + + + diff --git a/src/app/modules/join-meeting/components/consumer/consumer.component.ts b/src/app/modules/join-meeting/components/consumer/consumer.component.ts index 5f5e7b3..593dfe3 100644 --- a/src/app/modules/join-meeting/components/consumer/consumer.component.ts +++ b/src/app/modules/join-meeting/components/consumer/consumer.component.ts @@ -1,38 +1,86 @@ -import { AfterViewInit, Component, inject } from '@angular/core'; +import { AfterViewInit, Component, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SocketService } from 'src/app/core/services/socket.service'; - +import { ButtonModule } from 'primeng/button'; +import { PeerjsService } from 'src/app/core/services/peerjs.service'; @Component({ selector: 'app-consumer', standalone: true, - imports: [CommonModule], + imports: [CommonModule,ButtonModule], templateUrl: './consumer.component.html', styleUrls: ['./consumer.component.scss'] }) -export class ConsumerComponent implements AfterViewInit{ - +export class ConsumerComponent implements OnInit, AfterViewInit{ + vid:any; + remoteStream:any; socketService = inject(SocketService); - + peerService = inject(PeerjsService); + + ngOnInit(): void { + + } ngAfterViewInit(): void { - this.init(); - } + // this.init(); + // let stream = this.peerService.getMedia(); + // this.vid = stream; + // this.peerService.remoteCall.answer(stream); + // this.peerService.remoteCall.on('stream', (remoteStream:any)=> { + // // Show stream in some video/canvas element. + // this.remoteStream = remoteStream; + // }); - async init() { - const peer = this.createPeer(); - peer.addTransceiver('video', { direction: 'recvonly' }) + // this.init(); + } + + async init() { + try { + const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true}); + console.log(stream,'stream') + this.vid = stream; + + this.peerService.remoteCall.answer(stream); + this.peerService.remoteCall.on('stream', (remoteStream:any)=> { + // Show stream in some video/canvas element. + this.remoteStream = remoteStream; + }); + } catch (error) { + console.log(error); + } } + call(){ + // this.init() + } + +// async init() { +// const peer = this.createPeer(); +// peer.addTransceiver('video', { direction: 'recvonly' }) +// } + createPeer() { - const peer = new RTCPeerConnection(); + const peer = new RTCPeerConnection({ + iceServers: [ + { + urls: "stun:stun.stunprotocol.org" + }, + { + urls: 'turn:numb.viagenie.ca', + credential: 'muazkh', + username: 'webrtc@live.com' + }, + ] + }); peer.ontrack = this.handleTrack; peer.onnegotiationneeded = () => this.handleonnegotiationneeded(peer); return peer; } handleTrack = async (ev:any) => { + console.log(ev,'senderstream'); this.vid = ev.streams[0]; + console.log(this.vid,'vid') } async handleonnegotiationneeded(peer:any) { @@ -49,7 +97,7 @@ async handleonnegotiationneeded(peer:any) { // }, // body: JSON.stringify(payload), // })).json(); - + console.log(peer.localDescription,'localDescription'); this.socketService.consumer(payload).subscribe((response:any)=>{ console.log(response.sdp,'from server consumer'); @@ -58,5 +106,9 @@ async handleonnegotiationneeded(peer:any) { } +onLoadedMetadata(e:any){ + console.log(e,'metadata loaded'); +} + } diff --git a/src/app/modules/join-meeting/components/join/join.component.html b/src/app/modules/join-meeting/components/join/join.component.html index e3c99af..216e6c0 100644 --- a/src/app/modules/join-meeting/components/join/join.component.html +++ b/src/app/modules/join-meeting/components/join/join.component.html @@ -1,7 +1,174 @@ -
-

Room join

- - +
+
+
+
+ + videocam +
+

Meet People

+
+
+ +
+
+
+ + + + +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+ {{userName}} + more_vert +
+
+ +
+ {{'Do you want people to see and hear you in the meeting?'}} + {{'Do you want people to see you in the meeting?'}} + {{'Do you want people to hear you in the meeting?'}} + +
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+

Ready to join?

+

No one else is here

+
+
+ +
+
+ + present_to_all + +
+
+
+
+
+
+
+
- \ No newline at end of file + + + + +
+ {{data.icon_name}} +
{{ data.ngModel.label }}
+
+
+ +
+ {{data.icon_name}} +
{{ country.label }}
+
+
+
+
+ + + + svg +

click Allow

+ You can still turn off your microphone and camera anytime in the meeting. +
+ + + + + svg +
+

Meet is blocked from using your microphone and camera

+
    +
  1. Click the + + + + + page info icon in your browser's address bar +
  2. +
  3. + Turn on microphone and camera. +
  4. +
+
+
+ \ No newline at end of file diff --git a/src/app/modules/join-meeting/components/join/join.component.ts b/src/app/modules/join-meeting/components/join/join.component.ts index dd2a2e2..58a3954 100644 --- a/src/app/modules/join-meeting/components/join/join.component.ts +++ b/src/app/modules/join-meeting/components/join/join.component.ts @@ -1,81 +1,346 @@ -import { AfterViewInit, Component, OnInit, ViewChild, inject } from '@angular/core'; +//Angular imports +import { AfterViewInit, Component, HostListener, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { SocketService } from 'src/app/core/services/socket.service'; +import { ActivatedRoute, Router } from '@angular/router'; + +//rxjs imports +import { Subject, forkJoin, fromEvent, merge, of, timer } from 'rxjs'; +import { debounceTime, filter, switchMap, take, takeUntil, tap } from 'rxjs/operators'; + +// primeNg imports import { ButtonModule } from 'primeng/button'; +import { TooltipModule } from 'primeng/tooltip'; +import { DropdownModule } from 'primeng/dropdown'; +import { DialogModule } from 'primeng/dialog'; + +// Service imports +import { SocketService } from 'src/app/core/services/socket.service'; +import { PeerjsService } from 'src/app/core/services/peerjs.service'; +import { CallService } from 'src/app/core/services/call.service'; + +//delete below line +import { TokenModel, RecordingInfo, BroadcastingError, OpenViduAngularModule, BroadcastingService, ParticipantService, RecordingService } from 'openvidu-angular'; +import { FormsModule } from '@angular/forms'; +import { NavigatorService } from 'src/app/core/services/navigator.service'; @Component({ selector: 'app-join', standalone: true, - imports: [CommonModule,ButtonModule], + imports: [CommonModule,FormsModule,ButtonModule,TooltipModule,DropdownModule,DialogModule], templateUrl: './join.component.html', - styleUrls: ['./join.component.scss'] + styleUrls: ['./join.component.scss'], + host:{ + '(window:keydown.control.d)':'toggleMic($event)', + '(window:keydown.control.e)':'handleKeyPress($event)' + } }) export class JoinComponent implements OnInit,AfterViewInit{ // @ViewChild('video',{static:true}) video!:HTMLVideoElement; - vid:any; - srcObject:any; + private keyDownSubject = new Subject(); + remoteSocketId:any = null; socketService = inject(SocketService); + router = inject(Router); + activatedRouter = inject(ActivatedRoute); + callService = inject(CallService); + nS = inject(NavigatorService); + + + remoteVideo:any; + remoteAudio:any; + userName:string = ''; + isMicOn:boolean = true; + isVideoOn:boolean = true; + isVideoAllowed:boolean = false; + isAudioAllowed:boolean = false; + askPermissionModal:boolean = false; + permissionDeniedModal:boolean = false; + + videoDevices: MediaDeviceInfo[] = []; + audioDevices: MediaDeviceInfo[] = []; + speakers: MediaDeviceInfo[] = []; + + selectedCamera:any; + selectedMic:any; + selectedSpeaker: any; + + + countries:any[] = [ + { name: 'Australia', code: 'AU' }, + { name: 'Brazil', code: 'BR' }, + { name: 'China', code: 'CN' }, + // { name: 'Egypt', code: 'EG' }, + // { name: 'France', code: 'FR' }, + +]; + + async ngOnInit() { + + this.activatedRouter.queryParams.subscribe((qparams:any)=>{ + if (qparams && qparams.userName) { + this.userName = qparams.userName + } + }) + + this.keyDownSubject.pipe( + filter((event: any) => { + return !event.repeat && event.key === 'e'; + }), + switchMap((e:any) => { + return of(e); + }), + ).subscribe((event:any) => { + // Your logic for handling the last keypress here + console.log('Mateen',event); + this.toggleVideo(event); + }) + + this.socketService.connectEvent().subscribe((socket:any)=>{ + + console.log(socket,'connect in join comp') + }) + + // Call the function to check permissions + await this.checkVideoAudioPermissions(); + } + + + async checkVideoAudioPermissions() { + try { + const cameraPermissionStatus = await navigator.permissions.query({ name: 'camera' as PermissionName }); + const microphonePermissionStatus = await navigator.permissions.query({ name: 'microphone' as PermissionName}); + + console.log(cameraPermissionStatus) + console.log(microphonePermissionStatus) + + if (cameraPermissionStatus.state === 'granted' || microphonePermissionStatus.state === 'granted') { + + if (cameraPermissionStatus.state === 'granted') { + this.isVideoAllowed = true; + this.isVideoOn = true; + this.nS.getVideoDevices().subscribe({ + next:(videoDevices:any)=> { this.videoDevices = videoDevices; this.selectedCamera = this.videoDevices[0]; this.initVideo() }, + error:(error:any) => console.log(error) + }) + }else{ + this.isVideoAllowed = false; + this.isVideoOn = false; + this.askPermissionModal = true; + await this.askPermission(); + } + + if (microphonePermissionStatus.state === 'granted') { + this.isAudioAllowed = true; + this.isMicOn = true; + this.nS.getAudioDevices().subscribe({ + next:(audios:any)=> { this.audioDevices = audios; this.selectedMic = this.audioDevices[0]; this.initAudio()}, + error:(error:any) => console.log(error) + }) + this.nS.getSpeakers().subscribe({ + next:(speakers:any)=> { + this.speakers = speakers; + this.selectedSpeaker = this.speakers[0]; + }, + error:(error:any) => console.log(error) + }) + }else{ + this.isAudioAllowed = false; + this.isMicOn = false; + + this.askPermissionModal = true; + await this.askPermission(); + } - ngOnInit(): void { + // await this.initVideo(); + console.log('Camera and microphone permissions granted.'); + }else if(cameraPermissionStatus.state === 'prompt' || microphonePermissionStatus.state === 'prompt'){ + cameraPermissionStatus.state === 'prompt' ? this.isVideoAllowed = false : this.isVideoAllowed = true; + microphonePermissionStatus.state === 'prompt' ? this.isAudioAllowed = false : this.isAudioAllowed = true; + + this.askPermissionModal = true; + await this.askPermission(); + + if (this.isVideoAllowed && this.isAudioAllowed) { + forkJoin([this.nS.getVideoDevices(),this.nS.getAudioDevices(),this.nS.getSpeakers()]).subscribe({ + next:([videoDevices,audios,speakers]:any)=> { + this.videoDevices = videoDevices; + this.audioDevices = audios; + this.speakers = speakers; - // this.socketService.userJoined().subscribe((user: any) => { - // // this.messageList.push(message); - // console.log('userjoined',user); - // this.remoteSocketId = user; - // }) - this.init(); + this.selectedCamera = this.videoDevices[0]; + this.selectedMic = this.audioDevices[0]; + this.selectedSpeaker = this.speakers[0]; + this.initVideo(); + console.log(videoDevices,audios,speakers,'res') + }, + error:(error:any) => console.log(error) + }) + } + console.log('Camera and microphone permissions need to ask.'); + } + else { + this.isVideoAllowed = false; + this.isAudioAllowed = false; + this.isMicOn = false; + this.isVideoOn = false; + console.log('Camera and/or microphone permissions are denied.'); + } + } catch (error) { + console.error('Error checking permissions:', error); + } } + async askPermission(){ + try { + if (!this.isVideoAllowed && this.isAudioAllowed) { + let stream = await getMedia({video:true}); + this.isVideoAllowed = true; + this.askPermissionModal = false; + await this.stopTracks(stream); + }else if (!this.isAudioAllowed && this.isVideoAllowed) { + // await this.initAudio(); + let stream = await getMedia({audio:true}); + this.isAudioAllowed = true; + this.askPermissionModal = false; + await this.stopTracks(stream); + }else{ + // await this.initVideo(); + // await this.initAudio(); + let stream = await getMedia({video:true,audio:true}); + this.isVideoAllowed = true; + this.isAudioAllowed = true; + this.askPermissionModal = false; + await this.stopTracks(stream); + } + } catch (error:any) { + if (error instanceof DOMException) { + this.askPermissionModal = false; + if (error.message == 'Permission dismissed') { + if (!this.isVideoAllowed) { + this.isVideoOn = false; + } + if (!this.isAudioAllowed) { + this.isMicOn = false; + } + } + if (error.message == 'Permission denied') { + this.permissionDeniedModal = true; + } + } + console.log(error,error.code,error.name,error.message); + } + + async function getMedia(settings:any) { + + return await navigator.mediaDevices.getUserMedia(settings); + } + + } + call(){ - + // this.init(); } ngAfterViewInit(){ } + +async stopTracks(stream:any) { + stream.getTracks().forEach((track:any) => { + track.stop(); + }); +} - async init() { + async initVideo() { + try { - const stream = await navigator.mediaDevices.getUserMedia({ video: true }); - // let videoTag = document.getElementById('my-video').srcObject; - // this.srcObject = stream; - console.log(stream,'stream') - this.vid = stream; - const peer = this.createPeer(); - stream.getTracks().forEach(track => peer.addTrack(track, stream)); - } catch (error) { - console.log(error); + if (this.remoteVideo) { + await this.stopTracks(this.remoteVideo); + } + const deviceId = this.selectedCamera.deviceId; + const videoConstraints = { + audio: false, + video: { + width: { ideal: 1280 }, + height: { ideal: 720 }, + deviceId: deviceId, + aspectRatio: 1.777, + }, + }; + const stream = await navigator.mediaDevices.getUserMedia(videoConstraints); + + this.remoteVideo = stream; + console.log(this.remoteVideo,'remoteVideo'); + } catch (error:any) { + console.log(error.status,'uoyoyoyo'); + console.log(typeof error,'type'); } } - createPeer() { - const peer = new RTCPeerConnection(); - peer.onnegotiationneeded = () => this.handleonnegotiationneeded(peer); - return peer; +async initAudio() { + + try { + if (this.remoteAudio) { + await this.stopTracks(this.remoteAudio); + } + const deviceId = this.selectedMic.deviceId; + const audioConstraints = { + audio: true, + }; + const stream = await navigator.mediaDevices.getUserMedia(audioConstraints); + + this.remoteAudio = stream; + console.log(this.remoteAudio,'remoteAudio'); + } catch (error:any) { + console.log(error.status,'remoteAudio uoyoyoyo'); + console.log(typeof error,'remoteAudio type'); + } } -async handleonnegotiationneeded(peer:any) { - const offer = await peer.createOffer(); - await peer.setLocalDescription(offer); - const payload = { - sdp: peer.localDescription +toggleMic(e:any){ + e.preventDefault(); + if (this.isAudioAllowed) { + this.isMicOn = !this.isMicOn; + if (this.isMicOn) { + this.initAudio(); + } else { + if (this.remoteAudio) { + this.stopTracks(this.remoteAudio); + this.remoteAudio = null; + } } - // const response = await(await fetch('/broadcast', { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json', - // }, - // body: JSON.stringify(payload), - // })).json(); - console.log(peer.localDescription,'localDescription'); - this.socketService.broadcast(payload).subscribe((response:any)=>{ - - console.log(response.sdp,'from server'); - peer.setRemoteDescription(new RTCSessionDescription(response.sdp)); - }) + } else { + this.permissionDeniedModal = true; + } } +handleKeyPress(event: KeyboardEvent): void { + event.preventDefault(); + if (event.ctrlKey && event.key === 'e') { + this.keyDownSubject.next(event); + } + } + +async toggleVideo(e:any){ + e.preventDefault(); + if (this.isVideoAllowed) { + this.isVideoOn = !this.isVideoOn; + console.log(e) + if (this.isVideoOn) { + this.initVideo(); + } else { + if (this.remoteVideo) { + this.stopTracks(this.remoteVideo); + this.remoteVideo = null; + } + } + }else{ + this.permissionDeniedModal= true; + } +} + +joinNow(){ + this.socketService.sendMessage() } -console.log("mateen") \ No newline at end of file +} diff --git a/src/app/modules/join-meeting/joinmeet.routes.ts b/src/app/modules/join-meeting/joinmeet.routes.ts index 344433f..744788a 100644 --- a/src/app/modules/join-meeting/joinmeet.routes.ts +++ b/src/app/modules/join-meeting/joinmeet.routes.ts @@ -5,11 +5,11 @@ import { ConsumerComponent } from './components/consumer/consumer.component'; export const joinRoutes: Routes = [ { path:'', - redirectTo:'page', + redirectTo:':id', pathMatch:'full' }, { - path:'page', + path:':id', component:JoinComponent }, { diff --git a/src/assets/svgs/permissions_flow_allow.svg b/src/assets/svgs/permissions_flow_allow.svg new file mode 100644 index 0000000..263a38c --- /dev/null +++ b/src/assets/svgs/permissions_flow_allow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/permissions_flow_meet_blocked.svg b/src/assets/svgs/permissions_flow_meet_blocked.svg new file mode 100644 index 0000000..c4956fd --- /dev/null +++ b/src/assets/svgs/permissions_flow_meet_blocked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/styles.scss b/src/styles.scss index 7bc2d6a..8fbb7a5 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -14,4 +14,23 @@ body{ font-weight: 400; color: var(--text-color); -webkit-font-smoothing: antialiased; +} + +.active_shadow{ + box-shadow: 0px 3px 1px -2px rgba(45,212,191, 0.2), + 0px 2px 2px 0px rgba(45,212,191, 0.14), + 0px 1px 5px 0px rgba(45,212,191, 0.12); +} + +.deactive_shadow{ + box-shadow: 0px 3px 1px -2px rgba(239,68,68, 0.2), + 0px 2px 2px 0px rgba(239,68,68, 0.14), + 0px 1px 5px 0px rgba(239,68,68, 0.12); +} + +.denied_modal{ + + .p-dialog-header{ + padding: 8px; + } } \ No newline at end of file