mirror of https://github.com/OpenVidu/openvidu.git
ov-components: Improve prejoin card styles
parent
68d855a245
commit
f92ee9b886
|
@ -1,14 +1,11 @@
|
||||||
.prejoin-mode {
|
.prejoin-mode {
|
||||||
margin-top: 0px;
|
margin: 0 10px 0px 10px;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
|
||||||
.effects-container {
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.background-title {
|
.background-title {
|
||||||
color: var(--ov-text-surface-color);
|
color: var(--ov-text-surface-color);
|
||||||
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
.effects-container {
|
.effects-container {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
|
|
|
@ -13,13 +13,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<!-- Main Content with Side Panel Layout -->
|
<!-- Main Content -->
|
||||||
<div class="prejoin-content">
|
<div class="prejoin-content">
|
||||||
<!-- Main Card -->
|
<!-- Main Card -->
|
||||||
<div class="prejoin-main">
|
<div class="prejoin-main">
|
||||||
<!-- Video Preview Section -->
|
<!-- Video Preview Section -->
|
||||||
<div class="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">
|
<div class="video-frame">
|
||||||
<ov-media-element
|
<ov-media-element
|
||||||
[track]="videoTrack"
|
[track]="videoTrack"
|
||||||
|
@ -63,8 +63,9 @@
|
||||||
class="background-button"
|
class="background-button"
|
||||||
(click)="toggleBackgroundPanel()"
|
(click)="toggleBackgroundPanel()"
|
||||||
[matTooltip]="'Virtual Backgrounds'"
|
[matTooltip]="'Virtual Backgrounds'"
|
||||||
|
[disabled]="!isVideoEnabled"
|
||||||
>
|
>
|
||||||
<mat-icon>auto_fix_high</mat-icon>
|
<mat-icon class="material-symbols-outlined">background_replace</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,7 +74,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (showBackgroundPanel) {
|
@if (showBackgroundPanel) {
|
||||||
|
<div class="vb-container" [@slideInOut]>
|
||||||
<ov-background-effects-panel [mode]="'prejoin'" (onClose)="closeBackgroundPanel()"> </ov-background-effects-panel>
|
<ov-background-effects-panel [mode]="'prejoin'" (onClose)="closeBackgroundPanel()"> </ov-background-effects-panel>
|
||||||
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<!-- Configuration Section -->
|
<!-- Configuration Section -->
|
||||||
<div class="configuration-section">
|
<div class="configuration-section">
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
.prejoin-main {
|
.prejoin-main {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 520px;
|
max-width: 520px;
|
||||||
|
max-height: 544px;
|
||||||
background: var(--ov-surface-color, #ffffff);
|
background: var(--ov-surface-color, #ffffff);
|
||||||
border-radius: var(--ov-surface-radius);
|
border-radius: var(--ov-surface-radius);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -93,18 +94,16 @@
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
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 {
|
.video-preview-section {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
.video-preview-container {
|
.video-preview-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 4/3; // Changed from 16/9 to 4/3 for taller video
|
aspect-ratio: 4/3;
|
||||||
border-radius: var(--ov-surface-radius) var(--ov-surface-radius) 0 0; // Only top corners rounded
|
border-radius: var(--ov-surface-radius) var(--ov-surface-radius) 0 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #000;
|
background: #000;
|
||||||
|
|
||||||
.video-frame {
|
.video-frame {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -156,16 +155,23 @@
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
color: #333333;
|
color: #333333;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
transform: translateZ(0);
|
||||||
&:hover {
|
|
||||||
color: rgba(255, 255, 255, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
transition: all 0.15s ease;
|
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 {
|
mat-icon {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
width: 22px;
|
width: 22px;
|
||||||
|
@ -183,6 +189,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vb-container {
|
||||||
|
height: fit-content;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
// Configuration Section
|
// Configuration Section
|
||||||
.configuration-section {
|
.configuration-section {
|
||||||
padding: 24px 24px 24px; // Added top padding since video has no padding
|
padding: 24px 24px 24px; // Added top padding since video has no padding
|
||||||
|
@ -393,8 +404,8 @@
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.prejoin-main {
|
.prejoin-main {
|
||||||
animation: fadeIn 0.3s ease-out;
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
transform: translateZ(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
OnInit,
|
OnInit,
|
||||||
Output
|
Output
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||||
import { filter, Subject, takeUntil, tap } from 'rxjs';
|
import { filter, Subject, takeUntil, tap } from 'rxjs';
|
||||||
import { ILogger } from '../../models/logger.model';
|
import { ILogger } from '../../models/logger.model';
|
||||||
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
|
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
|
||||||
|
@ -28,7 +29,47 @@ import { LangOption } from '../../models/lang.model';
|
||||||
templateUrl: './pre-join.component.html',
|
templateUrl: './pre-join.component.html',
|
||||||
styleUrls: ['./pre-join.component.scss'],
|
styleUrls: ['./pre-join.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
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 {
|
export class PreJoinComponent implements OnInit, OnDestroy {
|
||||||
@Input() set error(error: { name: string; message: string } | undefined) {
|
@Input() set error(error: { name: string; message: string } | undefined) {
|
||||||
|
@ -61,6 +102,7 @@ export class PreJoinComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
videoTrack: LocalTrack | undefined;
|
videoTrack: LocalTrack | undefined;
|
||||||
audioTrack: LocalTrack | undefined;
|
audioTrack: LocalTrack | undefined;
|
||||||
|
isVideoEnabled: boolean = false;
|
||||||
private tracks: LocalTrack[];
|
private tracks: LocalTrack[];
|
||||||
private log: ILogger;
|
private log: ILogger;
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
@ -195,12 +237,16 @@ export class PreJoinComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
async videoEnabledChanged(enabled: boolean) {
|
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);
|
const newVideoTrack = await this.openviduService.createLocalTracks(true, false);
|
||||||
this.videoTrack = newVideoTrack[0];
|
this.videoTrack = newVideoTrack[0];
|
||||||
this.tracks.push(this.videoTrack);
|
this.tracks.push(this.videoTrack);
|
||||||
this.openviduService.setLocalTracks(this.tracks);
|
this.openviduService.setLocalTracks(this.tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onVideoEnabledChanged.emit(enabled);
|
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() {
|
toggleBackgroundPanel() {
|
||||||
this.showBackgroundPanel = !this.showBackgroundPanel;
|
// Add a small delay to ensure smooth transition
|
||||||
|
if (!this.showBackgroundPanel) {
|
||||||
|
// Opening panel
|
||||||
|
this.showBackgroundPanel = true;
|
||||||
this.changeDetector.markForCheck();
|
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() {
|
closeBackgroundPanel() {
|
||||||
|
// Add animation delay for smooth closing
|
||||||
|
setTimeout(() => {
|
||||||
this.showBackgroundPanel = false;
|
this.showBackgroundPanel = false;
|
||||||
this.changeDetector.markForCheck();
|
this.changeDetector.markForCheck();
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -249,6 +308,8 @@ export class PreJoinComponent implements OnInit, OnDestroy {
|
||||||
this.openviduService.setLocalTracks(this.tracks);
|
this.openviduService.setLocalTracks(this.tracks);
|
||||||
this.videoTrack = this.tracks.find((track) => track.kind === 'video');
|
this.videoTrack = this.tracks.find((track) => track.kind === 'video');
|
||||||
this.audioTrack = this.tracks.find((track) => track.kind === 'audio');
|
this.audioTrack = this.tracks.find((track) => track.kind === 'audio');
|
||||||
|
this.isVideoEnabled = this.openviduService.isVideoTrackEnabled();
|
||||||
|
|
||||||
return; // Success, exit retry loop
|
return; // Success, exit retry loop
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log.w(`Device initialization attempt ${attempt} failed:`, error);
|
this.log.w(`Device initialization attempt ${attempt} failed:`, error);
|
||||||
|
|
|
@ -87,8 +87,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
|
||||||
// Constants
|
// Constants
|
||||||
private static readonly PARTICIPANT_NAME_TIMEOUT_MS = 1000;
|
private static readonly PARTICIPANT_NAME_TIMEOUT_MS = 1000;
|
||||||
private static readonly ANIMATION_DURATION_MS = 300;
|
private static readonly ANIMATION_DURATION_MS = 300;
|
||||||
private static readonly MATERIAL_ICONS_URL =
|
private static readonly MATERIAL_ICONS_URL = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined';
|
||||||
'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined';
|
|
||||||
private static readonly MATERIAL_ICONS_SELECTOR = 'link[href*="Material+Symbols+Outlined"]';
|
private static readonly MATERIAL_ICONS_SELECTOR = 'link[href*="Material+Symbols+Outlined"]';
|
||||||
private static readonly SPINNER_DIAMETER = 50;
|
private static readonly SPINNER_DIAMETER = 50;
|
||||||
// *** Toolbar ***
|
// *** Toolbar ***
|
||||||
|
@ -690,6 +689,8 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
|
||||||
) {
|
) {
|
||||||
this.log = this.loggerSrv.get('VideoconferenceComponent');
|
this.log = this.loggerSrv.get('VideoconferenceComponent');
|
||||||
|
|
||||||
|
this.addMaterialIconsIfNeeded();
|
||||||
|
|
||||||
// Initialize state
|
// Initialize state
|
||||||
this.updateComponentState({
|
this.updateComponentState({
|
||||||
state: VideoconferenceState.INITIALIZING,
|
state: VideoconferenceState.INITIALIZING,
|
||||||
|
@ -713,7 +714,6 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
this.addMaterialIconsIfNeeded();
|
|
||||||
this.setupTemplates();
|
this.setupTemplates();
|
||||||
this.deviceSrv.initializeDevices().then(() => {
|
this.deviceSrv.initializeDevices().then(() => {
|
||||||
this.updateComponentState({
|
this.updateComponentState({
|
||||||
|
|
|
@ -238,23 +238,20 @@ export class OpenViduService {
|
||||||
videoDeviceId: string | boolean | undefined = undefined,
|
videoDeviceId: string | boolean | undefined = undefined,
|
||||||
audioDeviceId: string | boolean | undefined = undefined
|
audioDeviceId: string | boolean | undefined = undefined
|
||||||
): Promise<LocalTrack[]> {
|
): Promise<LocalTrack[]> {
|
||||||
// If video and audio device IDs are not provided, check if they are enabled and use the default devices
|
// Default values: true if device is enabled, false otherwise
|
||||||
if (videoDeviceId === undefined) videoDeviceId = this.deviceService.isCameraEnabled();
|
videoDeviceId ??= this.deviceService.isCameraEnabled();
|
||||||
if (audioDeviceId === undefined) audioDeviceId = this.deviceService.isMicrophoneEnabled();
|
audioDeviceId ??= this.deviceService.isMicrophoneEnabled();
|
||||||
|
|
||||||
let options: CreateLocalTracksOptions = {
|
const options: CreateLocalTracksOptions = {
|
||||||
audio: { echoCancellation: true, noiseSuppression: true },
|
audio: { echoCancellation: true, noiseSuppression: true },
|
||||||
video: {}
|
video: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Video device
|
// Video device
|
||||||
if (videoDeviceId === true) {
|
if (videoDeviceId === true) {
|
||||||
if (this.deviceService.hasVideoDeviceAvailable()) {
|
options.video = this.deviceService.hasVideoDeviceAvailable()
|
||||||
videoDeviceId = this.deviceService.getCameraSelected()?.device || 'default';
|
? { deviceId: this.deviceService.getCameraSelected()?.device || 'default' }
|
||||||
(options.video as VideoCaptureOptions).deviceId = videoDeviceId;
|
: false;
|
||||||
} else {
|
|
||||||
options.video = false;
|
|
||||||
}
|
|
||||||
} else if (videoDeviceId === false) {
|
} else if (videoDeviceId === false) {
|
||||||
options.video = false;
|
options.video = false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -279,13 +276,13 @@ export class OpenViduService {
|
||||||
if (options.audio || options.video) {
|
if (options.audio || options.video) {
|
||||||
this.log.d('Creating local tracks with options', options);
|
this.log.d('Creating local tracks with options', options);
|
||||||
newLocalTracks = await createLocalTracks(options);
|
newLocalTracks = await createLocalTracks(options);
|
||||||
|
|
||||||
|
// Mute tracks if devices are disabled
|
||||||
if (!this.deviceService.isCameraEnabled()) {
|
if (!this.deviceService.isCameraEnabled()) {
|
||||||
const videoTrack = newLocalTracks.find((track) => track.kind === Track.Kind.Video);
|
newLocalTracks.find((t) => t.kind === Track.Kind.Video)?.mute();
|
||||||
if (videoTrack) videoTrack.mute();
|
|
||||||
}
|
}
|
||||||
if (!this.deviceService.isMicrophoneEnabled()) {
|
if (!this.deviceService.isMicrophoneEnabled()) {
|
||||||
const audioTrack = newLocalTracks.find((track) => track.kind === Track.Kind.Audio);
|
newLocalTracks.find((t) => t.kind === Track.Kind.Audio)?.mute();
|
||||||
if (audioTrack) audioTrack.mute();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newLocalTracks;
|
return newLocalTracks;
|
||||||
|
@ -331,7 +328,7 @@ export class OpenViduService {
|
||||||
return this.deviceService.isCameraEnabled();
|
return this.deviceService.isCameraEnabled();
|
||||||
}
|
}
|
||||||
const videoTrack = this.localTracks.find((track) => track.kind === Track.Kind.Video);
|
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();
|
return this.deviceService.isMicrophoneEnabled();
|
||||||
}
|
}
|
||||||
const audioTrack = this.localTracks.find((track) => track.kind === Track.Kind.Audio);
|
const audioTrack = this.localTracks.find((track) => track.kind === Track.Kind.Audio);
|
||||||
return !!audioTrack && !audioTrack.isMuted;
|
return !!audioTrack && !audioTrack.isMuted && audioTrack?.mediaStreamTrack?.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue