diff --git a/basic/frontend/angular/README.md b/basic/frontend/angular/README.md index 24395180..6892e713 100644 --- a/basic/frontend/angular/README.md +++ b/basic/frontend/angular/README.md @@ -1,27 +1,30 @@ -# Angular +# Basic Angular -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.6. +Basic client application built with Angular. It internally uses [livekit-client-sdk-js](https://docs.livekit.io/client-sdk-js/). -## Development server +For further information, check the [tutorial documentation](https://livekit-tutorials.openvidu.io/basic/client/angular). -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. +## Prerequisites -## Code scaffolding +- [Node](https://nodejs.org/en/download) -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. +## Run -## Build +1. Download repository -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. +```bash +git clone https://github.com/OpenVidu/openvidu-livekit-tutorials.git +cd openvidu-livekit-tutorials/client/angular +``` -## Running unit tests +2. Install dependencies -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +```bash +npm install +``` -## Running end-to-end tests +3. Run the application -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. - -## Further help - -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. +```bash +npm start +``` diff --git a/basic/frontend/angular/src/app/app.component.css b/basic/frontend/angular/src/app/app.component.css index 07ab69ef..92656a4d 100644 --- a/basic/frontend/angular/src/app/app.component.css +++ b/basic/frontend/angular/src/app/app.component.css @@ -124,6 +124,11 @@ #join-dialog h2 { font-size: 40px; } + + #layout-container { + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + } + } @media screen and (max-width: 480px) { @@ -135,4 +140,8 @@ #join-dialog h2 { font-size: 30px; } + + #layout-container { + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + } } diff --git a/basic/frontend/angular/src/app/app.component.html b/basic/frontend/angular/src/app/app.component.html index 2a193adf..085a4540 100644 --- a/basic/frontend/angular/src/app/app.component.html +++ b/basic/frontend/angular/src/app/app.component.html @@ -31,11 +31,17 @@ [participantIdentity]="roomForm.value.participantName!" [isLocal]="true" > - } @for (publication of remoteTrackPublications.keys(); track publication.trackSid) { @if (publication.kind === "video") { - - } @else { - - } } + } + @for (remoteTrack of remoteTracksMap.values(); track remoteTrack.trackPublication.trackSid) { + @if (remoteTrack.trackPublication.kind === 'video') { + + } @else { + + } + } } diff --git a/basic/frontend/angular/src/app/app.component.ts b/basic/frontend/angular/src/app/app.component.ts index c1e15360..51d120d4 100644 --- a/basic/frontend/angular/src/app/app.component.ts +++ b/basic/frontend/angular/src/app/app.component.ts @@ -1,9 +1,7 @@ -import { Component } from '@angular/core'; +import { Component, HostListener, OnDestroy } from '@angular/core'; import { ReactiveFormsModule, FormControl, FormGroup, Validators } from '@angular/forms'; import { - LocalAudioTrack, LocalVideoTrack, - RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, @@ -15,6 +13,11 @@ import { AudioComponent } from './audio/audio.component'; import { HttpClient } from '@angular/common/http'; import { lastValueFrom } from 'rxjs'; +type TrackInfo = { + trackPublication: RemoteTrackPublication; + participantIdentity: string; +}; + // For local development, leave these variables empty // For production, configure them with correct URLs depending on your deployment var APPLICATION_SERVER_URL = ''; @@ -27,7 +30,7 @@ var LIVEKIT_URL = ''; templateUrl: './app.component.html', styleUrl: './app.component.css', }) -export class AppComponent { +export class AppComponent implements OnDestroy { roomForm = new FormGroup({ roomName: new FormControl('Test Room', Validators.required), participantName: new FormControl('Participant' + Math.floor(Math.random() * 100), Validators.required), @@ -35,7 +38,7 @@ export class AppComponent { room?: Room; localTrack?: LocalVideoTrack; - remoteTrackPublications: Map = new Map(); + remoteTracksMap: Map = new Map(); constructor(private httpClient: HttpClient) { this.configureUrls(); @@ -70,7 +73,10 @@ export class AppComponent { this.room.on( RoomEvent.TrackSubscribed, (_track: RemoteTrack, publication: RemoteTrackPublication, participant: RemoteParticipant) => { - this.remoteTrackPublications.set(publication, participant.identity); + this.remoteTracksMap.set(publication.trackSid, { + trackPublication: publication, + participantIdentity: participant.identity, + }); } ); @@ -78,7 +84,7 @@ export class AppComponent { this.room.on( RoomEvent.TrackUnsubscribed, (_track: RemoteTrack, publication: RemoteTrackPublication, _participant: RemoteParticipant) => { - this.remoteTrackPublications.delete(publication); + this.remoteTracksMap.delete(publication.trackSid); } ); @@ -106,15 +112,13 @@ export class AppComponent { // Empty all properties... delete this.room; delete this.localTrack; - this.remoteTrackPublications.clear(); + this.remoteTracksMap.clear(); } - getParticipantIdentity(publication: RemoteTrackPublication): string { - return this.remoteTrackPublications.get(publication) || ''; - } - - castToRemoteAudioTrack(audioTrack: LocalAudioTrack | RemoteAudioTrack): RemoteAudioTrack { - return audioTrack as RemoteAudioTrack; + @HostListener('window:beforeunload') + async ngOnDestroy() { + // On window closed or component destroyed, leave the room + await this.leaveRoom(); } /** diff --git a/basic/frontend/angular/src/app/audio/audio.component.html b/basic/frontend/angular/src/app/audio/audio.component.html index 505310ab..77f3f569 100644 --- a/basic/frontend/angular/src/app/audio/audio.component.html +++ b/basic/frontend/angular/src/app/audio/audio.component.html @@ -1 +1 @@ - + diff --git a/basic/frontend/angular/src/app/audio/audio.component.ts b/basic/frontend/angular/src/app/audio/audio.component.ts index 475c687b..354265f6 100644 --- a/basic/frontend/angular/src/app/audio/audio.component.ts +++ b/basic/frontend/angular/src/app/audio/audio.component.ts @@ -1,5 +1,5 @@ -import { Component, ElementRef, Input, ViewChild } from '@angular/core'; -import { RemoteAudioTrack } from 'livekit-client'; +import { AfterViewInit, Component, ElementRef, Input, OnDestroy, ViewChild } from '@angular/core'; +import { LocalAudioTrack, RemoteAudioTrack } from 'livekit-client'; @Component({ selector: 'audio-component', @@ -8,10 +8,23 @@ import { RemoteAudioTrack } from 'livekit-client'; templateUrl: './audio.component.html', styleUrl: './audio.component.css', }) -export class AudioComponent { +export class AudioComponent implements AfterViewInit, OnDestroy { @ViewChild('audioElement') audioElement?: ElementRef; - _track?: RemoteAudioTrack; + private _track?: RemoteAudioTrack | LocalAudioTrack; + + @Input() + set track(track: RemoteAudioTrack | LocalAudioTrack) { + this._track = track; + + if (this.audioElement) { + this._track.attach(this.audioElement.nativeElement); + } + } + + get track(): RemoteAudioTrack | LocalAudioTrack | undefined { + return this._track; + } ngAfterViewInit() { if (this._track && this.audioElement) { @@ -19,12 +32,7 @@ export class AudioComponent { } } - @Input() - set track(track: RemoteAudioTrack) { - this._track = track; - - if (this.audioElement) { - this._track.attach(this.audioElement.nativeElement); - } + ngOnDestroy() { + this._track?.detach(); } } diff --git a/basic/frontend/angular/src/app/video/video.component.css b/basic/frontend/angular/src/app/video/video.component.css index 1f7aede4..2eefc851 100644 --- a/basic/frontend/angular/src/app/video/video.component.css +++ b/basic/frontend/angular/src/app/video/video.component.css @@ -27,17 +27,7 @@ } /* Media Queries */ -@media screen and (max-width: 768px) { - #layout-container { - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - } -} - @media screen and (max-width: 480px) { - #layout-container { - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - } - .video-container { aspect-ratio: 9/16; } diff --git a/basic/frontend/angular/src/app/video/video.component.html b/basic/frontend/angular/src/app/video/video.component.html index 781248e6..0c0c586b 100644 --- a/basic/frontend/angular/src/app/video/video.component.html +++ b/basic/frontend/angular/src/app/video/video.component.html @@ -1,6 +1,6 @@
-

{{ participantIdentity }} @if (isLocal) { (You)}

+

{{ participantIdentity + (isLocal ? " (You)" : "") }}

- +
diff --git a/basic/frontend/angular/src/app/video/video.component.ts b/basic/frontend/angular/src/app/video/video.component.ts index 53dfe096..d9001336 100644 --- a/basic/frontend/angular/src/app/video/video.component.ts +++ b/basic/frontend/angular/src/app/video/video.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, Input, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, Input, OnDestroy, ViewChild } from '@angular/core'; import { LocalVideoTrack, RemoteVideoTrack } from 'livekit-client'; @Component({ @@ -8,19 +8,13 @@ import { LocalVideoTrack, RemoteVideoTrack } from 'livekit-client'; templateUrl: './video.component.html', styleUrl: './video.component.css', }) -export class VideoComponent { +export class VideoComponent implements AfterViewInit, OnDestroy { @ViewChild('videoElement') videoElement?: ElementRef; private _track?: LocalVideoTrack | RemoteVideoTrack; @Input() participantIdentity?: string; @Input() isLocal = false; - ngAfterViewInit() { - if (this._track && this.videoElement) { - this._track.attach(this.videoElement.nativeElement); - } - } - @Input() set track(track: LocalVideoTrack | RemoteVideoTrack) { this._track = track; @@ -29,4 +23,18 @@ export class VideoComponent { this._track.attach(this.videoElement.nativeElement); } } + + get track(): LocalVideoTrack | RemoteVideoTrack | undefined { + return this._track; + } + + ngAfterViewInit() { + if (this._track && this.videoElement) { + this._track.attach(this.videoElement.nativeElement); + } + } + + ngOnDestroy() { + this._track?.detach(); + } } diff --git a/basic/frontend/angular/src/index.html b/basic/frontend/angular/src/index.html index e5279437..a2dbc73f 100644 --- a/basic/frontend/angular/src/index.html +++ b/basic/frontend/angular/src/index.html @@ -31,7 +31,7 @@
-

Basic JavaScript

+

Basic Angular