diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.html
index 70f1f817..1356d0fc 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.html
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.html
@@ -63,9 +63,10 @@
}
-
-
-
+
+@if (showAdditionalButtonsOutside() && additionalButtonsPosition === 'beforeMenu') {
+
+}
@if (showMoreOptionsButtonDirect()) {
@@ -75,35 +76,17 @@
[matMenuTriggerFor]="settingsMenu"
[disabled]="isConnectionLost"
[class.mobile-btn]="isMobileView()"
- [matBadge]="hasActiveFeatures() && isMobileView() ? '!' : ''"
- matBadgeColor="accent"
- matBadgeSize="small"
- [matBadgeHidden]="!hasActiveFeatures() || !isMobileView()"
matTooltip="{{ 'TOOLBAR.MORE_OPTIONS' | translate }}"
>
more_vert
}
-
-
+
+@if (showAdditionalButtonsOutside() && additionalButtonsPosition === 'afterMenu') {
-
+}
@if (showLeaveButtonDirect()) {
-
+ @if (toolbarLeaveButtonTemplate) {
+
+ } @else {
+
+ }
}
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.scss
index 20d425ae..d3c16431 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.scss
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.scss
@@ -39,10 +39,9 @@
// Mobile responsive styles
&.mobile-btn {
- margin: 3px;
+ padding: 8px 0;
width: 36px;
height: 36px;
- text-align: justify;
mat-icon {
font-size: 20px;
@@ -59,7 +58,8 @@
background-color: var(--ov-accent-action-color) !important;
}
- #leave-btn {
+ #leave-btn,
+ ::ng-deep #leave-btn {
background-color: var(--ov-error-color) !important;
border-radius: var(--ov-leave-button-radius) !important;
width: 65px !important;
@@ -68,7 +68,6 @@
&.mobile-btn {
width: 56px !important;
height: 36px !important;
- margin: 3px;
text-align: center;
}
}
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.ts
index 4fa97038..1423e787 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.ts
@@ -4,231 +4,183 @@ import { BroadcastingStatus } from '../../../models/broadcasting.model';
import { ToolbarAdditionalButtonsPosition } from '../../../models/toolbar.model';
import { ViewportService } from '../../../services/viewport/viewport.service';
+/**
+ * @internal
+ */
@Component({
- selector: 'ov-toolbar-media-buttons',
- templateUrl: './toolbar-media-buttons.component.html',
- styleUrl: './toolbar-media-buttons.component.scss',
- standalone: false
+ selector: 'ov-toolbar-media-buttons',
+ templateUrl: './toolbar-media-buttons.component.html',
+ styleUrl: './toolbar-media-buttons.component.scss',
+ standalone: false
})
export class ToolbarMediaButtonsComponent {
+ // Camera related inputs
+ @Input() showCameraButton: boolean = true;
+ @Input() isCameraEnabled: boolean = true;
+ @Input() cameraMuteChanging: boolean = false;
- // Camera related inputs
- @Input() showCameraButton: boolean = true;
- @Input() isCameraEnabled: boolean = true;
- @Input() cameraMuteChanging: boolean = false;
+ // Microphone related inputs
+ @Input() showMicrophoneButton: boolean = true;
+ @Input() isMicrophoneEnabled: boolean = true;
+ @Input() microphoneMuteChanging: boolean = false;
- // Microphone related inputs
- @Input() showMicrophoneButton: boolean = true;
- @Input() isMicrophoneEnabled: boolean = true;
- @Input() microphoneMuteChanging: boolean = false;
+ // Screenshare related inputs
+ @Input() showScreenshareButton: boolean = true;
+ @Input() isScreenShareEnabled: boolean = false;
- // Screenshare related inputs
- @Input() showScreenshareButton: boolean = true;
- @Input() isScreenShareEnabled: boolean = false;
+ // Device availability inputs
+ @Input() hasVideoDevices: boolean = true;
+ @Input() hasAudioDevices: boolean = true;
- // Device availability inputs
- @Input() hasVideoDevices: boolean = true;
- @Input() hasAudioDevices: boolean = true;
+ // Connection state inputs
+ @Input() isConnectionLost: boolean = false;
- // Connection state inputs
- @Input() isConnectionLost: boolean = false;
+ // UI state inputs
+ @Input() isMinimal: boolean = false;
- // UI state inputs
- @Input() isMinimal: boolean = false;
+ // More options menu inputs
+ @Input() showMoreOptionsButton: boolean = true;
+ @Input() showFullscreenButton: boolean = true;
+ @Input() showRecordingButton: boolean = true;
+ @Input() showViewRecordingsButton: boolean = false;
+ @Input() showBroadcastingButton: boolean = true;
+ @Input() showBackgroundEffectsButton: boolean = true;
+ @Input() showCaptionsButton: boolean = true;
+ @Input() showSettingsButton: boolean = true;
- // More options menu inputs
- @Input() showMoreOptionsButton: boolean = true;
- @Input() showFullscreenButton: boolean = true;
- @Input() showRecordingButton: boolean = true;
- @Input() showViewRecordingsButton: boolean = false;
- @Input() showBroadcastingButton: boolean = true;
- @Input() showBackgroundEffectsButton: boolean = true;
- @Input() showCaptionsButton: boolean = true;
- @Input() showSettingsButton: boolean = true;
+ // Fullscreen state
+ @Input() isFullscreenActive: boolean = false;
- // Fullscreen state
- @Input() isFullscreenActive: boolean = false;
+ // Recording related inputs
+ @Input() recordingStatus: RecordingStatus = RecordingStatus.STOPPED;
+ @Input() hasRoomTracksPublished: boolean = false;
- // Recording related inputs
- @Input() recordingStatus: RecordingStatus = RecordingStatus.STOPPED;
- @Input() hasRoomTracksPublished: boolean = false;
+ // Broadcasting related inputs
+ @Input() broadcastingStatus: BroadcastingStatus = BroadcastingStatus.STOPPED;
- // Broadcasting related inputs
- @Input() broadcastingStatus: BroadcastingStatus = BroadcastingStatus.STOPPED;
+ // Captions
+ @Input() captionsEnabled: boolean = false;
- // Captions
- @Input() captionsEnabled: boolean = false;
+ // Leave button
+ @Input() showLeaveButton: boolean = true;
- // Leave button
- @Input() showLeaveButton: boolean = true;
+ // Additional buttons template
+ @Input() toolbarAdditionalButtonsTemplate: TemplateRef | null = null;
+ @Input() additionalButtonsPosition: ToolbarAdditionalButtonsPosition | undefined;
- // Additional buttons template
- @Input() toolbarAdditionalButtonsTemplate: TemplateRef | null = null;
- @Input() additionalButtonsPosition: ToolbarAdditionalButtonsPosition | undefined;
+ // Leave button template
+ @Input() toolbarLeaveButtonTemplate: TemplateRef | null = null;
- // Status enums for template usage
- _recordingStatus = RecordingStatus;
- _broadcastingStatus = BroadcastingStatus;
+ // Status enums for template usage
+ _recordingStatus = RecordingStatus;
+ _broadcastingStatus = BroadcastingStatus;
- // Viewport service for responsive behavior
- private viewportService = inject(ViewportService);
+ // Viewport service for responsive behavior
+ private viewportService = inject(ViewportService);
- // Computed properties for responsive button grouping
- readonly isMobileView = computed(() => this.viewportService.isMobile());
- readonly isTabletView = computed(() => this.viewportService.isTablet());
- readonly isDesktopView = computed(() => this.viewportService.isDesktop());
+ // Computed properties for responsive button grouping
+ readonly isMobileView = computed(() => this.viewportService.isMobile());
+ readonly isTabletView = computed(() => this.viewportService.isTablet());
+ readonly isDesktopView = computed(() => this.viewportService.isDesktop());
- // Essential buttons that always stay visible
- readonly showCameraButtonDirect = computed(() =>
- this.showCameraButton && !this.isMinimal
- );
+ // Essential buttons that always stay visible
+ readonly showCameraButtonDirect = computed(() => this.showCameraButton && !this.isMinimal);
- readonly showMicrophoneButtonDirect = computed(() =>
- this.showMicrophoneButton && !this.isMinimal
- );
+ readonly showMicrophoneButtonDirect = computed(() => this.showMicrophoneButton && !this.isMinimal);
- // Screenshare button - visible on tablet+ or when already active
- readonly showScreenshareButtonDirect = computed(() =>
- this.showScreenshareButton &&
- !this.isMinimal &&
- (!this.isMobileView() || this.isScreenShareEnabled)
- );
+ // Screenshare button - visible on tablet+ or when already active
+ readonly showScreenshareButtonDirect = computed(
+ () => this.showScreenshareButton && !this.isMinimal && (!this.isMobileView() || this.isScreenShareEnabled)
+ );
- // More options button - always visible when not minimal
- readonly showMoreOptionsButtonDirect = computed(() =>
- this.showMoreOptionsButton && !this.isMinimal
- );
+ // More options button - always visible when not minimal
+ readonly showMoreOptionsButtonDirect = computed(() => this.showMoreOptionsButton && !this.isMinimal);
- // Leave button - always visible
- readonly showLeaveButtonDirect = computed(() =>
- this.showLeaveButton
- );
+ // Leave button - always visible
+ readonly showLeaveButtonDirect = computed(() => this.showLeaveButton);
- // Buttons that should be moved to "More Options" on mobile
- readonly buttonsInMoreOptions = computed(() => {
- const buttons: Array<{
- key: string;
- show: boolean;
- label: string;
- icon: string;
- action: () => void;
- disabled?: boolean;
- active?: boolean;
- color?: string;
- }> = [];
+ // Check if there are active features that should show a badge on More Options
+ readonly hasActiveFeatures = computed(
+ () =>
+ this.isScreenShareEnabled ||
+ this.recordingStatus === this._recordingStatus.STARTED ||
+ this.broadcastingStatus === this._broadcastingStatus.STARTED
+ );
- const isMobile = this.isMobileView();
+ // Check if additional buttons should be shown outside (desktop/tablet) or inside More Options (mobile)
+ readonly showAdditionalButtonsOutside = computed(() => {
+ return !this.isMobileView() && this.toolbarAdditionalButtonsTemplate;
+ });
- // On mobile, screenshare goes to more options when not active
- if (isMobile && this.showScreenshareButton && !this.isScreenShareEnabled) {
- buttons.push({
- key: 'screenshare',
- show: true,
- label: 'TOOLBAR.ENABLE_SCREEN',
- icon: 'screen_share',
- action: () => this.onScreenShareToggle(),
- disabled: this.isConnectionLost
- });
- }
+ // Check if additional buttons should be shown inside More Options menu (mobile only)
+ readonly showAdditionalButtonsInsideMenu = computed(() => {
+ return this.isMobileView() && this.toolbarAdditionalButtonsTemplate;
+ });
- // Replace screenshare option when active on mobile
- if (isMobile && this.showScreenshareButton && this.isScreenShareEnabled) {
- buttons.push({
- key: 'screenshare-replace',
- show: true,
- label: 'STREAM.REPLACE_SCREEN',
- icon: 'picture_in_picture',
- action: () => this.onScreenTrackReplace(),
- disabled: this.isConnectionLost
- });
+ // Media button outputs
+ @Output() cameraToggled = new EventEmitter();
+ @Output() microphoneToggled = new EventEmitter();
+ @Output() screenShareToggled = new EventEmitter();
+ @Output() screenTrackReplaced = new EventEmitter();
- buttons.push({
- key: 'screenshare-stop',
- show: true,
- label: 'TOOLBAR.DISABLE_SCREEN',
- icon: 'stop_screen_share',
- action: () => this.onScreenShareToggle(),
- disabled: this.isConnectionLost,
- color: 'warn'
- });
- }
+ // More options menu outputs
+ @Output() fullscreenToggled = new EventEmitter();
+ @Output() recordingToggled = new EventEmitter();
+ @Output() viewRecordingsClicked = new EventEmitter();
+ @Output() broadcastingToggled = new EventEmitter();
+ @Output() backgroundEffectsToggled = new EventEmitter();
+ @Output() captionsToggled = new EventEmitter();
+ @Output() settingsToggled = new EventEmitter();
- return buttons;
- });
+ // Leave button output
+ @Output() leaveClicked = new EventEmitter();
- // Check if there are active features that should show a badge on More Options
- readonly hasActiveFeatures = computed(() =>
- this.isScreenShareEnabled ||
- this.recordingStatus === this._recordingStatus.STARTED ||
- this.broadcastingStatus === this._broadcastingStatus.STARTED
- );
+ // Event handler methods
+ onCameraToggle(): void {
+ this.cameraToggled.emit();
+ }
- // Media button outputs
- @Output() cameraToggled = new EventEmitter();
- @Output() microphoneToggled = new EventEmitter();
- @Output() screenShareToggled = new EventEmitter();
- @Output() screenTrackReplaced = new EventEmitter();
+ onMicrophoneToggle(): void {
+ this.microphoneToggled.emit();
+ }
- // More options menu outputs
- @Output() fullscreenToggled = new EventEmitter();
- @Output() recordingToggled = new EventEmitter();
- @Output() viewRecordingsClicked = new EventEmitter();
- @Output() broadcastingToggled = new EventEmitter();
- @Output() backgroundEffectsToggled = new EventEmitter();
- @Output() captionsToggled = new EventEmitter();
- @Output() settingsToggled = new EventEmitter();
+ onScreenShareToggle(): void {
+ this.screenShareToggled.emit();
+ }
- // Leave button output
- @Output() leaveClicked = new EventEmitter();
+ onScreenTrackReplace(): void {
+ this.screenTrackReplaced.emit();
+ }
- // Event handler methods
- onCameraToggle(): void {
- this.cameraToggled.emit();
- }
+ onFullscreenToggle(): void {
+ this.fullscreenToggled.emit();
+ }
- onMicrophoneToggle(): void {
- this.microphoneToggled.emit();
- }
+ onRecordingToggle(): void {
+ this.recordingToggled.emit();
+ }
- onScreenShareToggle(): void {
- this.screenShareToggled.emit();
- }
+ onViewRecordingsClick(): void {
+ this.viewRecordingsClicked.emit();
+ }
- onScreenTrackReplace(): void {
- this.screenTrackReplaced.emit();
- }
+ onBroadcastingToggle(): void {
+ this.broadcastingToggled.emit();
+ }
- onFullscreenToggle(): void {
- this.fullscreenToggled.emit();
- }
+ onBackgroundEffectsToggle(): void {
+ this.backgroundEffectsToggled.emit();
+ }
- onRecordingToggle(): void {
- this.recordingToggled.emit();
- }
-
- onViewRecordingsClick(): void {
- this.viewRecordingsClicked.emit();
- }
-
- onBroadcastingToggle(): void {
- this.broadcastingToggled.emit();
- }
-
- onBackgroundEffectsToggle(): void {
- this.backgroundEffectsToggled.emit();
- }
-
- onCaptionsToggle(): void {
- this.captionsToggled.emit();
- }
-
- onSettingsToggle(): void {
- this.settingsToggled.emit();
- }
-
- onLeaveClick(): void {
- this.leaveClicked.emit();
- }
+ onCaptionsToggle(): void {
+ this.captionsToggled.emit();
+ }
+ onSettingsToggle(): void {
+ this.settingsToggled.emit();
+ }
+ onLeaveClick(): void {
+ this.leaveClicked.emit();
+ }
}
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.html
index 8034f36a..8bdfa348 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.html
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.html
@@ -69,6 +69,7 @@
[showLeaveButton]="showLeaveButton"
[toolbarAdditionalButtonsTemplate]="toolbarAdditionalButtonsTemplate"
[additionalButtonsPosition]="additionalButtonsPosition"
+ [toolbarLeaveButtonTemplate]="toolbarLeaveButtonTemplate"
(cameraToggled)="toggleCamera()"
(microphoneToggled)="toggleMicrophone()"
(screenShareToggled)="toggleScreenShare()"
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.ts
index 4eeb85aa..8f16c5d0 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.ts
@@ -50,6 +50,7 @@ import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.servic
import { ParticipantLeftEvent, ParticipantLeftReason, ParticipantModel } from '../../models/participant.model';
import { Room, RoomEvent } from 'livekit-client';
import { ToolbarAdditionalButtonsPosition } from '../../models/toolbar.model';
+import { LeaveButtonDirective } from '../../directives/template/internals.directive';
/**
* The **ToolbarComponent** is hosted inside of the {@link VideoconferenceComponent}.
@@ -66,12 +67,18 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
/**
* @ignore
*/
- @ContentChild('toolbarAdditionalButtons', { read: TemplateRef }) toolbarAdditionalButtonsTemplate: TemplateRef;
+ @ContentChild('toolbarAdditionalButtons', { read: TemplateRef }) toolbarAdditionalButtonsTemplate: TemplateRef | undefined;
/**
* @ignore
*/
- @ContentChild('toolbarAdditionalPanelButtons', { read: TemplateRef }) toolbarAdditionalPanelButtonsTemplate: TemplateRef;
+ @ContentChild('toolbarLeaveButton', { read: TemplateRef }) toolbarLeaveButtonTemplate: TemplateRef | undefined;
+ /**
+ * @ignore
+ */
+ @ContentChild('toolbarAdditionalPanelButtons', { read: TemplateRef }) toolbarAdditionalPanelButtonsTemplate:
+ | TemplateRef
+ | undefined;
/**
* @ignore
@@ -84,6 +91,17 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
}
}
+ /**
+ * @ignore
+ */
+ @ContentChild(LeaveButtonDirective)
+ set externalLeaveButton(externalLeaveButton: LeaveButtonDirective) {
+ this._externalLeaveButton = externalLeaveButton;
+ if (externalLeaveButton) {
+ this.updateTemplatesAndMarkForCheck();
+ }
+ }
+
/**
* @ignore
*/
@@ -153,12 +171,12 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
/**
* @ignore
*/
- @ViewChild(MatMenuTrigger) public menuTrigger: MatMenuTrigger;
+ @ViewChild(MatMenuTrigger) public menuTrigger: MatMenuTrigger | undefined;
/**
* @ignore
*/
- room: Room;
+ room!: Room;
/**
* @ignore
*/
@@ -186,11 +204,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
/**
* @ignore
*/
- hasVideoDevices: boolean;
+ hasVideoDevices: boolean = true;
/**
* @ignore
*/
- hasAudioDevices: boolean;
+ hasAudioDevices: boolean = true;
/**
* @ignore
*/
@@ -305,7 +323,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
/**
* @ignore
*/
- additionalButtonsPosition: ToolbarAdditionalButtonsPosition;
+ additionalButtonsPosition: ToolbarAdditionalButtonsPosition = ToolbarAdditionalButtonsPosition.BEFORE_MENU;
/**
* @ignore
@@ -357,7 +375,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
/**
* @ignore
*/
- recordingTime: Date;
+ recordingTime: Date | undefined;
/**
* @internal
@@ -367,6 +385,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
// Store directive references for template setup
private _externalAdditionalButtons?: ToolbarAdditionalButtonsDirective;
+ private _externalLeaveButton?: LeaveButtonDirective;
private _externalAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective;
private log: ILogger;
@@ -416,7 +435,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
* @ignore
*/
@HostListener('window:resize', ['$event'])
- sizeChange(event) {
+ sizeChange(_: Event) {
if (this.currentWindowHeight >= window.innerHeight) {
// The user has exit the fullscreen mode
this.currentWindowHeight = window.innerHeight;
@@ -474,7 +493,8 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private setupTemplates(): void {
this.templateConfig = this.templateManagerService.setupToolbarTemplates(
this._externalAdditionalButtons,
- this._externalAdditionalPanelButtons
+ this._externalAdditionalPanelButtons,
+ this._externalLeaveButton
);
// Apply templates to component properties for backward compatibility
@@ -492,6 +512,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
if (this.templateConfig.toolbarAdditionalPanelButtonsTemplate) {
this.toolbarAdditionalPanelButtonsTemplate = this.templateConfig.toolbarAdditionalPanelButtonsTemplate;
}
+ if (this.templateConfig.toolbarLeaveButtonTemplate) {
+ this.toolbarLeaveButtonTemplate = this.templateConfig.toolbarLeaveButtonTemplate;
+ }
}
/**
@@ -518,11 +541,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.microphoneMuteChanging = false;
const isMicrophoneEnabled = this.participantService.isMyMicrophoneEnabled();
await this.participantService.setMicrophoneEnabled(!isMicrophoneEnabled);
- } catch (error) {
- this.log.e('There was an error toggling microphone:', error.code, error.message);
+ } catch (error: unknown) {
+ this.log.e('There was an error toggling microphone:', (error as any).code, (error as any).message);
this.actionService.openDialog(
this.translateService.translate('ERRORS.TOGGLE_MICROPHONE'),
- error?.error || error?.message || error
+ (error as any)?.error || (error as any)?.message || error
);
} finally {
this.microphoneMuteChanging = false;
@@ -541,8 +564,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
}
await this.participantService.setCameraEnabled(!isCameraEnabled);
} catch (error) {
- this.log.e('There was an error toggling camera:', error.code, error.message);
- this.actionService.openDialog(this.translateService.translate('ERRORS.TOGGLE_CAMERA'), error?.error || error?.message || error);
+ this.log.e('There was an error toggling camera:', (error as any).code, (error as any).message);
+ this.actionService.openDialog(
+ this.translateService.translate('ERRORS.TOGGLE_CAMERA'),
+ (error as any)?.error || (error as any)?.message || error
+ );
} finally {
this.cameraMuteChanging = false;
}
@@ -579,8 +605,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.onRoomDisconnected.emit();
}, false);
} catch (error) {
- this.log.e('There was an error disconnecting:', error.code, error.message);
- this.actionService.openDialog(this.translateService.translate('ERRORS.DISCONNECT'), error?.error || error?.message || error);
+ this.log.e('There was an error disconnecting:', (error as any).code, (error as any).message);
+ this.actionService.openDialog(
+ this.translateService.translate('ERRORS.DISCONNECT'),
+ (error as any)?.error || (error as any)?.message || error
+ );
}
}
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.html
index 89f616e5..93f69b0c 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.html
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.html
@@ -92,6 +92,10 @@
+
+
+
+
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts
index 90264f54..e704c932 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts
@@ -61,7 +61,8 @@ import { LangOption } from '../../models/lang.model';
import {
LayoutAdditionalElementsDirective,
ParticipantPanelAfterLocalParticipantDirective,
- PreJoinDirective
+ PreJoinDirective,
+ LeaveButtonDirective
} from '../../directives/template/internals.directive';
/**
@@ -124,6 +125,24 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
return this._externalToolbarAdditionalButtons;
}
+ private _externalToolbarLeaveButton?: LeaveButtonDirective;
+
+ /**
+ * @internal
+ */
+ @ContentChild(LeaveButtonDirective)
+ set externalToolbarLeaveButton(value: LeaveButtonDirective) {
+ this._externalToolbarLeaveButton = value;
+ this.setupTemplates();
+ }
+
+ /**
+ * @internal
+ */
+ get externalToolbarLeaveButton(): LeaveButtonDirective | undefined {
+ return this._externalToolbarLeaveButton;
+ }
+
private _externalToolbarAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective;
/**
@@ -397,6 +416,12 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
* @internal
*/
openviduAngularToolbarAdditionalButtonsTemplate: TemplateRef;
+
+ /**
+ * @internal
+ */
+ openviduAngularToolbarLeaveButtonTemplate: TemplateRef | undefined;
+
/**
* @internal
*/
@@ -744,6 +769,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
toolbar: this.externalToolbar,
toolbarAdditionalButtons: this.externalToolbarAdditionalButtons,
toolbarAdditionalPanelButtons: this.externalToolbarAdditionalPanelButtons,
+ toolbarLeaveButton: this.externalToolbarLeaveButton,
additionalPanels: this.externalAdditionalPanels,
panel: this.externalPanel,
chatPanel: this.externalChatPanel,
@@ -800,6 +826,9 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
if (this.templateConfig.toolbarAdditionalButtonsTemplate) {
assignIfChanged('openviduAngularToolbarAdditionalButtonsTemplate', this.templateConfig.toolbarAdditionalButtonsTemplate);
}
+ if (this.templateConfig.toolbarLeaveButtonTemplate) {
+ assignIfChanged('openviduAngularToolbarLeaveButtonTemplate', this.templateConfig.toolbarLeaveButtonTemplate);
+ }
if (this.templateConfig.toolbarAdditionalPanelButtonsTemplate) {
assignIfChanged(
'openviduAngularToolbarAdditionalPanelButtonsTemplate',
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/internals.directive.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/internals.directive.ts
index 74fabe38..14be054d 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/internals.directive.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/internals.directive.ts
@@ -222,6 +222,33 @@ export class ParticipantPanelAfterLocalParticipantDirective {
) {}
}
+/**
+ * The ***ovLeaveButton** directive allows you to replace the default leave button with a custom template.
+ * Use this directive to provide your own button, confirm dialogs, or any custom leave logic while keeping
+ * the internal leave flow intact.
+ *
+ * Usage example:
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+@Directive({
+ selector: '[ovToolbarLeaveButton]',
+ standalone: false
+})
+export class LeaveButtonDirective {
+ constructor(
+ public template: TemplateRef,
+ public container: ViewContainerRef
+ ) {}
+}
+
/**
* The ***ovLayoutAdditionalElements** directive allows you to inject custom HTML or Angular templates
* as additional layout elements within the videoconference UI.
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/openvidu-components-angular.directive.module.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/openvidu-components-angular.directive.module.ts
index ea096455..89f0aa90 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/openvidu-components-angular.directive.module.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/openvidu-components-angular.directive.module.ts
@@ -18,7 +18,8 @@ import {
LayoutAdditionalElementsDirective,
ParticipantPanelAfterLocalParticipantDirective,
ParticipantPanelParticipantBadgeDirective,
- PreJoinDirective
+ PreJoinDirective,
+ LeaveButtonDirective
} from './internals.directive';
@NgModule({
@@ -32,6 +33,7 @@ import {
StreamDirective,
ToolbarDirective,
ToolbarAdditionalButtonsDirective,
+ LeaveButtonDirective,
ToolbarAdditionalPanelButtonsDirective,
ParticipantPanelItemElementsDirective,
ActivitiesPanelDirective,
@@ -51,6 +53,7 @@ import {
StreamDirective,
ToolbarDirective,
ToolbarAdditionalButtonsDirective,
+ LeaveButtonDirective,
ToolbarAdditionalPanelButtonsDirective,
ParticipantPanelItemElementsDirective,
ActivitiesPanelDirective,
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/template/template-manager.service.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/template/template-manager.service.ts
index c24278af..15a3c91f 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/template/template-manager.service.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/template/template-manager.service.ts
@@ -18,7 +18,8 @@ import {
import {
PreJoinDirective,
ParticipantPanelAfterLocalParticipantDirective,
- LayoutAdditionalElementsDirective
+ LayoutAdditionalElementsDirective,
+ LeaveButtonDirective
} from '../../directives/template/internals.directive';
/**
@@ -29,6 +30,7 @@ export interface TemplateConfiguration {
toolbarTemplate: TemplateRef;
toolbarAdditionalButtonsTemplate?: TemplateRef;
toolbarAdditionalPanelButtonsTemplate?: TemplateRef;
+ toolbarLeaveButtonTemplate?: TemplateRef;
// Panel templates
panelTemplate: TemplateRef;
@@ -69,6 +71,7 @@ export interface PanelTemplateConfiguration {
export interface ToolbarTemplateConfiguration {
toolbarAdditionalButtonsTemplate?: TemplateRef;
toolbarAdditionalPanelButtonsTemplate?: TemplateRef;
+ toolbarLeaveButtonTemplate?: TemplateRef;
}
/**
@@ -110,6 +113,7 @@ export interface ExternalDirectives {
toolbar?: ToolbarDirective;
toolbarAdditionalButtons?: ToolbarAdditionalButtonsDirective;
toolbarAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective;
+ toolbarLeaveButton?: LeaveButtonDirective;
additionalPanels?: AdditionalPanelsDirective;
panel?: PanelDirective;
chatPanel?: ChatPanelDirective;
@@ -179,6 +183,11 @@ export class TemplateManagerService {
this.log.v('Setting EXTERNAL TOOLBAR ADDITIONAL BUTTONS');
}
+ if (externalDirectives.toolbarLeaveButton) {
+ config.toolbarLeaveButtonTemplate = externalDirectives.toolbarLeaveButton.template;
+ this.log.v('Setting EXTERNAL TOOLBAR LEAVE BUTTON');
+ }
+
if (externalDirectives.toolbarAdditionalPanelButtons) {
config.toolbarAdditionalPanelButtonsTemplate = externalDirectives.toolbarAdditionalPanelButtons.template;
this.log.v('Setting EXTERNAL TOOLBAR ADDITIONAL PANEL BUTTONS');
@@ -358,13 +367,15 @@ export class TemplateManagerService {
*/
setupToolbarTemplates(
externalAdditionalButtons?: ToolbarAdditionalButtonsDirective,
- externalAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective
+ externalAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective,
+ externalLeaveButton?: LeaveButtonDirective
): ToolbarTemplateConfiguration {
this.log.v('Setting up toolbar templates...');
return {
toolbarAdditionalButtonsTemplate: externalAdditionalButtons?.template,
- toolbarAdditionalPanelButtonsTemplate: externalAdditionalPanelButtons?.template
+ toolbarAdditionalPanelButtonsTemplate: externalAdditionalPanelButtons?.template,
+ toolbarLeaveButtonTemplate: externalLeaveButton?.template
};
}
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts
index 367565e4..d9b85c2f 100644
--- a/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts
+++ b/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts
@@ -19,7 +19,6 @@ export * from './lib/components/toolbar/toolbar-media-buttons/toolbar-media-butt
export * from './lib/components/videoconference/videoconference.component';
export * from './lib/config/openvidu-components-angular.config';
// Directives
-export * from './lib/directives/template/internals.directive';
export * from './lib/directives/api/activities-panel.directive';
export * from './lib/directives/api/admin.directive';
export * from './lib/directives/api/api.directive.module';
@@ -28,10 +27,12 @@ export * from './lib/directives/api/participant-panel-item.directive';
export * from './lib/directives/api/stream.directive';
export * from './lib/directives/api/toolbar.directive';
export * from './lib/directives/api/videoconference.directive';
+
+export * from './lib/directives/template/internals.directive';
export * from './lib/directives/template/openvidu-components-angular.directive';
export * from './lib/directives/template/openvidu-components-angular.directive.module';
-export * from './lib/models/broadcasting.model';
// Models
+export * from './lib/models/broadcasting.model';
export * from './lib/models/panel.model';
export * from './lib/models/participant.model';
export * from './lib/models/recording.model';