Improve basic Angular tutorial
This commit is contained in:
parent
186d6d5806
commit
031acf3db1
@ -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
|
||||
```
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,11 +31,17 @@
|
||||
[participantIdentity]="roomForm.value.participantName!"
|
||||
[isLocal]="true"
|
||||
></video-component>
|
||||
} @for (publication of remoteTrackPublications.keys(); track publication.trackSid) { @if (publication.kind === "video") {
|
||||
<video-component [track]="publication.videoTrack!" [participantIdentity]="getParticipantIdentity(publication)"></video-component>
|
||||
} @else {
|
||||
<audio-component [track]="castToRemoteAudioTrack(publication.audioTrack!)" hidden></audio-component>
|
||||
} }
|
||||
}
|
||||
@for (remoteTrack of remoteTracksMap.values(); track remoteTrack.trackPublication.trackSid) {
|
||||
@if (remoteTrack.trackPublication.kind === 'video') {
|
||||
<video-component
|
||||
[track]="remoteTrack.trackPublication.videoTrack!"
|
||||
[participantIdentity]="remoteTrack.participantIdentity"
|
||||
></video-component>
|
||||
} @else {
|
||||
<audio-component [track]="remoteTrack.trackPublication.audioTrack!" hidden></audio-component>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -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<RemoteTrackPublication, string> = new Map();
|
||||
remoteTracksMap: Map<string, TrackInfo> = 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1 +1 @@
|
||||
<audio #audioElement></audio>
|
||||
<audio #audioElement [id]="track?.sid"></audio>
|
||||
|
||||
@ -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<HTMLAudioElement>;
|
||||
|
||||
_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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<div id="camera-{{ participantIdentity }}" class="video-container">
|
||||
<div class="participant-data">
|
||||
<p>{{ participantIdentity }} @if (isLocal) { (You)}</p>
|
||||
<p>{{ participantIdentity + (isLocal ? " (You)" : "") }}</p>
|
||||
</div>
|
||||
<video #videoElement></video>
|
||||
<video #videoElement [id]="track?.sid"></video>
|
||||
</div>
|
||||
|
||||
@ -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<HTMLVideoElement>;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a href="/" title="Home"><h1>Basic JavaScript</h1></a>
|
||||
<a href="/" title="Home"><h1>Basic Angular</h1></a>
|
||||
<div id="links">
|
||||
<a
|
||||
href="https://github.com/OpenVidu/openvidu-livekit-tutorials/tree/master/basic/frontend/javascript"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user