ov-components: add PreJoin directive to support custom pre-join templates in VideoconferenceComponent

master
Carlos Santos 2025-07-17 16:53:17 +02:00
parent 4dd007395f
commit c304c9c761
4 changed files with 237 additions and 10 deletions

View File

@ -5,6 +5,10 @@
</div>
<div [@inOutAnimation] id="pre-join-container" *ngIf="showPrejoin && !loading">
<ng-container *ngIf="openviduAngularPreJoinTemplate; else defaultPreJoin">
<ng-container *ngTemplateOutlet="openviduAngularPreJoinTemplate"></ng-container>
</ng-container>
<ng-template #defaultPreJoin>
<ov-pre-join
[error]="_tokenError"
(onReadyToJoin)="_onReadyToJoin()"
@ -14,6 +18,7 @@
(onAudioEnabledChanged)="onAudioEnabledChanged.emit($event)"
(onLangChanged)="onLangChanged.emit($event)"
></ov-pre-join>
</ng-template>
</div>
<div id="spinner" *ngIf="!loading && error">

View File

@ -10,6 +10,7 @@ import {
ParticipantPanelItemDirective,
ParticipantPanelItemElementsDirective,
ParticipantsPanelDirective,
PreJoinDirective,
StreamDirective,
ToolbarAdditionalButtonsDirective,
ToolbarAdditionalPanelButtonsDirective,
@ -114,6 +115,12 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
*/
@ContentChild(StreamDirective) externalStream: StreamDirective;
// *** PreJoin ***
/**
* @internal
*/
@ContentChild(PreJoinDirective) externalPreJoin: PreJoinDirective;
/**
* @internal
*/
@ -198,6 +205,10 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
* @internal
*/
openviduAngularStreamTemplate: TemplateRef<any>;
/**
* @internal
*/
openviduAngularPreJoinTemplate: TemplateRef<any>;
/**
* Provides event notifications that fire when the local participant is ready to join to the room.
@ -435,6 +446,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
this.setupToolbarTemplate();
this.setupPanelTemplate();
this.setupLayoutTemplate();
this.setupPreJoinTemplate();
}
/**
@ -576,6 +588,19 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
}
}
/**
* @internal
*/
private setupPreJoinTemplate(): void {
if (this.externalPreJoin) {
this.log.d('Setting EXTERNAL PREJOIN');
this.openviduAngularPreJoinTemplate = this.externalPreJoin.template;
} else {
this.log.d('Setting DEFAULT PREJOIN');
// Keep the default behavior - no template is set
}
}
/**
* @internal
*/

View File

@ -12,7 +12,8 @@ import {
ToolbarAdditionalPanelButtonsDirective,
AdditionalPanelsDirective,
ActivitiesPanelDirective,
BackgroundEffectsPanelDirective
BackgroundEffectsPanelDirective,
PreJoinDirective
} from './openvidu-components-angular.directive';
@NgModule({
@ -29,6 +30,7 @@ import {
ToolbarAdditionalPanelButtonsDirective,
ParticipantPanelItemElementsDirective,
ActivitiesPanelDirective,
PreJoinDirective,
// BackgroundEffectsPanelDirective
],
exports: [
@ -44,6 +46,7 @@ import {
ToolbarAdditionalPanelButtonsDirective,
ParticipantPanelItemElementsDirective,
ActivitiesPanelDirective,
PreJoinDirective,
// BackgroundEffectsPanelDirective
]
})

View File

@ -1814,3 +1814,197 @@ export class StreamDirective {
public container: ViewContainerRef
) {}
}
/**
* The ***ovPreJoin** directive empowers you to substitute the default pre-join component template with a custom one.
* This directive allows you to create a completely custom pre-join experience while maintaining the core functionality.
*
* In the example below, we demonstrate how to replace the pre-join template with a custom one that includes
* device selection and a custom join button.
*
* <!--ovPreJoin-start-tutorial-->
* ```typescript
* import { HttpClient } from '@angular/common/http';
* import { Component } from '@angular/core';
* import { lastValueFrom } from 'rxjs';
* import { FormsModule } from '@angular/forms';
*
* import {
* DeviceService,
* ParticipantService,
* OpenViduComponentsModule,
* } from 'openvidu-components-angular';
*
* @Component({
* selector: 'app-root',
* template: `
* <ov-videoconference
* [token]="token"
* [livekitUrl]="LIVEKIT_URL"
* (onTokenRequested)="onTokenRequested($event)"
* (onReadyToJoin)="onReadyToJoin()"
* >
* <!-- Custom Pre-Join Component -->
* <div *ovPreJoin class="custom-prejoin">
* <h2>Join Meeting</h2>
* <div class="prejoin-form">
* <input
* type="text"
* placeholder="Enter your name"
* [(ngModel)]="participantName"
* class="name-input"
* />
* <button
* (click)="joinMeeting()"
* [disabled]="!participantName"
* class="join-button"
* >
* Join Meeting
* </button>
* </div>
* </div>
* </ov-videoconference>
* `,
* styles: `
* .custom-prejoin {
* display: flex;
* flex-direction: column;
* align-items: center;
* justify-content: center;
* height: 100vh;
* background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
* color: white;
* }
* .prejoin-form {
* display: flex;
* flex-direction: column;
* gap: 20px;
* align-items: center;
* }
* .name-input {
* padding: 12px;
* border: none;
* border-radius: 8px;
* font-size: 16px;
* min-width: 250px;
* }
* .join-button {
* padding: 12px 24px;
* background: #4CAF50;
* color: white;
* border: none;
* border-radius: 8px;
* font-size: 16px;
* cursor: pointer;
* transition: background 0.3s;
* }
* .join-button:hover:not(:disabled) {
* background: #45a049;
* }
* .join-button:disabled {
* background: #cccccc;
* cursor: not-allowed;
* }
* `,
* standalone: true,
* imports: [OpenViduComponentsModule, FormsModule],
* })
* export class AppComponent {
* // For local development, leave these variables empty
* // For production, configure them with correct URLs depending on your deployment
* APPLICATION_SERVER_URL = '';
* LIVEKIT_URL = '';
*
* // Define the name of the room and initialize the token variable
* roomName = 'custom-prejoin';
* token!: string;
* participantName: string = '';
*
* constructor(
* private httpClient: HttpClient,
* private deviceService: DeviceService,
* private participantService: ParticipantService
* ) {
* this.configureUrls();
* }
*
* private configureUrls() {
* // If APPLICATION_SERVER_URL is not configured, use default value from local development
* if (!this.APPLICATION_SERVER_URL) {
* if (window.location.hostname === 'localhost') {
* this.APPLICATION_SERVER_URL = 'http://localhost:6080/';
* } else {
* this.APPLICATION_SERVER_URL =
* 'https://' + window.location.hostname + ':6443/';
* }
* }
*
* // If LIVEKIT_URL is not configured, use default value from local development
* if (!this.LIVEKIT_URL) {
* if (window.location.hostname === 'localhost') {
* this.LIVEKIT_URL = 'ws://localhost:7880/';
* } else {
* this.LIVEKIT_URL = 'wss://' + window.location.hostname + ':7443/';
* }
* }
* }
*
* // Function to request a token when a participant joins the room
* async onTokenRequested(participantName: string) {
* const { token } = await this.getToken(this.roomName, participantName);
* this.token = token;
* }
*
* // Function called when ready to join
* onReadyToJoin() {
* console.log('Ready to join the meeting');
* }
*
* // Function to join the meeting
* async joinMeeting() {
* if (this.participantName.trim()) {
* // Request token with the participant name
* await this.onTokenRequested(this.participantName);
* }
* }
*
* // Function to get a token from the server
* getToken(roomName: string, participantName: string): Promise<any> {
* try {
* // Send a POST request to the server to obtain a token
* return lastValueFrom(
* this.httpClient.post<any>(this.APPLICATION_SERVER_URL + 'token', {
* roomName,
* participantName,
* })
* );
* } catch (error: any) {
* // Handle errors, e.g., if the server is not reachable
* if (error.status === 404) {
* throw {
* status: error.status,
* message:
* 'Cannot connect with the backend. ' + error.url + ' not found',
* };
* }
* throw error;
* }
* }
* }
*
* ```
* <!--ovPreJoin-end-tutorial-->
*
* For a detailed tutorial on customizing the pre-join component, please visit [this link](https://openvidu.io/latest/docs/tutorials/angular-components/openvidu-custom-prejoin/).
*/
@Directive({
selector: '[ovPreJoin]',
standalone: false
})
export class PreJoinDirective {
constructor(
public template: TemplateRef<any>,
public container: ViewContainerRef
) {}
}