-
+
- 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;
}
/**