From ee30c3ce95316cad0c8ca696efa817e12adc79b7 Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Fri, 22 Aug 2025 12:23:40 +0200 Subject: [PATCH] ov-components: Update device selection logic and improve UI handling for audio and video devices --- .../e2e/api-directives.test.ts | 20 ++-- .../e2e/events.test.ts | 86 +++++------------ .../e2e/media-devices.test.ts | 25 +++-- .../pre-join/pre-join.component.html | 34 ++++--- .../pre-join/pre-join.component.scss | 6 ++ .../components/pre-join/pre-join.component.ts | 5 + .../audio-devices.component.html | 92 ++++++++++-------- .../video-devices.component.html | 95 ++++++++++++------- .../video-devices/video-devices.component.ts | 2 + 9 files changed, 189 insertions(+), 176 deletions(-) diff --git a/openvidu-components-angular/e2e/api-directives.test.ts b/openvidu-components-angular/e2e/api-directives.test.ts index f43d8a38..dafcfc64 100644 --- a/openvidu-components-angular/e2e/api-directives.test.ts +++ b/openvidu-components-angular/e2e/api-directives.test.ts @@ -89,7 +89,7 @@ describe('Testing API Directives', () => { await utils.checkPrejoinIsPresent(); - await utils.waitForElement('#lang-btn-compact'); + await utils.waitForElement('.language-selector'); const element = await utils.waitForElement('#join-button'); expect(await element.getText()).toEqual('Unirme ahora'); @@ -108,20 +108,20 @@ describe('Testing API Directives', () => { const panelTitle = await utils.waitForElement('.panel-title'); expect(await panelTitle.getText()).toEqual('Configuración'); - const element = await utils.waitForElement('#lang-selected-name'); - expect(await element.getAttribute('innerText')).toEqual('Español'); + const element = await utils.waitForElement('.lang-name'); + expect(await element.getAttribute('innerText')).toEqual('Español expand_more'); }); it('should override the LANG OPTIONS', async () => { await browser.get(`${url}&prejoin=true&langOptions=true`); await utils.checkPrejoinIsPresent(); - await utils.waitForElement('#lang-btn-compact'); - await utils.clickOn('#lang-btn-compact'); + await utils.waitForElement('.language-selector'); + await utils.clickOn('.language-selector'); await browser.sleep(500); - expect(await utils.getNumberOfElements('.lang-menu-opt')).toEqual(2); + expect(await utils.getNumberOfElements('.language-option')).toEqual(2); - await utils.clickOn('.lang-menu-opt'); + await utils.clickOn('.language-option'); await browser.sleep(500); await utils.clickOn('#join-button'); @@ -136,12 +136,12 @@ describe('Testing API Directives', () => { await browser.sleep(500); await utils.waitForElement('#settings-container'); - await utils.waitForElement('.lang-button'); - await utils.clickOn('.lang-button'); + await utils.waitForElement('.full-lang-button'); + await utils.clickOn('.full-lang-button'); await browser.sleep(500); - expect(await utils.getNumberOfElements('.lang-menu-opt')).toEqual(2); + expect(await utils.getNumberOfElements('.language-option')).toEqual(2); }); it('should show the PREJOIN page', async () => { diff --git a/openvidu-components-angular/e2e/events.test.ts b/openvidu-components-angular/e2e/events.test.ts index 70446518..ff45c9e8 100644 --- a/openvidu-components-angular/e2e/events.test.ts +++ b/openvidu-components-angular/e2e/events.test.ts @@ -92,35 +92,12 @@ describe('Testing videoconference EVENTS', () => { expect(await utils.isPresent('#onVideoEnabledChanged-true')).toBeTrue(); }); - it('should receive the onVideoEnabledChanged event when clicking on the settings panel', async () => { - await browser.get(`${url}&prejoin=false`); - - await utils.checkSessionIsPresent(); - - await utils.checkToolbarIsPresent(); - await utils.togglePanel('settings'); - await browser.sleep(500); - - await utils.waitForElement('#settings-container'); - await utils.clickOn('#video-opt'); - - await utils.waitForElement('ov-video-devices-select'); - await utils.clickOn('ov-video-devices-select #camera-button'); - // Checking if onVideoEnabledChanged has been received - await utils.waitForElement('#onVideoEnabledChanged-false'); - expect(await utils.isPresent('#onVideoEnabledChanged-false')).toBeTrue(); - - await utils.clickOn('ov-video-devices-select #camera-button'); - await utils.waitForElement('#onVideoEnabledChanged-true'); - expect(await utils.isPresent('#onVideoEnabledChanged-true')).toBeTrue(); - }); - it('should receive the onVideoDeviceChanged event on prejoin', async () => { await browser.get(`${url}&fakeDevices=true`); await utils.checkPrejoinIsPresent(); - await utils.waitForElement('#video-devices-form'); - await utils.clickOn('#video-devices-form'); + await utils.waitForElement('#video-dropdown'); + await utils.clickOn('#video-dropdown'); await utils.waitForElement('#option-custom_fake_video_1'); await utils.clickOn('#option-custom_fake_video_1'); @@ -142,8 +119,8 @@ describe('Testing videoconference EVENTS', () => { await utils.clickOn('#video-opt'); await utils.waitForElement('ov-video-devices-select'); - await utils.waitForElement('#video-devices-form'); - await utils.clickOn('#video-devices-form'); + await utils.waitForElement('#video-dropdown'); + await utils.clickOn('#video-dropdown'); await utils.waitForElement('#option-custom_fake_video_1'); await utils.clickOn('#option-custom_fake_video_1'); @@ -184,35 +161,12 @@ describe('Testing videoconference EVENTS', () => { expect(await utils.isPresent('#onAudioEnabledChanged-true')).toBeTrue(); }); - it('should receive the onAudioEnabledChanged event when clicking on the settings panel', async () => { - await browser.get(`${url}&prejoin=false`); - - await utils.checkSessionIsPresent(); - - await utils.checkToolbarIsPresent(); - await utils.togglePanel('settings'); - await browser.sleep(500); - - await utils.waitForElement('#settings-container'); - await utils.clickOn('#audio-opt'); - - await utils.waitForElement('ov-audio-devices-select'); - await utils.clickOn('ov-audio-devices-select #microphone-button'); - // Checking if onAudioEnabledChanged has been received - await utils.waitForElement('#onAudioEnabledChanged-false'); - expect(await utils.isPresent('#onAudioEnabledChanged-false')).toBeTrue(); - - await utils.clickOn('ov-audio-devices-select #microphone-button'); - await utils.waitForElement('#onAudioEnabledChanged-true'); - expect(await utils.isPresent('#onAudioEnabledChanged-true')).toBeTrue(); - }); - it('should receive the onAudioDeviceChanged event on prejoin', async () => { await browser.get(`${url}&fakeDevices=true`); await utils.checkPrejoinIsPresent(); - await utils.waitForElement('#audio-devices-form'); - await utils.clickOn('#audio-devices-form'); + await utils.waitForElement('#audio-dropdown'); + await utils.clickOn('#audio-dropdown'); await utils.waitForElement('#option-custom_fake_audio_1'); await utils.clickOn('#option-custom_fake_audio_1'); @@ -234,8 +188,8 @@ describe('Testing videoconference EVENTS', () => { await utils.clickOn('#audio-opt'); await utils.waitForElement('ov-audio-devices-select'); - await utils.waitForElement('#audio-devices-form'); - await utils.clickOn('#audio-devices-form'); + await utils.waitForElement('#audio-dropdown'); + await utils.clickOn('#audio-dropdown'); await utils.waitForElement('#option-custom_fake_audio_1'); await utils.clickOn('#option-custom_fake_audio_1'); @@ -248,8 +202,8 @@ describe('Testing videoconference EVENTS', () => { await browser.get(`${url}`); await utils.checkPrejoinIsPresent(); - await utils.waitForElement('#lang-btn-compact'); - await utils.clickOn('#lang-btn-compact'); + await utils.waitForElement('.language-selector'); + await utils.clickOn('.language-selector'); await browser.sleep(500); await utils.clickOn('#lang-opt-es'); @@ -269,8 +223,8 @@ describe('Testing videoconference EVENTS', () => { await browser.sleep(500); await utils.waitForElement('#settings-container'); - await utils.waitForElement('.lang-button'); - await utils.clickOn('.lang-button'); + await utils.waitForElement('.full-lang-button'); + await utils.clickOn('.full-lang-button'); await browser.sleep(500); await utils.clickOn('#lang-opt-es'); @@ -398,7 +352,7 @@ describe('Testing videoconference EVENTS', () => { expect(await utils.isPresent('#onSettingsPanelStatusChanged-false')).toBeTrue(); }); - it('should receive the onRecordingStartRequested event when clicking toolbar button', async () => { + fit('should receive the onRecordingStartRequested and onRecordingStopRequested event when clicking toolbar button', async () => { const roomName = 'recordingToolbarEvent'; await browser.get(`${url}&prejoin=false&roomName=${roomName}`); @@ -410,9 +364,15 @@ describe('Testing videoconference EVENTS', () => { // Checking if onRecordingStartRequested has been received await utils.waitForElement(`#onRecordingStartRequested-${roomName}`); expect(await utils.isPresent(`#onRecordingStartRequested-${roomName}`)).toBeTrue(); - }); - xit('should receive the onRecordingStopRequested event when clicking toolbar button', async () => {}); + await utils.waitForElement('.activity-status.started'); + + await utils.toggleRecordingFromToolbar(); + + // Checking if onRecordingStopRequested has been received + await utils.waitForElement(`#onRecordingStopRequested-${roomName}`); + expect(await utils.isPresent(`#onRecordingStopRequested-${roomName}`)).toBeTrue(); + }); xit('should receive the onBroadcastingStopRequested event when clicking toolbar button', async () => { await browser.get(`${url}&prejoin=false`); @@ -446,7 +406,7 @@ describe('Testing videoconference EVENTS', () => { expect(await utils.isPresent('#onBroadcastingStopRequested')).toBeTrue(); }); - it('should receive the onRecordingStartRequested when clicking from activities panel', async () => { + it('should receive the onRecordingStartRequested and onRecordingStopRequested when clicking from activities panel', async () => { const roomName = 'recordingActivitiesEvent'; await browser.get(`${url}&prejoin=false&roomName=${roomName}`); @@ -472,8 +432,6 @@ describe('Testing videoconference EVENTS', () => { expect(await utils.isPresent(`#onRecordingStartRequested-${roomName}`)).toBeTrue(); }); - xit('should receive the onRecordingStopRequested when clicking from activities panel', async () => {}); - xit('should receive the onRecordingDeleteRequested event', async () => { let element; const roomName = 'deleteRecordingEvent'; diff --git a/openvidu-components-angular/e2e/media-devices.test.ts b/openvidu-components-angular/e2e/media-devices.test.ts index b188f653..0e6ff624 100644 --- a/openvidu-components-angular/e2e/media-devices.test.ts +++ b/openvidu-components-angular/e2e/media-devices.test.ts @@ -33,7 +33,7 @@ describe('Media Devices: Virtual Device Replacement and Permissions Handling', ( await browser.get(`${url}&fakeDevices=true`); - let videoDevices = await utils.waitForElement('#video-devices-form'); + let videoDevices = await utils.waitForElement('#video-dropdown'); await videoDevices.click(); let element = await utils.waitForElement('#option-custom_fake_video_1'); await element.click(); @@ -63,7 +63,7 @@ describe('Media Devices: Virtual Device Replacement and Permissions Handling', ( await browser.sleep(500); await utils.clickOn('#video-opt'); expect(await utils.isPresent('ov-video-devices-select')).toBeTrue(); - let videoDevices = await utils.waitForElement('#video-devices-form'); + let videoDevices = await utils.waitForElement('#video-dropdown'); await videoDevices.click(); let element = await utils.waitForElement('#option-custom_fake_video_1'); await element.click(); @@ -130,16 +130,15 @@ describe('Media Devices: UI Behavior Without Media Device Permissions', () => { await browser.quit(); }); - it('should disable camera and microphone buttons in the prejoin page when permissions are denied', async () => { + it('should camera and microphone buttons be disabled in the prejoin page when permissions are denied', async () => { await browser.get(`${url}`); await utils.checkPrejoinIsPresent(); - let button = await utils.waitForElement('#camera-button'); - expect(await button.isEnabled()).toBeFalse(); - button = await utils.waitForElement('#microphone-button'); - expect(await button.isEnabled()).toBeFalse(); + await utils.waitForElement('#no-video-device-message'); + await utils.waitForElement('#no-audio-device-message'); + expect(await utils.isPresent('#backgrounds-button')).toBeFalse(); }); - it('should disable camera and microphone buttons in the room page when permissions are denied', async () => { + it('should camera and microphone buttons be disabled in the room page when permissions are denied', async () => { await browser.get(`${url}`); await utils.checkPrejoinIsPresent(); await utils.clickOn('#join-button'); @@ -151,7 +150,7 @@ describe('Media Devices: UI Behavior Without Media Device Permissions', () => { expect(await button.isEnabled()).toBeFalse(); }); - it('should disable camera and microphone buttons in the room page without prejoin when permissions are denied', async () => { + it('should camera and microphone buttons be disabled in the room page without prejoin when permissions are denied', async () => { await browser.get(`${url}&prejoin=false`); await utils.checkSessionIsPresent(); await utils.checkToolbarIsPresent(); @@ -161,7 +160,7 @@ describe('Media Devices: UI Behavior Without Media Device Permissions', () => { expect(await button.isEnabled()).toBeFalse(); }); - it('should disable camera and microphone device selection buttons in settings when permissions are denied', async () => { + it('should show an audio and video device warning in settings when permissions are denied', async () => { await browser.get(`${url}&prejoin=false`); await utils.checkToolbarIsPresent(); await utils.togglePanel('settings'); @@ -170,11 +169,9 @@ describe('Media Devices: UI Behavior Without Media Device Permissions', () => { expect(await utils.isPresent('.settings-container')).toBeTrue(); await utils.clickOn('#video-opt'); expect(await utils.isPresent('ov-video-devices-select')).toBeTrue(); - let button = await utils.waitForElement('#camera-button'); - expect(await button.isEnabled()).toBeFalse(); + await utils.waitForElement('#no-video-device-message'); await utils.clickOn('#audio-opt'); expect(await utils.isPresent('ov-audio-devices-select')).toBeTrue(); - button = await utils.waitForElement('#microphone-button'); - expect(await button.isEnabled()).toBeFalse(); + await utils.waitForElement('#no-audio-device-message'); }); }); 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 8fb7fcc5..d11b68d5 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 @@ -1,4 +1,4 @@ -
+
@@ -39,6 +39,7 @@ [compact]="true" (onVideoDeviceChanged)="onVideoDeviceChanged.emit($event)" (onVideoEnabledChanged)="videoEnabledChanged($event)" + (onVideoDevicesLoaded)="onVideoDevicesLoaded($event)" class="device-selector" > @@ -57,17 +58,20 @@
-
- -
+ @if (backgroundEffectEnabled && hasVideoDevices) { +
+ +
+ }
@@ -81,7 +85,7 @@
-
+
-
+
error_outline {{ _error }}
@@ -104,9 +108,9 @@ mat-flat-button (click)="join()" class="join-button" + id="join-button" [disabled]="showParticipantName && !participantName" > - videocam {{ 'PREJOIN.JOIN' | translate }}
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 ef1368a4..ec57d0a0 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 @@ -14,6 +14,12 @@ position: relative; transition: all 0.3s ease; + &.name-error { + .prejoin-main { + min-height: fit-content; + } + } + .prejoin-content { display: flex; justify-content: center; 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 46a12085..fefc07d1 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 @@ -103,6 +103,7 @@ export class PreJoinComponent implements OnInit, OnDestroy { videoTrack: LocalTrack | undefined; audioTrack: LocalTrack | undefined; isVideoEnabled: boolean = false; + hasVideoDevices: boolean = true; private tracks: LocalTrack[]; private log: ILogger; private destroy$ = new Subject(); @@ -247,6 +248,10 @@ export class PreJoinComponent implements OnInit, OnDestroy { this.onVideoEnabledChanged.emit(enabled); } + onVideoDevicesLoaded(devices: CustomDevice[]) { + this.hasVideoDevices = devices.length > 0; + } + async audioEnabledChanged(enabled: boolean) { if (enabled && !this.audioTrack) { const newAudioTrack = await this.openviduService.createLocalTracks(false, true); diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.html index 89a812f4..c95fb377 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.html @@ -1,28 +1,43 @@
@if (compact) { -
- - - - - @if (microphones.length > 1 && isMicrophoneEnabled) { - - } -
+ + + @if (isMicrophoneEnabled) { + + } +
+ } @else { + +
+ warning + {{ 'PREJOIN.NO_AUDIO_DEVICE' | translate }} +
+ } } @else {
@@ -33,6 +48,7 @@
} @else { - -
-
- mic_off - - {{ !hasAudioDevices ? ('PREJOIN.NO_AUDIO_DEVICE' | translate) : 'Microphone disabled' }} - + @if (hasAudioDevices) { + +
+
+ mic_off + + {{ !hasAudioDevices ? ('PREJOIN.NO_AUDIO_DEVICE' | translate) : 'Microphone disabled' }} + +
-
+ } @else { + +
+ warning + {{ 'PREJOIN.NO_AUDIO_DEVICE' | translate }} +
+ } }
} + @for (microphone of microphones; track microphone.device) { } - - - @if (microphones.length === 0) { -
- warning - {{ 'PREJOIN.NO_AUDIO_DEVICE' | translate }} -
- }
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.html index 7bd746f6..dd65e256 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.html @@ -1,28 +1,45 @@
@if (compact) { -
- - - - - @if (isCameraEnabled && cameras.length > 1) { - - } -
+ + + @if (isCameraEnabled) { + + } +
+ } @else { + +
+ warning + {{ 'PREJOIN.NO_VIDEO_DEVICE' | translate }} +
+ } } @else {
@@ -31,6 +48,7 @@
} @else { - -
-
- videocam_off - {{ 'PANEL.SETTINGS.DISABLED_VIDEO' | translate }} + @if (hasVideoDevices) { + +
+
+ videocam_off + {{ 'PANEL.SETTINGS.DISABLED_VIDEO' | translate }} +
-
+ } @else { + +
+ warning + {{ 'PREJOIN.NO_VIDEO_DEVICE' | translate }} +
+ } }
} @@ -55,18 +81,15 @@ @for (camera of cameras; track camera.device) { - } - - - @if (cameras.length === 0) { -
- warning - {{ 'PREJOIN.NO_VIDEO_DEVICE' | translate }} -
- }
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.ts index d4c77118..bf8559c9 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.ts @@ -19,6 +19,7 @@ export class VideoDevicesComponent implements OnInit, OnDestroy { @Input() compact: boolean = false; @Output() onVideoDeviceChanged = new EventEmitter(); @Output() onVideoEnabledChanged = new EventEmitter(); + @Output() onVideoDevicesLoaded = new EventEmitter(); cameraStatusChanging: boolean; isCameraEnabled: boolean; @@ -42,6 +43,7 @@ export class VideoDevicesComponent implements OnInit, OnDestroy { this.cameraSelected = this.deviceSrv.getCameraSelected(); } + this.onVideoDevicesLoaded.emit(this.cameras); this.isCameraEnabled = this.participantService.isMyCameraEnabled(); }