From f92ee9b886e68c15eeccd2b491c7eef2e38ef2cb Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Thu, 21 Aug 2025 11:47:44 +0200 Subject: [PATCH] ov-components: Improve prejoin card styles --- .../background-effects-panel.component.scss | 7 +- .../pre-join/pre-join.component.html | 11 ++- .../pre-join/pre-join.component.scss | 31 +++++--- .../components/pre-join/pre-join.component.ts | 77 +++++++++++++++++-- .../videoconference.component.ts | 6 +- .../lib/services/openvidu/openvidu.service.ts | 29 ++++--- 6 files changed, 115 insertions(+), 46 deletions(-) diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.scss index f29a2026..594b78ba 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/background-effects-panel/background-effects-panel.component.scss @@ -1,14 +1,11 @@ .prejoin-mode { - margin-top: 0px; + margin: 0 10px 0px 10px; max-height: 100%; min-height: 100%; - - .effects-container { - padding: 0px; - } } .background-title { color: var(--ov-text-surface-color); + margin: 10px 0; } .effects-container { display: block !important; diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.html index a25bd53c..8fb7fcc5 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.html @@ -13,13 +13,13 @@ } @else { - +
-
+
- auto_fix_high + background_replace
@@ -73,7 +74,9 @@
@if (showBackgroundPanel) { - +
+ +
} @else {
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.scss index 46ebf578..ef1368a4 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.scss @@ -85,6 +85,7 @@ .prejoin-main { width: 100%; max-width: 520px; + max-height: 544px; background: var(--ov-surface-color, #ffffff); border-radius: var(--ov-surface-radius); overflow: hidden; @@ -93,18 +94,16 @@ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); } - // Video Preview Section (moved to top with no padding) + // Video Preview Section .video-preview-section { padding: 0; - .video-preview-container { position: relative; width: 100%; - aspect-ratio: 4/3; // Changed from 16/9 to 4/3 for taller video - border-radius: var(--ov-surface-radius) var(--ov-surface-radius) 0 0; // Only top corners rounded + aspect-ratio: 4/3; + border-radius: var(--ov-surface-radius) var(--ov-surface-radius) 0 0; overflow: hidden; background: #000; - .video-frame { width: 100%; height: 100%; @@ -156,16 +155,23 @@ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); color: #333333; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - - &:hover { - color: rgba(255, 255, 255, 1); - } + transform: translateZ(0); &:active { transform: translateY(-1px); transition: all 0.15s ease; } + &.mat-mdc-button-disabled { + background: rgba(255, 255, 255, 0.137); + color: rgba(233, 233, 233, 0.5); + cursor: not-allowed; + + &:hover { + transform: none; + } + } + mat-icon { font-size: 22px; width: 22px; @@ -183,6 +189,11 @@ } } + .vb-container { + height: fit-content; + overflow: hidden; + } + // Configuration Section .configuration-section { padding: 24px 24px 24px; // Added top padding since video has no padding @@ -393,8 +404,8 @@ transform: translateY(0); } } - .prejoin-main { animation: fadeIn 0.3s ease-out; + transform: translateZ(0); } } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.ts index cc5bc932..e837e887 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/pre-join/pre-join.component.ts @@ -9,6 +9,7 @@ import { OnInit, Output } from '@angular/core'; +import { animate, state, style, transition, trigger } from '@angular/animations'; import { filter, Subject, takeUntil, tap } from 'rxjs'; import { ILogger } from '../../models/logger.model'; import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service'; @@ -28,7 +29,47 @@ import { LangOption } from '../../models/lang.model'; templateUrl: './pre-join.component.html', styleUrls: ['./pre-join.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, - standalone: false + standalone: false, + animations: [ + trigger('containerResize', [ + state( + 'normal', + style({ + height: '*' + }) + ), + state( + 'compact', + style({ + height: '28vh' + }) + ), + transition('normal => compact', [animate('250ms cubic-bezier(0.25, 0.8, 0.25, 1)')]), + transition('compact => normal', [animate('350ms cubic-bezier(0.25, 0.8, 0.25, 1)')]) + ]), + trigger('slideInOut', [ + transition(':enter', [ + style({ + opacity: 0 + }), + animate( + '300ms cubic-bezier(0.34, 1.56, 0.64, 1)', + style({ + opacity: 1 + }) + ) + ]), + transition(':leave', [ + animate( + '200ms cubic-bezier(0.25, 0.46, 0.45, 0.94)', + style({ + opacity: 0, + transform: 'translateY(-10px)' + }) + ) + ]) + ]) + ] }) export class PreJoinComponent implements OnInit, OnDestroy { @Input() set error(error: { name: string; message: string } | undefined) { @@ -61,6 +102,7 @@ export class PreJoinComponent implements OnInit, OnDestroy { videoTrack: LocalTrack | undefined; audioTrack: LocalTrack | undefined; + isVideoEnabled: boolean = false; private tracks: LocalTrack[]; private log: ILogger; private destroy$ = new Subject(); @@ -195,12 +237,16 @@ export class PreJoinComponent implements OnInit, OnDestroy { } async videoEnabledChanged(enabled: boolean) { - if (enabled && !this.videoTrack) { + this.isVideoEnabled = enabled; + if (!enabled) { + this.closeBackgroundPanel(); + } else if (!this.videoTrack) { const newVideoTrack = await this.openviduService.createLocalTracks(true, false); this.videoTrack = newVideoTrack[0]; this.tracks.push(this.videoTrack); this.openviduService.setLocalTracks(this.tracks); } + this.onVideoEnabledChanged.emit(enabled); } @@ -215,19 +261,32 @@ export class PreJoinComponent implements OnInit, OnDestroy { } /** - * Toggle virtual background panel visibility + * Toggle virtual background panel visibility with smooth animation */ toggleBackgroundPanel() { - this.showBackgroundPanel = !this.showBackgroundPanel; - this.changeDetector.markForCheck(); + // Add a small delay to ensure smooth transition + if (!this.showBackgroundPanel) { + // Opening panel + this.showBackgroundPanel = true; + this.changeDetector.markForCheck(); + } else { + // Closing panel - add slight delay for smooth animation + setTimeout(() => { + this.showBackgroundPanel = false; + this.changeDetector.markForCheck(); + }, 50); + } } /** - * Close virtual background panel + * Close virtual background panel with smooth animation */ closeBackgroundPanel() { - this.showBackgroundPanel = false; - this.changeDetector.markForCheck(); + // Add animation delay for smooth closing + setTimeout(() => { + this.showBackgroundPanel = false; + this.changeDetector.markForCheck(); + }, 100); } /** @@ -249,6 +308,8 @@ export class PreJoinComponent implements OnInit, OnDestroy { this.openviduService.setLocalTracks(this.tracks); this.videoTrack = this.tracks.find((track) => track.kind === 'video'); this.audioTrack = this.tracks.find((track) => track.kind === 'audio'); + this.isVideoEnabled = this.openviduService.isVideoTrackEnabled(); + return; // Success, exit retry loop } catch (error) { this.log.w(`Device initialization attempt ${attempt} failed:`, error); 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 75eb3419..421e7107 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 @@ -87,8 +87,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { // Constants private static readonly PARTICIPANT_NAME_TIMEOUT_MS = 1000; private static readonly ANIMATION_DURATION_MS = 300; - private static readonly MATERIAL_ICONS_URL = - 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined'; + private static readonly MATERIAL_ICONS_URL = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined'; private static readonly MATERIAL_ICONS_SELECTOR = 'link[href*="Material+Symbols+Outlined"]'; private static readonly SPINNER_DIAMETER = 50; // *** Toolbar *** @@ -690,6 +689,8 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { ) { this.log = this.loggerSrv.get('VideoconferenceComponent'); + this.addMaterialIconsIfNeeded(); + // Initialize state this.updateComponentState({ state: VideoconferenceState.INITIALIZING, @@ -713,7 +714,6 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { * @internal */ ngAfterViewInit() { - this.addMaterialIconsIfNeeded(); this.setupTemplates(); this.deviceSrv.initializeDevices().then(() => { this.updateComponentState({ diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts index b27e0543..2c82f26b 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts @@ -238,23 +238,20 @@ export class OpenViduService { videoDeviceId: string | boolean | undefined = undefined, audioDeviceId: string | boolean | undefined = undefined ): Promise { - // If video and audio device IDs are not provided, check if they are enabled and use the default devices - if (videoDeviceId === undefined) videoDeviceId = this.deviceService.isCameraEnabled(); - if (audioDeviceId === undefined) audioDeviceId = this.deviceService.isMicrophoneEnabled(); + // Default values: true if device is enabled, false otherwise + videoDeviceId ??= this.deviceService.isCameraEnabled(); + audioDeviceId ??= this.deviceService.isMicrophoneEnabled(); - let options: CreateLocalTracksOptions = { + const options: CreateLocalTracksOptions = { audio: { echoCancellation: true, noiseSuppression: true }, video: {} }; // Video device if (videoDeviceId === true) { - if (this.deviceService.hasVideoDeviceAvailable()) { - videoDeviceId = this.deviceService.getCameraSelected()?.device || 'default'; - (options.video as VideoCaptureOptions).deviceId = videoDeviceId; - } else { - options.video = false; - } + options.video = this.deviceService.hasVideoDeviceAvailable() + ? { deviceId: this.deviceService.getCameraSelected()?.device || 'default' } + : false; } else if (videoDeviceId === false) { options.video = false; } else { @@ -279,13 +276,13 @@ export class OpenViduService { if (options.audio || options.video) { this.log.d('Creating local tracks with options', options); newLocalTracks = await createLocalTracks(options); + + // Mute tracks if devices are disabled if (!this.deviceService.isCameraEnabled()) { - const videoTrack = newLocalTracks.find((track) => track.kind === Track.Kind.Video); - if (videoTrack) videoTrack.mute(); + newLocalTracks.find((t) => t.kind === Track.Kind.Video)?.mute(); } if (!this.deviceService.isMicrophoneEnabled()) { - const audioTrack = newLocalTracks.find((track) => track.kind === Track.Kind.Audio); - if (audioTrack) audioTrack.mute(); + newLocalTracks.find((t) => t.kind === Track.Kind.Audio)?.mute(); } } return newLocalTracks; @@ -331,7 +328,7 @@ export class OpenViduService { return this.deviceService.isCameraEnabled(); } const videoTrack = this.localTracks.find((track) => track.kind === Track.Kind.Video); - return !!videoTrack && !videoTrack.isMuted; + return !!videoTrack && !videoTrack.isMuted && videoTrack?.mediaStreamTrack?.enabled; } /** @@ -344,7 +341,7 @@ export class OpenViduService { return this.deviceService.isMicrophoneEnabled(); } const audioTrack = this.localTracks.find((track) => track.kind === Track.Kind.Audio); - return !!audioTrack && !audioTrack.isMuted; + return !!audioTrack && !audioTrack.isMuted && audioTrack?.mediaStreamTrack?.enabled; } /**