diff --git a/.github/workflows/openvidu-components-angular-tests.yml b/.github/workflows/openvidu-components-angular-tests.yml index f34d2fd0..ad217f70 100644 --- a/.github/workflows/openvidu-components-angular-tests.yml +++ b/.github/workflows/openvidu-components-angular-tests.yml @@ -245,10 +245,9 @@ jobs: if: always() uses: ./.github/actions/cleanup - webcomponent_e2e_events: + e2e_events: needs: test_setup - name: Webcomponent events - if: false + name: Events E2E runs-on: ubuntu-latest steps: - name: Checkout Repository @@ -269,26 +268,12 @@ jobs: uses: ./.github/actions/setup-local-deployment - name: Setup OpenVidu Call Backend uses: ./.github/actions/setup-openvidu-call-backend - - name: Install dependencies - run: | - cd openvidu-components-angular - npm install - - name: Build openvidu-angular - run: npm run lib:build --prefix openvidu-components-angular - - name: Build openvidu-webcomponent - run: npm run webcomponent:testing-build --prefix openvidu-components-angular - - name: Serve Webcomponent Testapp - run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular & - - name: Wait for openvidu-components-angular Testapp - run: | - until curl -s -f -o /dev/null http://localhost:8080; do - echo "Waiting for openvidu-components-angular Testapp to be ready..." - sleep 5 - done - - name: Run Webcomponent E2E + - name: Build and Serve openvidu-components-angular Testapp + uses: ./.github/actions/build-and-serve-components-testapp + - name: Run Tests env: LAUNCH_MODE: CI - run: npm run e2e:webcomponent-events --prefix openvidu-components-angular + run: npm run e2e:lib-events --prefix openvidu-components-angular - name: Cleanup if: always() uses: ./.github/actions/cleanup diff --git a/openvidu-components-angular/e2e/events.test.ts b/openvidu-components-angular/e2e/events.test.ts new file mode 100644 index 00000000..8e16a300 --- /dev/null +++ b/openvidu-components-angular/e2e/events.test.ts @@ -0,0 +1,645 @@ +import { Builder, Key, WebDriver } from 'selenium-webdriver'; +import { TestAppConfig } from './selenium.conf'; +import { OpenViduComponentsPO } from './utils.po.test'; + +const url = TestAppConfig.appUrl; + +describe('Testing videoconference EVENTS', () => { + let browser: WebDriver; + let utils: OpenViduComponentsPO; + const isHeadless: boolean = (TestAppConfig.browserOptions as any).options_.args.includes('--headless'); + async function createChromeBrowser(): Promise { + return await new Builder() + .forBrowser(TestAppConfig.browserName) + .withCapabilities(TestAppConfig.browserCapabilities) + .setChromeOptions(TestAppConfig.browserOptions) + .usingServer(TestAppConfig.seleniumAddress) + .build(); + } + + beforeEach(async () => { + browser = await createChromeBrowser(); + utils = new OpenViduComponentsPO(browser); + }); + + afterEach(async () => { + try { + // leaving room if connected + await utils.leaveRoom(); + } catch (error) {} + await browser.quit(); + }); + + it('should receive the onReadyToJoin event', async () => { + await browser.get(`${url}`); + + await utils.waitForElement('#prejoin-container'); + expect(await utils.isPresent('#prejoin-container')).toBeTrue(); + + // Clicking to join button + await utils.waitForElement('#join-button'); + await utils.clickOn('#join-button'); + + // Checking if onReadyToJoin has been received + await utils.waitForElement('#onReadyToJoin'); + expect(await utils.isPresent('#onReadyToJoin')).toBeTrue(); + }); + + it('should receive the onTokenRequested event', async () => { + await browser.get(`${url}`); + + await utils.waitForElement('#prejoin-container'); + expect(await utils.isPresent('#prejoin-container')).toBeTrue(); + + // Clicking to join button + await utils.waitForElement('#join-button'); + await utils.clickOn('#join-button'); + + // Checking if onTokenRequested has been received + await utils.waitForElement('#onTokenRequested'); + expect(await utils.isPresent('#onTokenRequested')).toBeTrue(); + }); + + it('should receive the onVideoEnabledChanged event when clicking on the prejoin', async () => { + await browser.get(url); + await utils.checkPrejoinIsPresent(); + + await utils.waitForElement('#camera-button'); + await utils.clickOn('#camera-button'); + + // Checking if onVideoEnabledChanged has been received + await utils.waitForElement('#onVideoEnabledChanged-false'); + expect(await utils.isPresent('#onVideoEnabledChanged-false')).toBeTrue(); + }); + + it('should receive the onVideoEnabledChanged event when clicking on the toolbar', async () => { + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + + await utils.checkToolbarIsPresent(); + + // Clicking to leave button + await utils.waitForElement('#camera-btn'); + await utils.clickOn('#camera-btn'); + + // Checking if onVideoEnabledChanged has been received + await utils.waitForElement('#onVideoEnabledChanged-false'); + expect(await utils.isPresent('#onVideoEnabledChanged-false')).toBeTrue(); + + await utils.clickOn('#camera-btn'); + await utils.waitForElement('#onVideoEnabledChanged-true'); + 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('#option-custom_fake_video_1'); + await utils.clickOn('#option-custom_fake_video_1'); + + await utils.waitForElement('#onVideoDeviceChanged'); + expect(await utils.isPresent('#onVideoDeviceChanged')).toBeTrue(); + }); + + it('should receive the onVideoDeviceChanged event on settings panel', async () => { + await browser.get(`${url}&prejoin=false&fakeDevices=true`); + + 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.waitForElement('#video-devices-form'); + await utils.clickOn('#video-devices-form'); + + await utils.waitForElement('#option-custom_fake_video_1'); + await utils.clickOn('#option-custom_fake_video_1'); + + await utils.waitForElement('#onVideoDeviceChanged'); + expect(await utils.isPresent('#onVideoDeviceChanged')).toBeTrue(); + }); + + it('should receive the onAudioEnabledChanged event when clicking on the prejoin', async () => { + await browser.get(url); + await utils.checkPrejoinIsPresent(); + + await utils.waitForElement('#microphone-button'); + await utils.clickOn('#microphone-button'); + + // Checking if onAudioEnabledChanged has been received + await utils.waitForElement('#onAudioEnabledChanged-false'); + expect(await utils.isPresent('#onAudioEnabledChanged-false')).toBeTrue(); + }); + + it('should receive the onAudioEnabledChanged event when clicking on the toolbar', async () => { + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + + await utils.checkToolbarIsPresent(); + + // Clicking to leave button + await utils.waitForElement('#mic-btn'); + await utils.clickOn('#mic-btn'); + + // Checking if onAudioEnabledChanged has been received + await utils.waitForElement('#onAudioEnabledChanged-false'); + expect(await utils.isPresent('#onAudioEnabledChanged-false')).toBeTrue(); + + await utils.clickOn('#mic-btn'); + await utils.waitForElement('#onAudioEnabledChanged-true'); + 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('#option-custom_fake_audio_1'); + await utils.clickOn('#option-custom_fake_audio_1'); + + await utils.waitForElement('#onAudioDeviceChanged'); + expect(await utils.isPresent('#onAudioDeviceChanged')).toBeTrue(); + }); + + it('should receive the onAudioDeviceChanged event on settings panel', async () => { + await browser.get(`${url}&prejoin=false&fakeDevices=true`); + + 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.waitForElement('#audio-devices-form'); + await utils.clickOn('#audio-devices-form'); + + await utils.waitForElement('#option-custom_fake_audio_1'); + await utils.clickOn('#option-custom_fake_audio_1'); + + await utils.waitForElement('#onAudioDeviceChanged'); + expect(await utils.isPresent('#onAudioDeviceChanged')).toBeTrue(); + }); + + it('should receive the onLangChanged event on prejoin', async () => { + await browser.get(`${url}`); + await utils.checkPrejoinIsPresent(); + + await utils.waitForElement('#lang-btn-compact'); + await utils.clickOn('#lang-btn-compact'); + + await browser.sleep(500); + await utils.clickOn('#lang-opt-es'); + await browser.sleep(500); + + await utils.waitForElement('#onLangChanged-es'); + expect(await utils.isPresent('#onLangChanged-es')).toBeTrue(); + }); + + it('should receive the onLangChanged event on 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.waitForElement('.lang-button'); + await utils.clickOn('.lang-button'); + + await browser.sleep(500); + await utils.clickOn('#lang-opt-es'); + await browser.sleep(500); + + await utils.waitForElement('#onLangChanged-es'); + expect(await utils.isPresent('#onLangChanged-es')).toBeTrue(); + }); + + it('should receive the onScreenShareEnabledChanged event', async () => { + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + + await utils.checkToolbarIsPresent(); + + // Clicking to leave button + const screenshareButton = await utils.waitForElement('#screenshare-btn'); + expect(await utils.isPresent('#screenshare-btn')).toBeTrue(); + await screenshareButton.click(); + + // Checking if onScreenShareEnabledChanged has been received + await utils.waitForElement('#onScreenShareEnabledChanged'); + expect(await utils.isPresent('#onScreenShareEnabledChanged')).toBeTrue(); + }); + + // With headless mode, the Fullscreen API doesn't work + it('should receive the onFullscreenEnabledChanged event', async () => { + let element; + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + + await utils.checkToolbarIsPresent(); + + await utils.toggleFullscreenFromToolbar(); + await browser.sleep(500); + + // Checking if onFullscreenEnabledChanged has been received + await utils.waitForElement('#onFullscreenEnabledChanged-true'); + expect(await utils.isPresent('#onFullscreenEnabledChanged-true')).toBeTrue(); + + await (await utils.waitForElement('html')).sendKeys(Key.F11); + await browser.sleep(500); + + await utils.waitForElement('#onFullscreenEnabledChanged-false'); + expect(await utils.isPresent('#onFullscreenEnabledChanged-false')).toBeTrue(); + }); + + it('should receive the onChatPanelStatusChanged event', async () => { + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + + await utils.checkToolbarIsPresent(); + + await utils.togglePanel('chat'); + + // Checking if onChatPanelStatusChanged has been received + await utils.waitForElement('#onChatPanelStatusChanged-true'); + expect(await utils.isPresent('#onChatPanelStatusChanged-true')).toBeTrue(); + + await utils.togglePanel('chat'); + + // Checking if onChatPanelStatusChanged has been received + await utils.waitForElement('#onChatPanelStatusChanged-false'); + expect(await utils.isPresent('#onChatPanelStatusChanged-false')).toBeTrue(); + }); + + it('should receive the onParticipantsPanelStatusChanged event', async () => { + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + + await utils.checkToolbarIsPresent(); + + await utils.togglePanel('participants'); + + // Checking if onParticipantsPanelStatusChanged has been received + await utils.waitForElement('#onParticipantsPanelStatusChanged-true'); + expect(await utils.isPresent('#onParticipantsPanelStatusChanged-true')).toBeTrue(); + + await utils.togglePanel('participants'); + + // Checking if onParticipantsPanelStatusChanged has been received + await utils.waitForElement('#onParticipantsPanelStatusChanged-false'); + expect(await utils.isPresent('#onParticipantsPanelStatusChanged-false')).toBeTrue(); + }); + + it('should receive the onActivitiesPanelStatusChanged event', async () => { + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + await utils.checkToolbarIsPresent(); + + await utils.togglePanel('activities'); + + // Checking if onActivitiesPanelStatusChanged has been received + await utils.waitForElement('#onActivitiesPanelStatusChanged-true'); + expect(await utils.isPresent('#onActivitiesPanelStatusChanged-true')).toBeTrue(); + + await utils.togglePanel('activities'); + + // Checking if onActivitiesPanelStatusChanged has been received + await utils.waitForElement('#onActivitiesPanelStatusChanged-false'); + expect(await utils.isPresent('#onActivitiesPanelStatusChanged-false')).toBeTrue(); + }); + + it('should receive the onSettingsPanelStatusChanged event', async () => { + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + await utils.checkToolbarIsPresent(); + + await utils.togglePanel('settings'); + + // Checking if onSettingsPanelStatusChanged has been received + await utils.waitForElement('#onSettingsPanelStatusChanged-true'); + expect(await utils.isPresent('#onSettingsPanelStatusChanged-true')).toBeTrue(); + + await utils.togglePanel('settings'); + + // Checking if onSettingsPanelStatusChanged has been received + await utils.waitForElement('#onSettingsPanelStatusChanged-false'); + expect(await utils.isPresent('#onSettingsPanelStatusChanged-false')).toBeTrue(); + }); + + it('should receive the onRecordingStartRequested event when clicking toolbar button', async () => { + const roomName = 'recordingToolbarEvent'; + await browser.get(`${url}&prejoin=false&roomName=${roomName}`); + + await utils.checkSessionIsPresent(); + await utils.checkToolbarIsPresent(); + + await utils.toggleRecordingFromToolbar(); + + // 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 () => {}); + + xit('should receive the onBroadcastingStopRequested event when clicking toolbar button', async () => { + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + await utils.checkToolbarIsPresent(); + + await utils.toggleToolbarMoreOptions(); + + await utils.waitForElement('#broadcasting-btn'); + await utils.clickOn('#broadcasting-btn'); + + await browser.sleep(500); + + await utils.waitForElement('.sidenav-menu'); + await utils.waitForElement('#activities-container'); + + await utils.waitForElement('#broadcasting-url-input'); + const input = await utils.waitForElement('#broadcast-url-input'); + await input.sendKeys('BroadcastUrl'); + await utils.clickOn('#broadcasting-btn'); + + // Open more options menu + await utils.toggleToolbarMoreOptions(); + + await utils.waitForElement('#broadcasting-btn'); + await utils.clickOn('#broadcasting-btn'); + + // Checking if onBroadcastingStopRequested has been received + await utils.waitForElement('#onBroadcastingStopRequested'); + expect(await utils.isPresent('#onBroadcastingStopRequested')).toBeTrue(); + }); + + it('should receive the onRecordingStartRequested when clicking from activities panel', async () => { + const roomName = 'recordingActivitiesEvent'; + await browser.get(`${url}&prejoin=false&roomName=${roomName}`); + + await utils.checkSessionIsPresent(); + await utils.checkToolbarIsPresent(); + + await utils.togglePanel('activities'); + + await browser.sleep(1000); + + // Open recording + await utils.waitForElement('ov-recording-activity'); + await utils.clickOn('ov-recording-activity'); + + await browser.sleep(1000); + + // Clicking to recording button + await utils.waitForElement('#start-recording-btn'); + await utils.clickOn('#start-recording-btn'); + + // Checking if onRecordingStartRequested has been received + await utils.waitForElement(`#onRecordingStartRequested-${roomName}`); + 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'; + await browser.get(`${url}&prejoin=false&roomName=${roomName}&fakeRecordings=true`); + + await utils.checkSessionIsPresent(); + + await utils.checkToolbarIsPresent(); + + // Clicking to activities button + const activitiesButton = await utils.waitForElement('#activities-panel-btn'); + expect(await utils.isPresent('#activities-panel-btn')).toBeTrue(); + await activitiesButton.click(); + + await browser.sleep(1500); + // Open recording + element = await utils.waitForElement('ov-recording-activity'); + await element.click(); + + await browser.sleep(1500); + + // Delete event + element = await utils.waitForElement('#delete-recording-btn'); + expect(await utils.isPresent('#delete-recording-btn')).toBeTrue(); + await element.click(); + + element = await utils.waitForElement('#delete-recording-confirm-btn'); + expect(await utils.isPresent('#delete-recording-confirm-btn')).toBeTrue(); + await element.click(); + + await utils.waitForElement(`#onRecordingDeleteRequested-${roomName}-fakeRecording`); + expect(await utils.isPresent(`#onRecordingDeleteRequested-${roomName}-fakeRecording`)).toBeTrue(); + }); + + it('should receive the onBroadcastingStartRequested event when clicking from panel', async () => { + const roomName = 'broadcastingStartEvent'; + const broadcastUrl = 'BroadcastUrl'; + await browser.get(`${url}&prejoin=false&roomName=${roomName}`); + + await utils.checkSessionIsPresent(); + await utils.checkToolbarIsPresent(); + + await utils.togglePanel('activities'); + + await browser.sleep(1000); + await utils.waitForElement('#broadcasting-activity'); + await utils.clickOn('#broadcasting-activity'); + + await browser.sleep(1000); + + const button = await utils.waitForElement('#broadcasting-btn'); + expect(await button.isEnabled()).toBeFalse(); + + const input = await utils.waitForElement('#broadcast-url-input'); + await input.sendKeys(broadcastUrl); + + await utils.clickOn('#broadcasting-btn'); + + // Checking if onBroadcastingStartRequested has been received + await utils.waitForElement(`#onBroadcastingStartRequested-${roomName}-${broadcastUrl}`); + expect(await utils.isPresent(`#onBroadcastingStartRequested-${roomName}-${broadcastUrl}`)).toBeTrue(); + }); + + xit('should receive the onBroadcastingStopRequested event when clicking from panel', async () => { + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + await utils.checkToolbarIsPresent(); + + // Open activities panel + await utils.togglePanel('activities'); + + await utils.waitForElement('#broadcasting-activity'); + await utils.clickOn('#broadcasting-activity'); + + const button = await utils.waitForElement('#broadcasting-btn'); + expect(await button.isEnabled()).toBeFalse(); + + const input = await utils.waitForElement('#broadcast-url-input'); + await input.sendKeys('BroadcastUrl'); + + await utils.clickOn('#broadcasting-btn'); + + expect(await utils.isPresent('#broadcasting-tag')).toBeTrue(); + + await utils.clickOn('#stop-broadcasting-btn'); + + // Checking if onBroadcastingStopRequested has been received + await utils.waitForElement('#onBroadcastingStopRequested'); + expect(await utils.isPresent('#onBroadcastingStopRequested')).toBeTrue(); + expect(await utils.isPresent('#broadcasting-tag')).toBeFalse(); + }); + + xit('should receive the onBroadcastingStopRequested event when clicking from toolbar', async () => { + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + await utils.checkToolbarIsPresent(); + + // Open more options menu + await utils.toggleToolbarMoreOptions(); + await utils.waitForElement('#broadcasting-btn'); + await utils.clickOn('#broadcasting-btn'); + + await browser.sleep(500); + + // Checking if onBroadcastingStopRequested has been received + await utils.waitForElement('#onBroadcastingStopRequested'); + expect(await utils.isPresent('#onBroadcastingStopRequested')).toBeTrue(); + expect(await utils.isPresent('#broadcasting-tag')).toBeFalse(); + }); + + it('should receive the onRoomCreated event', async () => { + await browser.get(`${url}&prejoin=false`); + + await utils.checkSessionIsPresent(); + + await utils.checkToolbarIsPresent(); + + await utils.waitForElement('#onRoomCreated'); + expect(await utils.isPresent('#onRoomCreated')).toBeTrue(); + + expect(await utils.isPresent('#onReadyToJoin')).toBeFalse(); + }); + + // * PUBLISHER EVENTS + + it('should receive onParticipantCreated event from LOCAL participant', async () => { + const participantName = 'TEST_USER'; + await browser.get(`${url}&participantName=${participantName}&prejoin=false`); + await utils.waitForElement(`#${participantName}-onParticipantCreated`); + expect(await utils.isPresent(`#${participantName}-onParticipantCreated`)).toBeTrue(); + }); + + it('should receive the onParticipantLeft event', async () => { + await browser.get(`${url}&prejoin=false&redirect=false`); + + await utils.checkSessionIsPresent(); + + await utils.checkToolbarIsPresent(); + + // Clicking to leave button + const leaveButton = await utils.waitForElement('#leave-btn'); + expect(await utils.isPresent('#leave-btn')).toBeTrue(); + await leaveButton.click(); + + // Checking if onParticipantLeft has been received + await utils.waitForElement('#onParticipantLeft'); + expect(await utils.isPresent('#onParticipantLeft')).toBeTrue(); + }); + + // * ROOM EVENTS + + //TODO: Implement a mechanism to emulate network disconnection + // it('should receive the onRoomDisconnected event', async () => { + // await browser.get(`${url}&prejoin=false`); + + // await utils.checkSessionIsPresent(); + + // await utils.checkToolbarIsPresent(); + + // // Emulate network disconnection + // await utils.forceCloseWebsocket(); + + // // Checking if onRoomDisconnected has been received + // await utils.waitForElement('#onRoomDisconnected'); + // expect(await utils.isPresent('#onRoomDisconnected')).toBeTrue(); + // }); +}); diff --git a/openvidu-components-angular/package.json b/openvidu-components-angular/package.json index c5b79b89..b2439689 100644 --- a/openvidu-components-angular/package.json +++ b/openvidu-components-angular/package.json @@ -92,11 +92,10 @@ "e2e:nested-attribute-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/attribute-directives.test.js", "e2e:lib-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/api-directives.test.js", "e2e:lib-chat": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/chat.test.js", + "e2e:lib-events": "tsc --project ./e2e && npx jasmine ./e2e/dist/events.test.js", "e2e:webcomponent-all": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/**/*.test.js", "e2e:webcomponent-captions": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/captions.test.js", - "e2e:webcomponent-chat": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/chat.test.js", - "e2e:webcomponent-events": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/events.test.js", "e2e:webcomponent-media-devices": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/media-devices.test.js", "e2e:webcomponent-panels": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/panels.test.js", "e2e:webcomponent-screensharing": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/screensharing.test.js", diff --git a/openvidu-components-angular/src/app/openvidu-call/call.component.html b/openvidu-components-angular/src/app/openvidu-call/call.component.html index c69befd0..b7e78f47 100644 --- a/openvidu-components-angular/src/app/openvidu-call/call.component.html +++ b/openvidu-components-angular/src/app/openvidu-call/call.component.html @@ -30,9 +30,11 @@ [activitiesPanelRecordingActivity]="activitiesPanelRecordingActivity" [activitiesPanelBroadcastingActivity]="activitiesPanelBroadcastingActivity" [toolbarSettingsButton]="toolbarSettingsButton" + (onTokenRequested)="onTokenRequested($event)" (onReadyToJoin)="onReadyToJoin()" (onRoomCreated)="onRoomCreated($event)" + (onParticipantCreated)="onParticipantCreated($event)" (onParticipantLeft)="onParticipantLeft($event)" (onRoomDisconnected)="onRoomDisconnected()" (onVideoEnabledChanged)="onVideoEnabledChanged($event)" @@ -49,7 +51,12 @@ (onRecordingDeleteRequested)="onRecordingDeleteRequested($event)" (onBroadcastingStartRequested)="onBroadcastingStartRequested($event)" (onBroadcastingStopRequested)="onBroadcastingStopRequested($event)" + (onSettingsPanelStatusChanged)="onSettingsPanelStatusChanged($event)" + (onActivitiesPanelStatusChanged)="onActivitiesPanelStatusChanged($event)" > } + + +
\ No newline at end of file diff --git a/openvidu-components-angular/src/app/openvidu-call/call.component.ts b/openvidu-components-angular/src/app/openvidu-call/call.component.ts index 6a7a6dac..6fe65c3b 100644 --- a/openvidu-components-angular/src/app/openvidu-call/call.component.ts +++ b/openvidu-components-angular/src/app/openvidu-call/call.component.ts @@ -12,7 +12,8 @@ import { RestService } from '../services/rest.service'; import { CustomDevice } from 'dist/openvidu-components-angular/lib/models/device.model'; import { LangOption } from 'dist/openvidu-components-angular/lib/models/lang.model'; import { ActivatedRoute, Router } from '@angular/router'; -import { ParticipantLeftEvent } from '../../../projects/openvidu-components-angular/src/lib/models/participant.model'; +import { ParticipantLeftEvent, ParticipantModel } from '../../../projects/openvidu-components-angular/src/lib/models/participant.model'; +import { monkeyPatchMediaDevices } from '../utils/media-devices'; @Component({ selector: 'app-call', @@ -58,6 +59,8 @@ export class CallComponent implements OnInit { activitiesPanelRecordingActivity: boolean = true; activitiesPanelBroadcastingActivity: boolean = true; toolbarSettingsButton: boolean = true; + fakeDevices: boolean = false; + private redirectOnLeaves: boolean = true; private staticVideos = [ 'https://videos.pexels.com/video-files/4089575/4089575-hd_1280_720_50fps.mp4', @@ -110,8 +113,7 @@ export class CallComponent implements OnInit { if (params['cameraBtn'] !== undefined) this.toolbarCameraButton = params['cameraBtn'] === 'true'; if (params['toolbarMicrophoneButton'] !== undefined) this.toolbarMicrophoneButton = params['toolbarMicrophoneButton'] === 'true'; - if (params['screenshareBtn'] !== undefined) - this.toolbarScreenshareButton = params['screenshareBtn'] === 'true'; + if (params['screenshareBtn'] !== undefined) this.toolbarScreenshareButton = params['screenshareBtn'] === 'true'; if (params['fullscreenBtn'] !== undefined) this.toolbarFullscreenButton = params['fullscreenBtn'] === 'true'; if (params['toolbarRecordingButton'] !== undefined) this.toolbarRecordingButton = params['toolbarRecordingButton'] === 'true'; if (params['toolbarBroadcastingButton'] !== undefined) @@ -130,8 +132,7 @@ export class CallComponent implements OnInit { if (params['displayAudioDetection'] !== undefined) this.streamDisplayAudioDetection = params['displayAudioDetection'] === 'true'; if (params['streamVideoControls'] !== undefined) this.streamVideoControls = params['streamVideoControls'] === 'true'; - if (params['participantMuteBtn'] !== undefined) - this.participantPanelItemMuteButton = params['participantMuteBtn'] === 'true'; + if (params['participantMuteBtn'] !== undefined) this.participantPanelItemMuteButton = params['participantMuteBtn'] === 'true'; if (params['activitiesPanelRecordingActivity'] !== undefined) this.activitiesPanelRecordingActivity = params['activitiesPanelRecordingActivity'] === 'true'; if (params['activitiesPanelBroadcastingActivity'] !== undefined) @@ -139,31 +140,48 @@ export class CallComponent implements OnInit { if (params['toolbarSettingsBtn'] !== undefined) this.toolbarSettingsButton = params['toolbarSettingsBtn'] === 'true'; if (params['staticVideos'] !== undefined) this.areStaticVideosEnabled = params['staticVideos'] === 'true'; + if (params['fakeDevices'] !== undefined) this.fakeDevices = params['fakeDevices'] === 'true'; + + if (params['redirect'] === undefined) { + this.redirectOnLeaves = true; + } else { + this.redirectOnLeaves = params['redirect'] === 'true'; + } this.configReady = true; + + if (this.areStaticVideosEnabled) { + setTimeout(() => { + const videoElements = document.querySelectorAll('video'); + this.replaceWithStaticVideos(videoElements); + }, 3000); + } + + if (this.fakeDevices) { + console.warn('Using fake devices'); + monkeyPatchMediaDevices(); + } }); - if (this.areStaticVideosEnabled) { - setTimeout(() => { - const videoElements = document.querySelectorAll('video'); - this.replaceWithStaticVideos(videoElements); - }, 3000); - } } async onTokenRequested(participantName: string) { console.warn('VC TOKEN REQUESTED', participantName); + this.appendElement('onTokenRequested'); await this.requestForTokens(participantName); } async onReadyToJoin() { + this.appendElement('onReadyToJoin'); console.warn('VC IS READY TO JOIN'); } async onParticipantLeft(event: ParticipantLeftEvent) { + this.appendElement('onParticipantLeft'); console.warn('VC PARTICIPANT LEFT', event); - await this.router.navigate(['/']); + if (this.redirectOnLeaves) await this.router.navigate(['/']); } onRoomCreated(room: Room) { + this.appendElement('onRoomCreated'); console.warn('VC ROOM CREATED', room.name); room.on(RoomEvent.Connected, () => { if (this.areStaticVideosEnabled) { @@ -189,57 +207,86 @@ export class CallComponent implements OnInit { }); } + onParticipantCreated(event: ParticipantModel) { + this.appendElement(event.name + '-onParticipantCreated'); + console.warn('VC PARTICIPANT CREATED', event); + } + onVideoEnabledChanged(value: boolean) { + this.appendElement('onVideoEnabledChanged-' + value); console.warn('VC video enabled: ', value); } onVideoDeviceChanged(device: CustomDevice) { + this.appendElement('onVideoDeviceChanged'); console.warn('VC video device changed: ', device); } onAudioEnabledChanged(value: boolean) { + this.appendElement('onAudioEnabledChanged-' + value); console.warn('VC audio enabled: ', value); } onAudioDeviceChanged(device: CustomDevice) { + this.appendElement('onAudioDeviceChanged'); console.warn('VC audio device changed: ', device); } onScreenShareEnabledChanged(enabled: boolean) { + this.appendElement('onScreenShareEnabledChanged'); console.warn('VC screenshare enabled: ', enabled); } onFullscreenEnabledChanged(enabled: boolean) { + this.appendElement('onFullscreenEnabledChanged-' + enabled); console.warn('VC fullscreen enabled: ', enabled); } onParticipantsPanelStatusChanged(event) { + this.appendElement('onParticipantsPanelStatusChanged-' + event.isOpened); console.warn('VC participants panel status changed: ', event); } onChatPanelStatusChanged(event) { + this.appendElement('onChatPanelStatusChanged-' + event.isOpened); console.warn('VC chat status changed: ', event); } async onRoomDisconnected() { + this.appendElement('onRoomDisconnected'); this.isSessionAlive = false; console.log('VC LEAVE BUTTON CLICKED'); await this.router.navigate(['/']); } onFullscreenButtonClicked() { + this.appendElement('onFullscreenButtonClicked'); console.warn('TOOLBAR fullscreen CLICKED'); } onParticipantsPanelButtonClicked() { + this.appendElement('onParticipantsPanelButtonClicked'); console.warn('TOOLBAR participants CLICKED'); } onChatPanelButtonClicked() { + this.appendElement('onChatPanelButtonClicked'); console.warn('TOOLBAR chat CLICKED'); } onLeaveButtonClicked() { + this.appendElement('onLeaveButtonClicked'); this.isSessionAlive = false; console.log('TOOLBAR LEAVE CLICKED'); } onLangChanged(event: LangOption) { + this.appendElement('onLangChanged-' + event.lang); console.warn('LANG CHANGED', event); } + onSettingsPanelStatusChanged(event) { + this.appendElement('onSettingsPanelStatusChanged-' + event.isOpened); + console.warn('VC settings panel status changed: ', event); + } + + onActivitiesPanelStatusChanged(event) { + this.appendElement('onActivitiesPanelStatusChanged-' + event.isOpened); + console.warn('VC activities panel status changed: ', event); + } async onBroadcastingStartRequested(event: BroadcastingStartRequestedEvent) { + this.appendElement(`onBroadcastingStartRequested-${event.roomName}-${event.broadcastUrl}`); console.log('START STREAMING', event); try { const resp = await this.restService.startBroadcasting(event.broadcastUrl); @@ -248,8 +295,8 @@ export class CallComponent implements OnInit { console.error(error); } } - async onBroadcastingStopRequested(event: BroadcastingStopRequestedEvent) { + this.appendElement('onBroadcastingStopRequested'); console.log('STOP STREAMING', event); try { const resp = await this.restService.stopBroadcasting(); @@ -260,6 +307,7 @@ export class CallComponent implements OnInit { } async onRecordingStartRequested(event: RecordingStartRequestedEvent) { + this.appendElement('onRecordingStartRequested-' + event.roomName); console.warn('START RECORDING CLICKED', event); try { await this.restService.startRecording(this.roomName); @@ -268,6 +316,7 @@ export class CallComponent implements OnInit { } } async onRecordingStopRequested(event: RecordingStopRequestedEvent) { + this.appendElement('onRecordingStopRequested'); console.warn('STOP RECORDING CLICKED', event); try { await this.restService.stopRecording(event); @@ -277,6 +326,7 @@ export class CallComponent implements OnInit { } async onRecordingDeleteRequested(event: RecordingDeleteRequestedEvent) { + this.appendElement('onRecordingDeleteRequested'); console.warn('DELETE RECORDING requested', event); try { @@ -311,4 +361,13 @@ export class CallComponent implements OnInit { }); } } + + private appendElement(id: string) { + var eventsDiv = document.getElementById('events'); + eventsDiv?.setAttribute('style', 'position: absolute;'); + var element = document.createElement('div'); + element.setAttribute('id', id); + element.setAttribute('style', 'height: 1px;'); + eventsDiv?.appendChild(element); + } } diff --git a/openvidu-components-angular/src/app/utils/filter-stream.js b/openvidu-components-angular/src/app/utils/filter-stream.js new file mode 100644 index 00000000..485bc547 --- /dev/null +++ b/openvidu-components-angular/src/app/utils/filter-stream.js @@ -0,0 +1,30 @@ +class FilterStream { + constructor(stream, label) { + const videoTrack = stream.getVideoTracks()[0]; + const { width, height } = videoTrack.getSettings(); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + const video = document.createElement('video'); + video.srcObject = new MediaStream([videoTrack]); + video.play(); + + video.addEventListener('play', () => { + const loop = () => { + if (!video.paused && !video.ended) { + ctx.filter = 'grayscale(100%)'; + ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, video.videoWidth, video.videoHeight); + setTimeout(loop, 33); + } + }; + loop(); + }); + this.outputStream = canvas.captureStream(); + + Object.defineProperty(this.outputStream.getVideoTracks()[0], 'label', { + writable: true, + value: label + }); + } +} + +export { FilterStream }; diff --git a/openvidu-components-angular/src/app/utils/media-devices.js b/openvidu-components-angular/src/app/utils/media-devices.js new file mode 100644 index 00000000..b783e221 --- /dev/null +++ b/openvidu-components-angular/src/app/utils/media-devices.js @@ -0,0 +1,90 @@ +// Ideally we'd use an editor or import shaders directly from the API. +import { FilterStream } from './filter-stream.js'; + +export const monkeyPatchMediaDevices = () => { + const enumerateDevicesFn = MediaDevices.prototype.enumerateDevices; + const getUserMediaFn = MediaDevices.prototype.getUserMedia; + const getDisplayMediaFn = MediaDevices.prototype.getDisplayMedia; + + const fakeVideoDevice = { + deviceId: 'virtual_video', + groupId: '', + kind: 'videoinput', + label: 'custom_fake_video_1' + }; + + const fakeAudioDevice = { + deviceId: 'virtual_audio', + groupId: '', + kind: 'audioinput', + label: 'custom_fake_audio_1' + }; + + const enumerateDevicesMonkeyPatch = async function () { + const res = await enumerateDevicesFn.call(navigator.mediaDevices); + res.push(fakeVideoDevice); + res.push(fakeAudioDevice); + return res; + }; + + const getUserMediaMonkeyPatch = async function () { + const args = arguments[0]; + + if (args.audio && (args.audio.deviceId === 'virtual_audio' || args.audio.deviceId?.exact === 'virtual_audio')) { + const constraints = { + audio: { + facingMode: args.facingMode, + advanced: args.audio.advanced, + deviceId: fakeAudioDevice.deviceId + }, + video: false + }; + const res = await getUserMediaFn.call(navigator.mediaDevices, constraints); + return res; + } else if (args.video && (args.video.deviceId === 'virtual_video' || args.video.deviceId?.exact === 'virtual_video')) { + const { deviceId, advanced, width, height } = args.video; + + const constraints = { + video: { + facingMode: args.facingMode, + advanced, + width, + height + }, + audio: false + }; + const res = await getUserMediaFn.call(navigator.mediaDevices, constraints); + + if (res) { + const filter = new FilterStream(res, fakeVideoDevice.label); + return filter.outputStream; + } + + return res; + } + + return getUserMediaFn.call(navigator.mediaDevices, ...arguments); + }; + + const getDisplayMediaMonkeyPatch = async function () { + const { video, audio } = arguments[0]; + + const screenVideoElement = document.getElementsByClassName('OV_video-element screen-type')[0]; + const currentTrackLabel = screenVideoElement?.srcObject?.getVideoTracks()[0]?.label; + const res = await getDisplayMediaFn.call(navigator.mediaDevices, { video, audio }); + + if (res && currentTrackLabel && currentTrackLabel !== 'custom_fake_screen') { + const filter = new FilterStream(res, 'custom_fake_screen'); + return filter.outputStream; + } + + return res; + }; + + MediaDevices.prototype.enumerateDevices = enumerateDevicesMonkeyPatch; + navigator.mediaDevices.enumerateDevices = enumerateDevicesMonkeyPatch; + MediaDevices.prototype.getUserMedia = getUserMediaMonkeyPatch; + navigator.mediaDevices.getUserMedia = getUserMediaMonkeyPatch; + MediaDevices.prototype.getDisplayMedia = getDisplayMediaMonkeyPatch; + navigator.mediaDevices.getDisplayMedia = getDisplayMediaMonkeyPatch; +}