ov-components: Improve prejoin card styles

master
Carlos Santos 2025-08-21 11:47:44 +02:00
parent 68d855a245
commit f92ee9b886
6 changed files with 115 additions and 46 deletions

View File

@ -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;

View File

@ -13,13 +13,13 @@
</div>
</div>
} @else {
<!-- Main Content with Side Panel Layout -->
<!-- Main Content -->
<div class="prejoin-content">
<!-- Main Card -->
<div class="prejoin-main">
<!-- Video Preview Section -->
<div class="video-preview-section">
<div class="video-preview-container">
<div class="video-preview-container" [@containerResize]="showBackgroundPanel ? 'compact' : 'normal'">
<div class="video-frame">
<ov-media-element
[track]="videoTrack"
@ -63,8 +63,9 @@
class="background-button"
(click)="toggleBackgroundPanel()"
[matTooltip]="'Virtual Backgrounds'"
[disabled]="!isVideoEnabled"
>
<mat-icon>auto_fix_high</mat-icon>
<mat-icon class="material-symbols-outlined">background_replace</mat-icon>
</button>
</div>
</div>
@ -73,7 +74,9 @@
</div>
@if (showBackgroundPanel) {
<div class="vb-container" [@slideInOut]>
<ov-background-effects-panel [mode]="'prejoin'" (onClose)="closeBackgroundPanel()"> </ov-background-effects-panel>
</div>
} @else {
<!-- Configuration Section -->
<div class="configuration-section">

View File

@ -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);
}
}

View File

@ -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<void>();
@ -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;
// 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() {
// 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);

View File

@ -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({

View File

@ -238,23 +238,20 @@ export class OpenViduService {
videoDeviceId: string | boolean | undefined = undefined,
audioDeviceId: string | boolean | undefined = undefined
): Promise<LocalTrack[]> {
// 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;
}
/**